<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Shiny.NET | Blog</title><description/><link>https://www.shinylib.net/</link><language>en</language><item><title>Introducing Shiny.Music — Cross-Platform Music Library Access for .NET MAUI</title><link>https://www.shinylib.net/blog/shiny-music/</link><guid isPermaLink="true">https://www.shinylib.net/blog/shiny-music/</guid><pubDate>Sun, 01 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Here’s something that shouldn’t be hard but is: accessing the music library on a user’s device from .NET MAUI.&lt;/p&gt;
&lt;p&gt;On Android, you need &lt;code dir=&quot;auto&quot;&gt;MediaStore.Audio.Media&lt;/code&gt;, a &lt;code dir=&quot;auto&quot;&gt;ContentResolver&lt;/code&gt;, cursor iteration, and different permission models depending on whether you’re targeting API 33+ or older.  On iOS, you need &lt;code dir=&quot;auto&quot;&gt;MPMediaQuery&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;MPMediaItem&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;AVAudioPlayer&lt;/code&gt;, and an &lt;code dir=&quot;auto&quot;&gt;NSAppleMusicUsageDescription&lt;/code&gt; entry or the app crashes on launch.  There’s no MAUI abstraction.  No popular NuGet package.  You write platform-specific code from scratch every time.&lt;/p&gt;
&lt;p&gt;So I built &lt;a href=&quot;https://github.com/shinyorg/music&quot;&gt;Shiny.Music&lt;/a&gt; — a clean, DI-first API that gives you permission management, metadata queries, playback controls, and file export across both platforms.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;what-is-it&quot;&gt;What Is It?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Two interfaces, one registration call:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;IMediaLibrary&lt;/code&gt;&lt;/strong&gt; — request permissions, query all tracks, search by title/artist/album, copy track files to app storage&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;IMusicPlayer&lt;/code&gt;&lt;/strong&gt; — play, pause, resume, stop, seek, with state tracking and completion events&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;MauiProgram.cs&lt;/span&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;builder.Services.&lt;/span&gt;&lt;span&gt;AddShinyMusic&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That registers both interfaces as singletons.  Inject them anywhere.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;quick-start&quot;&gt;Quick Start&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MusicPage&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IMediaLibrary&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;library&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IMusicPlayer&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;player&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MusicPage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;IMediaLibrary&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;library&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;IMusicPlayer&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;player&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.library &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; library;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.player &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; player;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LoadAndPlay&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 1. Request permission&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; library.&lt;/span&gt;&lt;span&gt;RequestPermissionAsync&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (status &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; PermissionStatus.Granted)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 2. Browse the library&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;allTracks&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; library.&lt;/span&gt;&lt;span&gt;GetAllTracksAsync&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 3. Or search&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;results&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; library.&lt;/span&gt;&lt;span&gt;SearchTracksAsync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Bohemian&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 4. Play a track&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; player.&lt;/span&gt;&lt;span&gt;PlayAsync&lt;/span&gt;&lt;span&gt;(results[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 5. Control playback&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;player.&lt;/span&gt;&lt;span&gt;Pause&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;player.&lt;/span&gt;&lt;span&gt;Resume&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;player.&lt;/span&gt;&lt;span&gt;Seek&lt;/span&gt;&lt;span&gt;(TimeSpan.&lt;/span&gt;&lt;span&gt;FromSeconds&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;30&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;player.&lt;/span&gt;&lt;span&gt;Stop&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;the-media-library&quot;&gt;The Media Library&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;permissions&quot;&gt;Permissions&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Permissions differ between platforms:&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Platform&lt;/th&gt;&lt;th&gt;Permission&lt;/th&gt;&lt;th&gt;Notes&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Android 13+&lt;/strong&gt; (API 33)&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;READ_MEDIA_AUDIO&lt;/code&gt;&lt;/td&gt;&lt;td&gt;New granular media permission&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Android 12 and below&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;READ_EXTERNAL_STORAGE&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Legacy broad storage permission&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;iOS&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Apple Music usage description&lt;/td&gt;&lt;td&gt;Must be in &lt;code dir=&quot;auto&quot;&gt;Info.plist&lt;/code&gt; or app crashes&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The library handles this automatically.  &lt;code dir=&quot;auto&quot;&gt;RequestPermissionAsync()&lt;/code&gt; prompts the user with the correct platform permission.  &lt;code dir=&quot;auto&quot;&gt;CheckPermissionAsync()&lt;/code&gt; checks the current status without prompting.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Android &lt;code dir=&quot;auto&quot;&gt;AndroidManifest.xml&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;uses-permission&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;android:name&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;android.permission.READ_MEDIA_AUDIO&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;uses-permission&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;android:name&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;android.permission.READ_EXTERNAL_STORAGE&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                 &lt;/span&gt;&lt;span&gt;android:maxSdkVersion&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;32&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;iOS &lt;code dir=&quot;auto&quot;&gt;Info.plist&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&gt;NSAppleMusicUsageDescription&amp;#x3C;/&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&gt;This app needs access to your music library to browse and play your music.&amp;#x3C;/&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;track-metadata&quot;&gt;Track Metadata&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Every track comes back as a &lt;code dir=&quot;auto&quot;&gt;MusicMetadata&lt;/code&gt; record:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MusicMetadata&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Id&lt;/span&gt;&lt;span&gt;,           &lt;/span&gt;&lt;span&gt;// Platform-specific unique ID&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Artist&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Album&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;? &lt;/span&gt;&lt;span&gt;Genre&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;TimeSpan&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Duration&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;? &lt;/span&gt;&lt;span&gt;AlbumArtUri&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;// Android only — null on iOS&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ContentUri&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;// content:// (Android) or ipod-library:// (iOS)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;querying-tracks&quot;&gt;Querying Tracks&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Get everything&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tracks&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; library.&lt;/span&gt;&lt;span&gt;GetAllTracksAsync&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Search across title, artist, and album&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;results&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; library.&lt;/span&gt;&lt;span&gt;SearchTracksAsync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Beatles&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;On Android, queries go through &lt;code dir=&quot;auto&quot;&gt;MediaStore.Audio.Media&lt;/code&gt; with &lt;code dir=&quot;auto&quot;&gt;ContentResolver&lt;/code&gt;.  On iOS, they use &lt;code dir=&quot;auto&quot;&gt;MPMediaQuery&lt;/code&gt; with &lt;code dir=&quot;auto&quot;&gt;MPMediaPropertyPredicate&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;file-export&quot;&gt;File Export&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Need to copy a track to your app’s storage?&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;destination&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Path.&lt;/span&gt;&lt;span&gt;Combine&lt;/span&gt;&lt;span&gt;(FileSystem.AppDataDirectory, &lt;/span&gt;&lt;span&gt;&quot;exported.m4a&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;success&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; library.&lt;/span&gt;&lt;span&gt;CopyTrackAsync&lt;/span&gt;&lt;span&gt;(track, destination);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Returns &lt;code dir=&quot;auto&quot;&gt;false&lt;/code&gt; if the copy isn’t possible — which brings us to the DRM caveat.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;the-music-player&quot;&gt;The Music Player&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;playback-controls&quot;&gt;Playback Controls&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; player.&lt;/span&gt;&lt;span&gt;PlayAsync&lt;/span&gt;&lt;span&gt;(track);  &lt;/span&gt;&lt;span&gt;// Load and play&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;player.&lt;/span&gt;&lt;span&gt;Pause&lt;/span&gt;&lt;span&gt;();                  &lt;/span&gt;&lt;span&gt;// Pause at current position&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;player.&lt;/span&gt;&lt;span&gt;Resume&lt;/span&gt;&lt;span&gt;();                 &lt;/span&gt;&lt;span&gt;// Resume from paused position&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;player.&lt;/span&gt;&lt;span&gt;Seek&lt;/span&gt;&lt;span&gt;(TimeSpan.&lt;/span&gt;&lt;span&gt;FromMinutes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;));  &lt;/span&gt;&lt;span&gt;// Jump to position&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;player.&lt;/span&gt;&lt;span&gt;Stop&lt;/span&gt;&lt;span&gt;();                   &lt;/span&gt;&lt;span&gt;// Stop and release&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;state-tracking&quot;&gt;State Tracking&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;PlaybackState&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; player.State;       &lt;/span&gt;&lt;span&gt;// Stopped, Playing, or Paused&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;MusicMetadata&lt;/span&gt;&lt;span&gt;? &lt;/span&gt;&lt;span&gt;current&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; player.CurrentTrack;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;TimeSpan&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;position&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; player.Position;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;TimeSpan&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;duration&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; player.Duration;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;player.StateChanged &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;sender&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// React to state transitions&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;player.PlaybackCompleted &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;sender&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// Track finished — load next?&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;On Android, playback uses &lt;code dir=&quot;auto&quot;&gt;Android.Media.MediaPlayer&lt;/code&gt;.  On iOS, it uses &lt;code dir=&quot;auto&quot;&gt;AVAudioPlayer&lt;/code&gt; via AVFoundation.  Both are properly managed — resources are released on stop and disposal.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;the-drm-caveat&quot;&gt;The DRM Caveat&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This is important to know upfront.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;On iOS&lt;/strong&gt;, Apple Music subscription tracks (DRM-protected) &lt;strong&gt;cannot be played or copied&lt;/strong&gt; through this API.  For these tracks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;ContentUri&lt;/code&gt; will be an empty string&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;CopyTrackAsync&lt;/code&gt; returns &lt;code dir=&quot;auto&quot;&gt;false&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;PlayAsync&lt;/code&gt; throws &lt;code dir=&quot;auto&quot;&gt;InvalidOperationException&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Only locally synced or purchased (non-DRM) tracks work.  The metadata (title, artist, album, duration) is still available for all tracks — you just can’t access the audio data for DRM-protected ones.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;On Android&lt;/strong&gt;, all locally stored music files work without restrictions via &lt;code dir=&quot;auto&quot;&gt;ContentResolver&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is an OS-level limitation, not a library limitation.  iOS simply does not expose the audio asset URL for DRM-protected media items.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;platform-support&quot;&gt;Platform Support&lt;/h2&gt;&lt;/div&gt;























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Platform&lt;/th&gt;&lt;th&gt;Minimum Version&lt;/th&gt;&lt;th&gt;Audio Query&lt;/th&gt;&lt;th&gt;Playback Engine&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Android&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;API 24 (Android 7.0)&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;MediaStore.Audio.Media&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;Android.Media.MediaPlayer&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;iOS&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;15.0&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;MPMediaQuery&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;AVAudioPlayer&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Both platforms require a &lt;strong&gt;physical device&lt;/strong&gt; for meaningful testing — simulators and emulators don’t have music content.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;when-to-use-it&quot;&gt;When to Use It&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Good fit:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Music player apps that browse the device library&lt;/li&gt;
&lt;li&gt;Apps that need to search or display the user’s music collection&lt;/li&gt;
&lt;li&gt;Exporting audio files for processing (transcription, analysis, sharing)&lt;/li&gt;
&lt;li&gt;Any app that integrates with locally stored music&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Not the best fit:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Streaming music from web sources (use &lt;code dir=&quot;auto&quot;&gt;MediaElement&lt;/code&gt; or a streaming library)&lt;/li&gt;
&lt;li&gt;Audio recording (use &lt;a href=&quot;https://github.com/jfversluis/Plugin.Maui.Audio&quot;&gt;Plugin.Maui.Audio&lt;/a&gt; or platform audio APIs)&lt;/li&gt;
&lt;li&gt;Background audio playback with lock screen controls (this is foreground playback)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;get-started&quot;&gt;Get Started&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;dotnet&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;package&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Shiny.Music&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Full source and sample app at the &lt;a href=&quot;https://github.com/shinyorg/music&quot;&gt;GitHub repository&lt;/a&gt;.  Documentation coming soon at &lt;a href=&quot;https://shinylib.net/client/music&quot;&gt;shinylib.net/client/music&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>Music</category></item><item><title>Introducing Shiny.Spatial — A Dependency-Free Spatial Database and GPS Geofencing for .NET</title><link>https://www.shinylib.net/blog/shiny-spatial/</link><guid isPermaLink="true">https://www.shinylib.net/blog/shiny-spatial/</guid><pubDate>Sun, 01 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you’ve ever tried to do geospatial work on .NET MAUI, you know the options aren’t great.  SpatiaLite requires native binaries per platform.  NetTopologySuite is a full-featured geometry library — great on the server, but heavy on mobile and hostile to AOT.  And the built-in platform geofencing?  iOS gives you 20 circular regions.  Android gives you 60.  That’s it.&lt;/p&gt;
&lt;p&gt;I needed something different — a spatial database that works everywhere .NET runs, with zero native dependencies, and a geofencing system that can monitor thousands of polygon regions from a real geographic database.  So I built &lt;a href=&quot;https://github.com/shinyorg/geospatialdb&quot;&gt;Shiny.Spatial&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;what-is-it&quot;&gt;What Is It?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Two NuGet packages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;Shiny.Spatial&lt;/code&gt;&lt;/strong&gt; — a spatial database engine that stores geometry in SQLite using R*Tree virtual tables, with all spatial algorithms implemented in pure C#.  No SpatiaLite, no NetTopologySuite, no native binaries.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;Shiny.Spatial.Geofencing&lt;/code&gt;&lt;/strong&gt; — a GPS-driven geofence monitor that watches spatial database tables for region entry/exit instead of registering individual fences with the OS.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The spatial database targets &lt;code dir=&quot;auto&quot;&gt;netstandard2.0&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;net10.0&lt;/code&gt;, so it runs on MAUI, Blazor, console apps, servers — anywhere SQLite runs.  The geofencing package targets iOS and Android via MAUI.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;the-spatial-database&quot;&gt;The Spatial Database&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;two-pass-query-pipeline&quot;&gt;Two-Pass Query Pipeline&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Every spatial query follows the same pattern:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pass 1 — R*Tree bounding box filter (SQL, O(log n)).&lt;/strong&gt;  SQLite’s R*Tree virtual table eliminates most candidates based on bounding box overlap.  This runs entirely in SQL and is extremely fast.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pass 2 — C# geometry refinement.&lt;/strong&gt;  Surviving candidates are tested with exact geometric predicates — point-in-polygon (ray-casting), segment intersection (cross-product), Haversine distance — in pure C#.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This two-pass approach is the same strategy PostGIS and SpatiaLite use internally.  The difference is that both passes here require zero native extensions.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// What happens under the hood for an Intersecting query:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Pass 1: SELECT * FROM cities_rtree WHERE min_x &amp;#x3C;= @maxX AND max_x &gt;= @minX ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Pass 2: SpatialPredicates.Intersects(candidate.Geometry, queryGeometry)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;creating-a-database&quot;&gt;Creating a Database&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SpatialDatabase&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;locations.db&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;table&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; db.&lt;/span&gt;&lt;span&gt;CreateTable&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;cities&quot;&lt;/span&gt;&lt;span&gt;, CoordinateSystem.Wgs84,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PropertyDefinition&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;name&quot;&lt;/span&gt;&lt;span&gt;, PropertyType.Text),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;PropertyDefinition&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;population&quot;&lt;/span&gt;&lt;span&gt;, PropertyType.Integer)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Each table gets a single R*Tree virtual table with auxiliary columns for WKB-encoded geometry and user-defined properties.  No separate tables, no JOINs.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;geometry-types&quot;&gt;Geometry Types&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;All geometry classes are immutable and sealed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;Point&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;LineString&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Polygon&lt;/code&gt; (with holes)&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;MultiPoint&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;MultiLineString&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;MultiPolygon&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;GeometryCollection&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Coordinates use &lt;code dir=&quot;auto&quot;&gt;double X&lt;/code&gt; (longitude) and &lt;code dir=&quot;auto&quot;&gt;double Y&lt;/code&gt; (latitude).  &lt;code dir=&quot;auto&quot;&gt;Polygon&lt;/code&gt; supports interior rings (holes) — useful for real geographic boundaries like city limits with excluded areas.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// A simple polygon — Colorado&apos;s approximate boundary&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;colorado&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Polygon&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;[]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Coordinate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;109.05&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;37.0&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Coordinate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;109.05&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;41.0&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Coordinate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;102.05&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;41.0&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Coordinate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;102.05&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;37.0&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Coordinate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;109.05&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;37.0&lt;/span&gt;&lt;span&gt;)  &lt;/span&gt;&lt;span&gt;// closed ring&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;inserting-data&quot;&gt;Inserting Data&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Single insert&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;feature&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SpatialFeature&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Point&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;104.99&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;39.74&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Dictionary&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;object&lt;/span&gt;&lt;span&gt;?&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;name&quot;&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Denver&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;population&quot;&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;715522&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;table.&lt;/span&gt;&lt;span&gt;Insert&lt;/span&gt;&lt;span&gt;(feature);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Bulk insert — transaction-wrapped&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;table.&lt;/span&gt;&lt;span&gt;BulkInsert&lt;/span&gt;&lt;span&gt;(features);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;querying--the-fluent-builder&quot;&gt;Querying — The Fluent Builder&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;table.Query()&lt;/code&gt; returns a fluent builder that chains spatial filters, property filters, distance ordering, and paging:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Find all cities within 50km of Denver, ordered by distance&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;nearby&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; table.&lt;/span&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;WithinDistance&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Coordinate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;104.99&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;39.74&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;50_000&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;OrderByDistance&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Coordinate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;104.99&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;39.74&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Limit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ToList&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Find features inside a polygon&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;inColorado&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; table.&lt;/span&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Intersecting&lt;/span&gt;&lt;span&gt;(coloradoBoundary)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ToList&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Combine spatial + property filters&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;largeCities&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; table.&lt;/span&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;WithinDistance&lt;/span&gt;&lt;span&gt;(center, &lt;/span&gt;&lt;span&gt;100_000&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;WhereProperty&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;population&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;100000&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;OrderByDistance&lt;/span&gt;&lt;span&gt;(center)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Limit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ToList&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Paging&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;page2&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; table.&lt;/span&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;WhereProperty&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;state&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;=&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;Colorado&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Limit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Offset&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ToList&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Property filters run in SQL (Pass 1), spatial filters run as C# refinement (Pass 2), and distance ordering plus limit/offset are applied after refinement.  The query builder composes them automatically.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;direct-query-methods&quot;&gt;Direct Query Methods&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;For simple cases, skip the builder:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;feature&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; table.&lt;/span&gt;&lt;span&gt;GetById&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;42&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;inBox&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; table.&lt;/span&gt;&lt;span&gt;FindInEnvelope&lt;/span&gt;&lt;span&gt;(envelope);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;intersecting&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; table.&lt;/span&gt;&lt;span&gt;FindIntersecting&lt;/span&gt;&lt;span&gt;(polygon);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;contained&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; table.&lt;/span&gt;&lt;span&gt;FindContainedBy&lt;/span&gt;&lt;span&gt;(polygon);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;nearby&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; table.&lt;/span&gt;&lt;span&gt;FindWithinDistance&lt;/span&gt;&lt;span&gt;(center, &lt;/span&gt;&lt;span&gt;10_000&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;// meters&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;performance&quot;&gt;Performance&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;All benchmarks on Apple M2, .NET 10, in-memory SQLite, 100K point features:&lt;/p&gt;

































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Query&lt;/th&gt;&lt;th&gt;Mean&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;FindIntersecting (polygon)&lt;/td&gt;&lt;td&gt;1.15 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;FindWithinDistance&lt;/td&gt;&lt;td&gt;183 us&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;FindContainedBy&lt;/td&gt;&lt;td&gt;987 us&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;GetById&lt;/td&gt;&lt;td&gt;9.4 us&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Fluent: spatial + property filter&lt;/td&gt;&lt;td&gt;1.44 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Fluent: distance + order + limit&lt;/td&gt;&lt;td&gt;254 us&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Insert&lt;/th&gt;&lt;th&gt;Count&lt;/th&gt;&lt;th&gt;Mean&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;BulkInsert&lt;/td&gt;&lt;td&gt;1,000&lt;/td&gt;&lt;td&gt;9.8 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;BulkInsert&lt;/td&gt;&lt;td&gt;10,000&lt;/td&gt;&lt;td&gt;93 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;BulkInsert&lt;/td&gt;&lt;td&gt;100,000&lt;/td&gt;&lt;td&gt;964 ms&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The pure algorithms are fast too — Haversine at 28ns, point-in-polygon (5 vertices) at 24ns, segment intersection at 3ns.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;serialization&quot;&gt;Serialization&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Geometry is stored as WKB (Well-Known Binary), the same binary format used by PostGIS, SpatiaLite, and every major GIS tool.  The built-in &lt;code dir=&quot;auto&quot;&gt;WkbReader&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;WkbWriter&lt;/code&gt; handle all seven geometry types.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;gps-geofencing&quot;&gt;GPS Geofencing&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This is where it gets interesting for MAUI developers.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;the-problem-with-platform-geofencing&quot;&gt;The Problem with Platform Geofencing&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Native geofencing on mobile has hard limits:&lt;/p&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;iOS&lt;/th&gt;&lt;th&gt;Android&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Max regions&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;20&lt;/td&gt;&lt;td&gt;60&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Shape&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Circle only&lt;/td&gt;&lt;td&gt;Circle only&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Precision&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;OS-determined&lt;/td&gt;&lt;td&gt;OS-determined&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Timing&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;OS-determined&lt;/td&gt;&lt;td&gt;OS-determined&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Handler time&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;~4 seconds&lt;/td&gt;&lt;td&gt;Limited&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Emulator testing&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Works&lt;/td&gt;&lt;td&gt;Unreliable&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;If your app needs to know when a user enters Denver, you register a circular region around Denver’s center.  But Denver isn’t a circle — it’s an irregular polygon.  And if your app needs to track entry/exit for 200 cities, you’re out of luck.  You’d need 200 regions, but the OS caps you at 20 or 60.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;how-shinyspatialgeofencing-works&quot;&gt;How Shiny.Spatial.Geofencing Works&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Instead of registering individual fences with the OS, &lt;code dir=&quot;auto&quot;&gt;Shiny.Spatial.Geofencing&lt;/code&gt; hooks into GPS updates and queries your spatial databases in real-time:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;GPS reading comes in&lt;/li&gt;
&lt;li&gt;For each monitored table, query &lt;code dir=&quot;auto&quot;&gt;table.Query().Intersecting(point).FirstOrDefault()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Compare the current feature ID with the previously stored feature ID&lt;/li&gt;
&lt;li&gt;If they differ — fire exit for the old region and enter for the new one&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;No OS region limits.  No circular approximations.  Real polygon boundaries from real geographic data.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;setup&quot;&gt;Setup&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Install the package:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;dotnet&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;package&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Shiny.Spatial.Geofencing&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Create a delegate to handle region changes:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyGeofenceDelegate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;ILogger&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;MyGeofenceDelegate&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;logger&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;INotificationManager&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;notifications&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;) : &lt;/span&gt;&lt;span&gt;ISpatialGeofenceDelegate&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OnRegionChanged&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;SpatialRegionChange&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;change&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; change.Region.Properties.&lt;/span&gt;&lt;span&gt;GetValueOrDefault&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;name&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Unknown&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;action&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; change.Entered &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Entered&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Exited&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;logger.&lt;/span&gt;&lt;span&gt;LogInformation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;{Action} {Region} (table: {Table})&quot;&lt;/span&gt;&lt;span&gt;, action, name, change.TableName);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; notifications.&lt;/span&gt;&lt;span&gt;Send&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Geofence&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;$&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;action&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Register in &lt;code dir=&quot;auto&quot;&gt;MauiProgram.cs&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;builder.Services.&lt;/span&gt;&lt;span&gt;AddSpatialGps&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;MyGeofenceDelegate&lt;/span&gt;&lt;span&gt;&gt;(&lt;/span&gt;&lt;span&gt;cfg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; cfg&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Add&lt;/span&gt;&lt;span&gt;(dbPath, &lt;/span&gt;&lt;span&gt;&quot;states&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Add&lt;/span&gt;&lt;span&gt;(dbPath, &lt;/span&gt;&lt;span&gt;&quot;cities&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That’s it.  The library now monitors both the &lt;code dir=&quot;auto&quot;&gt;states&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;cities&lt;/code&gt; tables.  When the user crosses a state boundary, you get an exit + enter event.  When they enter a city, you get a separate enter event.  The two layers are tracked independently.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;the-api&quot;&gt;The API&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;interface&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ISpatialGeofenceManager&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IsStarted&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;AccessState&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;RequestAccess&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Start&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Stop&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;IReadOnlyList&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;SpatialCurrentRegion&lt;/span&gt;&lt;span&gt;&gt;&gt; &lt;/span&gt;&lt;span&gt;GetCurrent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cancelToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;GetCurrent()&lt;/code&gt; takes a one-shot GPS reading and tells you which region the device is currently in for each monitored table — useful for initial state on app launch.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;configuration&quot;&gt;Configuration&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;builder.Services.&lt;/span&gt;&lt;span&gt;AddSpatialGps&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;MyGeofenceDelegate&lt;/span&gt;&lt;span&gt;&gt;(&lt;/span&gt;&lt;span&gt;cfg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cfg.MinimumDistance &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Distance.&lt;/span&gt;&lt;span&gt;FromMeters&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;300&lt;/span&gt;&lt;span&gt;);  &lt;/span&gt;&lt;span&gt;// default&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cfg.MinimumTime &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; TimeSpan.&lt;/span&gt;&lt;span&gt;FromMinutes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;);       &lt;/span&gt;&lt;span&gt;// default&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cfg.&lt;/span&gt;&lt;span&gt;Add&lt;/span&gt;&lt;span&gt;(statesDbPath, &lt;/span&gt;&lt;span&gt;&quot;states&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cfg.&lt;/span&gt;&lt;span&gt;Add&lt;/span&gt;&lt;span&gt;(citiesDbPath, &lt;/span&gt;&lt;span&gt;&quot;cities&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;MinimumDistance&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;MinimumTime&lt;/code&gt; control how frequently GPS readings trigger spatial queries.  The defaults balance battery life with responsiveness.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;multi-layer-monitoring&quot;&gt;Multi-Layer Monitoring&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Because each table is tracked independently, you get precise layer-specific events:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Drive from Denver suburbs into Denver city limits → &lt;code dir=&quot;auto&quot;&gt;Entered: Denver&lt;/code&gt; (cities table)&lt;/li&gt;
&lt;li&gt;Drive from Denver to Colorado Springs → &lt;code dir=&quot;auto&quot;&gt;Exited: Denver&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Entered: Colorado Springs&lt;/code&gt; (cities table) — no state change event&lt;/li&gt;
&lt;li&gt;Drive from Colorado into Kansas → &lt;code dir=&quot;auto&quot;&gt;Exited: Colorado&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Entered: Kansas&lt;/code&gt; (states table) — plus whatever city events apply&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;pre-built-databases&quot;&gt;Pre-Built Databases&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The repo ships with ready-to-use databases:&lt;/p&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Database&lt;/th&gt;&lt;th&gt;Table&lt;/th&gt;&lt;th&gt;Geometry&lt;/th&gt;&lt;th&gt;Records&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;us-states.db&lt;/code&gt;&lt;/td&gt;&lt;td&gt;states&lt;/td&gt;&lt;td&gt;Polygon&lt;/td&gt;&lt;td&gt;51&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;us-cities.db&lt;/code&gt;&lt;/td&gt;&lt;td&gt;cities&lt;/td&gt;&lt;td&gt;Point&lt;/td&gt;&lt;td&gt;100&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;ca-provinces.db&lt;/code&gt;&lt;/td&gt;&lt;td&gt;provinces&lt;/td&gt;&lt;td&gt;Polygon&lt;/td&gt;&lt;td&gt;13&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;ca-cities.db&lt;/code&gt;&lt;/td&gt;&lt;td&gt;cities&lt;/td&gt;&lt;td&gt;Point&lt;/td&gt;&lt;td&gt;50&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Include them as MAUI assets and copy to app data on first launch.  Or build your own from any GeoJSON source using the included &lt;code dir=&quot;auto&quot;&gt;DatabaseSeeder&lt;/code&gt; tool.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;why-not-spatialite-or-nettopologysuite&quot;&gt;Why Not SpatiaLite or NetTopologySuite?&lt;/h2&gt;&lt;/div&gt;





















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Concern&lt;/th&gt;&lt;th&gt;SpatiaLite&lt;/th&gt;&lt;th&gt;NetTopologySuite&lt;/th&gt;&lt;th&gt;Shiny.Spatial&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Native binaries&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Yes — per platform&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;AOT compatible&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Platform-dependent&lt;/td&gt;&lt;td&gt;Reflection-heavy&lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Trimmable&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;N/A&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Bundle size&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Large (native libs)&lt;/td&gt;&lt;td&gt;Moderate&lt;/td&gt;&lt;td&gt;Small (single dep: Microsoft.Data.Sqlite)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Geometry algorithms&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;C/C++&lt;/td&gt;&lt;td&gt;C#&lt;/td&gt;&lt;td&gt;C#&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;R*Tree indexing&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Built-in&lt;/td&gt;&lt;td&gt;Separate&lt;/td&gt;&lt;td&gt;Built-in (SQLite)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;MAUI-ready&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Requires native binding per platform&lt;/td&gt;&lt;td&gt;Works but AOT issues&lt;/td&gt;&lt;td&gt;Works everywhere&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The tradeoff is coverage.  SpatiaLite and NTS support hundreds of spatial operations — buffer, union, difference, convex hull, spatial joins.  Shiny.Spatial covers the operations that matter for mobile apps: intersects, contains, within-distance, nearest-to.  If you need computational geometry operations, use NTS on the server and ship the results as a spatial database to the device.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;when-to-use-it&quot;&gt;When to Use It&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Good fit:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Geofencing beyond OS limits (more than 20/60 regions, polygon shapes)&lt;/li&gt;
&lt;li&gt;Location-aware apps that need “which city/state/zone am I in?”&lt;/li&gt;
&lt;li&gt;Spatial search (find nearby points of interest, stores, landmarks)&lt;/li&gt;
&lt;li&gt;Offline spatial queries without a server round-trip&lt;/li&gt;
&lt;li&gt;Shipping pre-built geographic databases with your app&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Not the best fit:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Simple circular geofencing with a few regions (use &lt;code dir=&quot;auto&quot;&gt;Shiny.Locations&lt;/code&gt; IGeofenceManager instead)&lt;/li&gt;
&lt;li&gt;Heavy computational geometry (buffer, union, difference) — use NTS on the server&lt;/li&gt;
&lt;li&gt;Real-time map rendering — this is a query engine, not a rendering engine&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;get-started&quot;&gt;Get Started&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;dotnet&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;package&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Shiny.Spatial&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;dotnet&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;package&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Shiny.Spatial.Geofencing&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;# for MAUI GPS geofencing&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Full documentation at &lt;a href=&quot;https://shinylib.net/spatial&quot;&gt;shinylib.net/spatial&lt;/a&gt; and the &lt;a href=&quot;https://github.com/shinyorg/geospatialdb&quot;&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>Spatial</category></item><item><title>Introducing Shiny.SqliteDocumentDb — Schema-Free JSON Documents in SQLite</title><link>https://www.shinylib.net/blog/shiny-sqlitedocumentdb/</link><guid isPermaLink="true">https://www.shinylib.net/blog/shiny-sqlitedocumentdb/</guid><pubDate>Sun, 22 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I’ve been building .NET apps long enough to know that SQLite is the workhorse of local storage.  It’s everywhere — mobile apps, desktop apps, embedded systems, even server-side caches.  But every time I reach for sqlite-net or raw ADO.NET, I end up in the same loop: design tables, write migrations, manage foreign keys, rehydrate object graphs from JOINs.&lt;/p&gt;
&lt;p&gt;For a lot of use cases — settings stores, offline caches, app state, anything with nested data — that ceremony is overkill.  What I actually want is to throw an object in and get it back out.  So I built &lt;a href=&quot;https://github.com/shinyorg/SqliteDocumentDb&quot;&gt;Shiny.SqliteDocumentDb&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;what-is-it&quot;&gt;What Is It?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;It’s a lightweight document store that sits on top of SQLite.  You give it a .NET object, it serializes it to JSON and stores it.  You query it with a fluent LINQ query builder, and it translates those expressions to &lt;code dir=&quot;auto&quot;&gt;json_extract&lt;/code&gt; SQL under the hood.  No schema, no migrations, no table design.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;store&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SqliteDocumentStore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;DocumentStoreOptions&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ConnectionString &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Data Source=mydata.db&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Store a document — Id is auto-generated for Guid/int/long types&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt; { Id &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;alice-1&quot;&lt;/span&gt;&lt;span&gt;, Name &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Alice&quot;&lt;/span&gt;&lt;span&gt;, Age &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;25&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; store.&lt;/span&gt;&lt;span&gt;Insert&lt;/span&gt;&lt;span&gt;(user);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Fluent query builder&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;results&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; store.&lt;/span&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Where&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;u&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; u.Name &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Alice&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;OrderBy&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;u&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; u.Age)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ToList&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Every document type must have a public &lt;code dir=&quot;auto&quot;&gt;Id&lt;/code&gt; property of type &lt;code dir=&quot;auto&quot;&gt;Guid&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;int&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;long&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;string&lt;/code&gt;. The Id is stored in both the SQLite column and the JSON blob, so query results always include it.&lt;/p&gt;
&lt;p&gt;All &lt;code dir=&quot;auto&quot;&gt;JsonTypeInfo&amp;#x3C;T&gt;&lt;/code&gt; parameters are optional — configure a &lt;code dir=&quot;auto&quot;&gt;JsonSerializerContext&lt;/code&gt; once and type info is auto-resolved on every call.  No per-call &lt;code dir=&quot;auto&quot;&gt;ctx.User&lt;/code&gt; needed.  Full AOT, fully trimmable, zero reflection.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;why-not-just-use-sqlite-net&quot;&gt;Why Not Just Use sqlite-net?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;sqlite-net is great for flat, single-table CRUD.  But the moment your data has structure — an order with line items, a user with addresses, a config with nested sections — things get painful.  You need multiple tables, foreign keys, multiple inserts per save, and multiple queries plus manual rehydration per read.&lt;/p&gt;
&lt;p&gt;The document store approach collapses all of that into a single operation.  One write, one read, one document.&lt;/p&gt;
&lt;p&gt;The benchmarks tell the story:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nested insert (Order + Address + OrderLines + Tags, 100 records):&lt;/strong&gt;&lt;/p&gt;

















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Method&lt;/th&gt;&lt;th&gt;Mean&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;DocumentStore&lt;/td&gt;&lt;td&gt;5.69 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;sqlite-net (3 tables)&lt;/td&gt;&lt;td&gt;176.48 ms&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Nested get by ID:&lt;/strong&gt;&lt;/p&gt;

















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Method&lt;/th&gt;&lt;th&gt;Mean&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;DocumentStore&lt;/td&gt;&lt;td&gt;5.04 us&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;sqlite-net (3 queries)&lt;/td&gt;&lt;td&gt;48.26 us&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;That’s &lt;strong&gt;31x faster inserts&lt;/strong&gt; and &lt;strong&gt;10x faster reads&lt;/strong&gt; for nested data.  The document store wins because it does one write and one read instead of multiple table operations.&lt;/p&gt;
&lt;p&gt;For flat data, sqlite-net can be faster on indexed column queries (it queries columns directly vs. &lt;code dir=&quot;auto&quot;&gt;json_extract&lt;/code&gt;).  Use the right tool for the shape of your data.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;what-about-ef-core-on-maui&quot;&gt;What About EF Core on MAUI?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The other question I get is “why not just use EF Core?”  On a server, EF Core is a reasonable choice.  On .NET MAUI — iOS, Android, Mac Catalyst — it becomes a liability.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AOT is not optional on Apple platforms.&lt;/strong&gt;  iOS, iPadOS, tvOS, and Mac Catalyst all prohibit JIT compilation at the OS level.  EF Core relies heavily on runtime reflection and dynamic code generation for change tracking, query compilation, and model building.  Its public API is decorated with &lt;code dir=&quot;auto&quot;&gt;[RequiresDynamicCode]&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;[RequiresUnreferencedCode]&lt;/code&gt; throughout.  That’s a non-starter for fully native AOT deployments on Apple platforms.&lt;/p&gt;
&lt;p&gt;Android doesn’t prohibit JIT, but AOT (&lt;code dir=&quot;auto&quot;&gt;PublishAot&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;AndroidEnableProfiledAot&lt;/code&gt;) delivers measurably faster startup and lower memory usage — both of which directly affect user experience on mobile.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Migrations solve a problem mobile apps don’t have.&lt;/strong&gt;  On a server, you run migrations against a shared database with a known lifecycle.  On a mobile device, the database is created on first launch or ships inside the app bundle.  EF Core’s migration pipeline (&lt;code dir=&quot;auto&quot;&gt;Add-Migration&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Update-Database&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;__EFMigrationsHistory&lt;/code&gt;) adds complexity with no real benefit.  A schema-free document store eliminates migrations entirely.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The dependency graph is heavy.&lt;/strong&gt;  EF Core pulls in &lt;code dir=&quot;auto&quot;&gt;Microsoft.EntityFrameworkCore&lt;/code&gt;, its SQLite provider, design-time packages, and their transitive dependencies.  That increases app bundle size — a real concern when app stores enforce download limits and users expect fast installs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mobile data is document-shaped.&lt;/strong&gt;  User preferences, cached API responses, offline data queues, local state — this data naturally has nested structure.  Forcing it into normalized tables with foreign keys and JOINs adds accidental complexity.&lt;/p&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Concern&lt;/th&gt;&lt;th&gt;EF Core&lt;/th&gt;&lt;th&gt;Shiny.SqliteDocumentDb&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;AOT / trimming&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Reflection-heavy; no AOT support&lt;/td&gt;&lt;td&gt;Optional &lt;code dir=&quot;auto&quot;&gt;JsonTypeInfo&amp;#x3C;T&gt;&lt;/code&gt; on every API; auto-resolves from context&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Migrations&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Required for every schema change&lt;/td&gt;&lt;td&gt;Not needed — schema-free JSON&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Nested objects&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Normalized tables, foreign keys, JOINs&lt;/td&gt;&lt;td&gt;Single document, single write, single read&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;App bundle size&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Large dependency tree&lt;/td&gt;&lt;td&gt;Single dependency on &lt;code dir=&quot;auto&quot;&gt;Microsoft.Data.Sqlite&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Startup time&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;DbContext model building, migration checks&lt;/td&gt;&lt;td&gt;Open connection and go&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The .NET trimmer makes this worse.  Libraries that depend on reflection break under trimming because the trimmer can’t statically determine which types and members are accessed at runtime.  This forces you to either disable trimming (larger binaries) or maintain complex trimmer XML configuration.  This library avoids both problems — source-generated JSON serialization means the trimmer can see every type, and there’s no &lt;code dir=&quot;auto&quot;&gt;Expression.Compile()&lt;/code&gt;, no &lt;code dir=&quot;auto&quot;&gt;Reflection.Emit&lt;/code&gt;, no dynamic delegates anywhere.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;the-fluent-query-builder&quot;&gt;The Fluent Query Builder&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This is the heart of the API.  &lt;code dir=&quot;auto&quot;&gt;store.Query&amp;#x3C;T&gt;()&lt;/code&gt; returns a fluent builder where you chain &lt;code dir=&quot;auto&quot;&gt;.Where()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;.OrderBy()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;.Paginate()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;.Select()&lt;/code&gt; — then terminate with &lt;code dir=&quot;auto&quot;&gt;.ToList()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;.ToAsyncEnumerable()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;.Count()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;.Any()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;.ExecuteDelete()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;.ExecuteUpdate()&lt;/code&gt;, or any aggregate method.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Filter + sort + paginate&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; store.&lt;/span&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Where&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;u&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; u.Age &lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;18&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;OrderBy&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;u&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; u.Name)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Paginate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ToList&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Count matching documents&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;count&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; store.&lt;/span&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Order&lt;/span&gt;&lt;span&gt;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Where&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; o.Status &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Pending&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Count&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Delete by predicate — returns count deleted&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;deleted&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; store.&lt;/span&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Where&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;u&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; u.Age &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;18&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ExecuteDelete&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Bulk update a property — returns count updated&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;updated&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; store.&lt;/span&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Where&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;u&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; u.Age &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;18&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ExecuteUpdate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;u&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; u.Age, &lt;/span&gt;&lt;span&gt;18&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Scalar aggregates&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;maxAge&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; store.&lt;/span&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt;&gt;().&lt;/span&gt;&lt;span&gt;Max&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;u&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; u.Age);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;avgAge&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; store.&lt;/span&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Where&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;u&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; u.IsActive)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Average&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;u&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; u.Age);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The expression visitor translates C# LINQ expressions into SQLite &lt;code dir=&quot;auto&quot;&gt;json_extract&lt;/code&gt; SQL, resolving property names from &lt;code dir=&quot;auto&quot;&gt;JsonTypeInfo&lt;/code&gt; metadata so &lt;code dir=&quot;auto&quot;&gt;[JsonPropertyName]&lt;/code&gt; and camelCase policies work correctly.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Nested properties&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;portland&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; store.&lt;/span&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Order&lt;/span&gt;&lt;span&gt;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Where&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; o.ShippingAddress.City &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Portland&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ToList&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// → json_extract(Data, &apos;$.shippingAddress.city&apos;) = @p0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Collection queries with Any()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;hasWidgets&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; store.&lt;/span&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Order&lt;/span&gt;&lt;span&gt;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Where&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; o.Lines.&lt;/span&gt;&lt;span&gt;Any&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;l&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; l.ProductName &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Widget&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ToList&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// → EXISTS (SELECT 1 FROM json_each(Data, &apos;$.lines&apos;) WHERE ...)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Collection Count()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bigOrders&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; store.&lt;/span&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Order&lt;/span&gt;&lt;span&gt;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Where&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; o.Lines.&lt;/span&gt;&lt;span&gt;Count&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ToList&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// → json_array_length(Data, &apos;$.lines&apos;) &gt; 5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// String methods&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;matches&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; store.&lt;/span&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Where&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;u&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; u.Name.&lt;/span&gt;&lt;span&gt;Contains&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;li&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ToList&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// → LIKE &apos;%&apos; || @p0 || &apos;%&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Equality, comparisons, logical operators (&lt;code dir=&quot;auto&quot;&gt;&amp;#x26;&amp;#x26;&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;||&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;!&lt;/code&gt;), null checks, &lt;code dir=&quot;auto&quot;&gt;DateTime&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;DateTimeOffset&lt;/code&gt;, captured variables — they all work.  The full expression reference is in the &lt;a href=&quot;https://github.com/shinyorg/SqliteDocumentDb&quot;&gt;README&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;sql-level-projections&quot;&gt;SQL-Level Projections&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Need just a few fields from a large document?  Chain &lt;code dir=&quot;auto&quot;&gt;.Select()&lt;/code&gt; to extract only the selected properties at the database level using &lt;code dir=&quot;auto&quot;&gt;json_object&lt;/code&gt; — no full deserialization.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;summaries&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; store.&lt;/span&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Order&lt;/span&gt;&lt;span&gt;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Where&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; o.Status &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Shipped&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;OrderBy&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; o.CustomerName)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Paginate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;50&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Select&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OrderSummary&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Customer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; o.CustomerName,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;City &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; o.ShippingAddress.City,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;LineCount &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; o.Lines.&lt;/span&gt;&lt;span&gt;Count&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ToList&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That &lt;code dir=&quot;auto&quot;&gt;Lines.Count()&lt;/code&gt; becomes &lt;code dir=&quot;auto&quot;&gt;json_array_length(Data, &apos;$.lines&apos;)&lt;/code&gt; in SQL.  You can also use &lt;code dir=&quot;auto&quot;&gt;Any()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Any(predicate)&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Count(predicate)&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Sum()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Max()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Min()&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;Average()&lt;/code&gt; inside selectors.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;aggregate-projections&quot;&gt;Aggregate Projections&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;For GROUP BY queries, use the &lt;code dir=&quot;auto&quot;&gt;Sql&lt;/code&gt; marker class inside &lt;code dir=&quot;auto&quot;&gt;.Select()&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stats&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; store.&lt;/span&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Order&lt;/span&gt;&lt;span&gt;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Where&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; o.Status &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Cancelled&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Select&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OrderStats&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Status &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; o.Status,            &lt;/span&gt;&lt;span&gt;// GROUP BY column&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;OrderCount &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Sql.&lt;/span&gt;&lt;span&gt;Count&lt;/span&gt;&lt;span&gt;(),     &lt;/span&gt;&lt;span&gt;// COUNT(*)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;TotalRevenue &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Sql.&lt;/span&gt;&lt;span&gt;Sum&lt;/span&gt;&lt;span&gt;(o.TotalAmount),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ToList&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Non-aggregate columns are automatically grouped.  &lt;code dir=&quot;auto&quot;&gt;Sql.Count()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Sql.Max()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Sql.Min()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Sql.Sum()&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Sql.Avg()&lt;/code&gt; — all available.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;surgical-field-updates&quot;&gt;Surgical Field Updates&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Don’t always need to replace an entire document.  &lt;code dir=&quot;auto&quot;&gt;SetProperty&lt;/code&gt; updates a single field in-place via &lt;code dir=&quot;auto&quot;&gt;json_set()&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;RemoveProperty&lt;/code&gt; strips a field via &lt;code dir=&quot;auto&quot;&gt;json_remove()&lt;/code&gt; — both without deserializing the document.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Update a single field&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; store.&lt;/span&gt;&lt;span&gt;SetProperty&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt;&gt;(&lt;/span&gt;&lt;span&gt;&quot;user-1&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;u&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; u.Age, &lt;/span&gt;&lt;span&gt;31&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Nested paths work too&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; store.&lt;/span&gt;&lt;span&gt;SetProperty&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Order&lt;/span&gt;&lt;span&gt;&gt;(&lt;/span&gt;&lt;span&gt;&quot;order-1&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; o.ShippingAddress.City, &lt;/span&gt;&lt;span&gt;&quot;Portland&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Strip a field entirely&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; store.&lt;/span&gt;&lt;span&gt;RemoveProperty&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt;&gt;(&lt;/span&gt;&lt;span&gt;&quot;user-1&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;u&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; u.Email);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;For multi-field patches, &lt;code dir=&quot;auto&quot;&gt;Upsert&lt;/code&gt; does RFC 7396 JSON Merge Patch — deep-merging only the provided fields while preserving everything else:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; store.&lt;/span&gt;&lt;span&gt;Upsert&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt; { Id &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;user-1&quot;&lt;/span&gt;&lt;span&gt;, Name &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Alice&quot;&lt;/span&gt;&lt;span&gt;, Age &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;31&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Email and other fields are preserved&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;streaming-with-iasyncenumerable&quot;&gt;Streaming with IAsyncEnumerable&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Use &lt;code dir=&quot;auto&quot;&gt;.ToAsyncEnumerable()&lt;/code&gt; instead of &lt;code dir=&quot;auto&quot;&gt;.ToList()&lt;/code&gt; to stream results one-at-a-time without buffering the entire set into memory.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;foreach&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;order&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; store.&lt;/span&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Order&lt;/span&gt;&lt;span&gt;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Where&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; o.Status &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Pending&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;OrderBy&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; o.CustomerName)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ToAsyncEnumerable&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ProcessOrder&lt;/span&gt;&lt;span&gt;(order);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The benchmarks show streaming eliminates Gen1 GC collections entirely at 1,000+ documents while maintaining within ~2% of buffered throughput.  If you’re processing results incrementally, streaming is free performance.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;expression-based-json-indexes&quot;&gt;Expression-Based JSON Indexes&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The default query performance is solid, but for hot paths you can create indexes on &lt;code dir=&quot;auto&quot;&gt;json_extract&lt;/code&gt; expressions:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; store.&lt;/span&gt;&lt;span&gt;CreateIndexAsync&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt;&gt;(&lt;/span&gt;&lt;span&gt;u&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; u.Name, ctx.User);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// CREATE INDEX IF NOT EXISTS idx_json_User_name&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// ON documents (json_extract(Data, &apos;$.name&apos;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// WHERE TypeName = &apos;User&apos;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Impact on a 1,000-record flat query:&lt;/strong&gt;&lt;/p&gt;

















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Method&lt;/th&gt;&lt;th&gt;Mean&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Without index&lt;/td&gt;&lt;td&gt;270 us&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;With index&lt;/td&gt;&lt;td&gt;8.52 us&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;~32x faster.&lt;/strong&gt;  The index lets SQLite use a B-tree lookup instead of scanning every row with &lt;code dir=&quot;auto&quot;&gt;json_extract&lt;/code&gt;.  Works with nested properties too:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; store.&lt;/span&gt;&lt;span&gt;CreateIndexAsync&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Order&lt;/span&gt;&lt;span&gt;&gt;(&lt;/span&gt;&lt;span&gt;o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; o.ShippingAddress.City, ctx.Order);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;aot-and-trimming--first-class&quot;&gt;AOT and Trimming — First Class&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;All &lt;code dir=&quot;auto&quot;&gt;JsonTypeInfo&amp;#x3C;T&gt;&lt;/code&gt; parameters are optional with &lt;code dir=&quot;auto&quot;&gt;= null&lt;/code&gt; defaults.  Configure a &lt;code dir=&quot;auto&quot;&gt;JsonSerializerContext&lt;/code&gt; once at setup and every method auto-resolves type info — no per-call parameters needed.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;JsonSerializable&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;typeof&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt;))]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;JsonSerializable&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;typeof&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Order&lt;/span&gt;&lt;span&gt;))]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;partial&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AppJsonContext&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;JsonSerializerContext&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AppJsonContext&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;JsonSerializerOptions&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;PropertyNamingPolicy &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; JsonNamingPolicy.CamelCase&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;store&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SqliteDocumentStore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;DocumentStoreOptions&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ConnectionString &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Data Source=mydata.db&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;JsonSerializerOptions &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ctx.Options,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;UseReflectionFallback &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;// recommended for AOT&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Now every call is AOT-safe without passing JsonTypeInfo explicitly&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt; { Id &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;alice-1&quot;&lt;/span&gt;&lt;span&gt;, Name &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Alice&quot;&lt;/span&gt;&lt;span&gt;, Age &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;25&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; store.&lt;/span&gt;&lt;span&gt;Insert&lt;/span&gt;&lt;span&gt;(user);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;users&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; store.&lt;/span&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;User&lt;/span&gt;&lt;span&gt;&gt;().&lt;/span&gt;&lt;span&gt;Where&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;u&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; u.Age &lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;18&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;ToList&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Pass &lt;code dir=&quot;auto&quot;&gt;ctx.Options&lt;/code&gt; to &lt;code dir=&quot;auto&quot;&gt;DocumentStoreOptions.JsonSerializerOptions&lt;/code&gt; so the expression visitor and serializer share the same naming configuration.  That’s the one thing people forget — and then their LINQ queries silently return zero results because property names don’t match.&lt;/p&gt;
&lt;p&gt;Set &lt;code dir=&quot;auto&quot;&gt;UseReflectionFallback = false&lt;/code&gt; for AOT deployments.  Instead of opaque runtime failures, you get a clear &lt;code dir=&quot;auto&quot;&gt;InvalidOperationException&lt;/code&gt; telling you exactly which type is missing from your &lt;code dir=&quot;auto&quot;&gt;JsonSerializerContext&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;transactions&quot;&gt;Transactions&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Atomic multi-document operations with automatic commit/rollback:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; store.&lt;/span&gt;&lt;span&gt;RunInTransaction&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; tx.&lt;/span&gt;&lt;span&gt;Insert&lt;/span&gt;&lt;span&gt;(order); &lt;/span&gt;&lt;span&gt;// order.Id must be set&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; tx.&lt;/span&gt;&lt;span&gt;Insert&lt;/span&gt;&lt;span&gt;(user);  &lt;/span&gt;&lt;span&gt;// user.Id must be set&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// Exception → automatic rollback&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;tx&lt;/code&gt; parameter is a full &lt;code dir=&quot;auto&quot;&gt;IDocumentStore&lt;/code&gt;, so you can use any operation inside the transaction — queries, counts, removes, everything.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;di-registration&quot;&gt;DI Registration&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;One line:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;services.&lt;/span&gt;&lt;span&gt;AddSqliteDocumentStore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Data Source=mydata.db&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Registers &lt;code dir=&quot;auto&quot;&gt;IDocumentStore&lt;/code&gt; as a singleton.  For full configuration:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;services.&lt;/span&gt;&lt;span&gt;AddSqliteDocumentStore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;opts&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;opts.ConnectionString &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Data Source=mydata.db&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;opts.TypeNameResolution &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; TypeNameResolution.FullName;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;opts.JsonSerializerOptions &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ctx.Options;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;opts.UseReflectionFallback &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;when-to-use-it&quot;&gt;When to Use It&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Good fit:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Offline caches and app state&lt;/li&gt;
&lt;li&gt;Settings and configuration stores&lt;/li&gt;
&lt;li&gt;Data with nested objects and child collections&lt;/li&gt;
&lt;li&gt;Rapid prototyping without schema design&lt;/li&gt;
&lt;li&gt;Any scenario where you want to store and query object graphs without table design&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Not the best fit:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bulk operations on millions of rows where raw SQL shines&lt;/li&gt;
&lt;li&gt;Simple flat-table CRUD where sqlite-net is already working well&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;get-started&quot;&gt;Get Started&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;dotnet&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;package&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Shiny.SqliteDocumentDb&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Full documentation at &lt;a href=&quot;https://shinylib.net/sqlite-docdb/&quot;&gt;shinylib.net/sqlite-docdb&lt;/a&gt; and the &lt;a href=&quot;https://github.com/shinyorg/SqliteDocumentDb&quot;&gt;GitHub repository&lt;/a&gt; has the complete README with benchmarks, expression reference tables, and examples.&lt;/p&gt;</content:encoded><category>Extensions</category></item><item><title>Introducing Shiny.Maui.TableView — Settings-Style Pages for .NET MAUI, Without the Platform Pain</title><link>https://www.shinylib.net/blog/shiny-maui-tableview/</link><guid isPermaLink="true">https://www.shinylib.net/blog/shiny-maui-tableview/</guid><pubDate>Sat, 21 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;a href=&quot;https://www.nuget.org/packages/Shiny.Maui.TableView&quot; target=&quot;_NEWWINDOW&quot;&gt;&lt;img src=&quot;https://img.shields.io/nuget/v/Shiny.Maui.TableView?style=for-the-badge&amp;#x26;logo=nuget&amp;#x26;label=Shiny.Maui.TableView&quot;&gt;&lt;/a&gt;
&lt;p&gt;If you’ve built a .NET MAUI app, you’ve probably needed a settings page.  A scrollable list of sections with toggles, text entries, pickers, radio buttons — the kind of UI you see in every iOS Settings screen or Android preferences panel.  And if you’ve tried to build one, you know it’s surprisingly painful.&lt;/p&gt;
&lt;p&gt;MAUI’s built-in &lt;code dir=&quot;auto&quot;&gt;TableView&lt;/code&gt; is limited.  The community options either depend on native renderers that break across platform updates or haven’t kept up with modern .NET.  I wanted something that just worked — pure MAUI, no platform-specific code, full MVVM support, and enough cell types to cover real app scenarios without writing custom templates for everything.&lt;/p&gt;
&lt;p&gt;So I built &lt;a href=&quot;https://github.com/shinyorg/shinytableview&quot;&gt;Shiny.Maui.TableView&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;what-is-it&quot;&gt;What Is It?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;It’s a settings-style TableView control built entirely on .NET MAUI layout primitives.  No custom handlers, no native renderers, no platform-specific code.  It ships with 15 cell types that cover the most common settings UI patterns, a three-level cascading style system, drag-and-drop reordering, dynamic section generation, and full MVVM data binding.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;xmlns:tv=&quot;http://shiny.net/maui/tableview&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:TableView&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CellAccentColor&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;#007AFF&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:TableRoot&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:TableSection&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Network&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:SwitchCell&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Wi-Fi&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                           &lt;/span&gt;&lt;span&gt;On&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;{Binding WifiEnabled, Mode=TwoWay}&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:SwitchCell&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Bluetooth&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                           &lt;/span&gt;&lt;span&gt;On&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;{Binding BluetoothEnabled, Mode=TwoWay}&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;tv:TableSection&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;tv:TableRoot&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;tv:TableView&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That’s a fully functional settings section with two-way bound toggles.  No custom renderers, no platform init code beyond one line in &lt;code dir=&quot;auto&quot;&gt;MauiProgram.cs&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;why-pure-maui&quot;&gt;Why Pure MAUI?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The existing community solution — AiForms.Maui.SettingsView — is excellent, but it uses native platform renderers (&lt;code dir=&quot;auto&quot;&gt;UITableView&lt;/code&gt; on iOS, &lt;code dir=&quot;auto&quot;&gt;RecyclerView&lt;/code&gt; on Android).  That means platform-specific bugs, maintenance burden across OS updates, and behavior differences between platforms.&lt;/p&gt;
&lt;p&gt;Shiny.Maui.TableView takes a different approach: everything is built from &lt;code dir=&quot;auto&quot;&gt;ContentView&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;ScrollView&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;VerticalStackLayout&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;Grid&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;Border&lt;/code&gt;.  One codebase, identical behavior everywhere.  It runs on iOS, Android, and Mac Catalyst with zero platform-specific code.&lt;/p&gt;
&lt;p&gt;The tradeoff is no virtualization — this is a full-render model.  For settings pages with dozens of items, that’s perfectly fine.  This isn’t meant for scrolling through thousands of rows; it’s meant for building the kind of structured, section-based UI that settings and forms demand.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;15-cell-types&quot;&gt;15 Cell Types&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The library ships with cell types that cover the breadth of settings UI patterns:&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;display--navigation&quot;&gt;Display &amp;#x26; Navigation&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LabelCell&lt;/strong&gt; — Read-only title/value display&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CommandCell&lt;/strong&gt; — Tappable cell with optional disclosure arrow and command binding&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ButtonCell&lt;/strong&gt; — Full-width button-style action cell&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;toggles--selection&quot;&gt;Toggles &amp;#x26; Selection&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SwitchCell&lt;/strong&gt; — Toggle with customizable accent color&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CheckboxCell&lt;/strong&gt; — Native checkbox with accent color&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SimpleCheckCell&lt;/strong&gt; — Lightweight checkmark for selection lists&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RadioCell&lt;/strong&gt; — Radio selection within a section or across the entire TableView&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;text-input&quot;&gt;Text Input&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;EntryCell&lt;/strong&gt; — Inline text entry with placeholder, keyboard type, password masking, and max length&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;pickers&quot;&gt;Pickers&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DatePickerCell&lt;/strong&gt; — Native date picker dialog&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TimePickerCell&lt;/strong&gt; — Native time picker dialog&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TextPickerCell&lt;/strong&gt; — Dropdown-style picker from a list of items&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NumberPickerCell&lt;/strong&gt; — Numeric input via dialog with min/max/unit&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PickerCell&lt;/strong&gt; — Full-page selection with single or multi-select, auto-generated display text, and configurable pick-to-close behavior&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;custom&quot;&gt;Custom&lt;/h3&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CustomCell&lt;/strong&gt; — Host any MAUI View with optional full-width mode, command binding, and disclosure arrow&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Every cell shares a common base with title, description, hint text, icon, background color, selection highlight, and border customization.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;cascading-styles&quot;&gt;Cascading Styles&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;One of the features I’m most happy with is the three-level cascading style system.  Set defaults at the TableView level, override at the section level, and fine-tune on individual cells.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Level 1 — TableView (global defaults):&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:TableView&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;CellTitleColor&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;#333333&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;CellTitleFontSize&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;17&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;CellDescriptionColor&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;#888888&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;CellValueTextColor&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;#007AFF&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;CellBackgroundColor&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;White&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;CellAccentColor&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;#007AFF&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;HeaderTextColor&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;#666666&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;SeparatorColor&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;#C6C6C8&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This sets the look for every cell and header in the entire TableView.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Level 2 — TableSection (section overrides):&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:TableSection&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Important&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                 &lt;/span&gt;&lt;span&gt;HeaderBackgroundColor&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;#E3F2FD&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                 &lt;/span&gt;&lt;span&gt;HeaderTextColor&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;#1565C0&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Level 3 — Individual cell (highest priority):&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:LabelCell&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Warning&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TitleColor&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Red&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The resolution logic is simple: cell property wins over section property, section wins over TableView, TableView wins over framework defaults.  You set your theme once at the top and only override where you need to.&lt;/p&gt;
&lt;p&gt;The style system covers everything: title, description, hint text, and value text fonts and colors; icon size and radius; accent color for interactive controls; cell backgrounds and selection highlights; header and footer appearance; separator color, height, and inset; section gap height and color; cell padding and borders.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;dynamic-sections&quot;&gt;Dynamic Sections&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Static XAML is great for fixed settings pages, but sometimes you need sections or cells generated from data.  Both &lt;code dir=&quot;auto&quot;&gt;TableView&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;TableSection&lt;/code&gt; support &lt;code dir=&quot;auto&quot;&gt;ItemsSource&lt;/code&gt; with &lt;code dir=&quot;auto&quot;&gt;DataTemplate&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dynamic cells within a section:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:TableSection&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Devices&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                 &lt;/span&gt;&lt;span&gt;ItemsSource&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;{Binding Devices}&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:TableSection.ItemTemplate&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;DataTemplate&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:LabelCell&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;{Binding Name}&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                          &lt;/span&gt;&lt;span&gt;ValueText&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;{Binding Status}&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;DataTemplate&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;tv:TableSection.ItemTemplate&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;tv:TableSection&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;You can mix static and templated cells in the same section using &lt;code dir=&quot;auto&quot;&gt;TemplateStartIndex&lt;/code&gt; to control where generated cells appear.  The binding supports &lt;code dir=&quot;auto&quot;&gt;INotifyCollectionChanged&lt;/code&gt;, so adding or removing items from your collection updates the UI automatically.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;drag--sort&quot;&gt;Drag &amp;#x26; Sort&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Enable reordering with a single property:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:TableView&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ItemDroppedCommand&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;{Binding ReorderCommand}&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:TableRoot&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:TableSection&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Priority&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;UseDragSort&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;True&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:LabelCell&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;High&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:LabelCell&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Medium&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:LabelCell&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Low&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;tv:TableSection&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;tv:TableRoot&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;tv:TableView&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Each cell gets up/down arrow controls.  The &lt;code dir=&quot;auto&quot;&gt;ItemDroppedCommand&lt;/code&gt; receives &lt;code dir=&quot;auto&quot;&gt;ItemDroppedEventArgs&lt;/code&gt; with the section, cell, and from/to indexes so you can update your backing data.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;radio-groups&quot;&gt;Radio Groups&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Radio selection is handled through an attached property that can scope to a section or the entire TableView:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:TableSection&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Theme&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                 &lt;/span&gt;&lt;span&gt;tv:RadioCell.SelectedValue&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;{Binding SelectedTheme, Mode=TwoWay}&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:RadioCell&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Light&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Light&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:RadioCell&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Dark&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Dark&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:RadioCell&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;System&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;System&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;tv:TableSection&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Bind &lt;code dir=&quot;auto&quot;&gt;RadioCell.SelectedValue&lt;/code&gt; to your ViewModel and you get two-way radio group selection with no code-behind.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;full-page-picker&quot;&gt;Full-Page Picker&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;PickerCell&lt;/code&gt; navigates to a dedicated selection page — useful for long lists or multi-select scenarios:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:PickerCell&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Interests&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;               &lt;/span&gt;&lt;span&gt;ItemsSource&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;{Binding AllInterests}&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;               &lt;/span&gt;&lt;span&gt;SelectedItems&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;{Binding SelectedInterests, Mode=TwoWay}&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;               &lt;/span&gt;&lt;span&gt;SelectionMode&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Multiple&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;               &lt;/span&gt;&lt;span&gt;MaxSelectedNumber&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;5&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;               &lt;/span&gt;&lt;span&gt;UsePickToClose&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;True&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;               &lt;/span&gt;&lt;span&gt;UseAutoValueText&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;True&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;               &lt;/span&gt;&lt;span&gt;PageTitle&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Select Interests&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;It auto-generates the value text from your selections, supports single and multi-select modes, and can auto-close when the max selection count is reached.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;setup&quot;&gt;Setup&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Install the NuGet package:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;dotnet&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;package&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Shiny.Maui.TableView&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Register in &lt;code dir=&quot;auto&quot;&gt;MauiProgram.cs&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; MauiApp.&lt;/span&gt;&lt;span&gt;CreateBuilder&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;builder&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;UseMauiApp&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;UseShinyTableView&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Add the XAML namespace and start building:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;ContentPage&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;xmlns:tv&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;http://shiny.net/maui/tableview&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:TableView&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:TableRoot&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:TableSection&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;General&quot;&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:SwitchCell&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Notifications&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                               &lt;/span&gt;&lt;span&gt;On&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;{Binding NotificationsEnabled, Mode=TwoWay}&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:EntryCell&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Name&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                              &lt;/span&gt;&lt;span&gt;ValueText&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;{Binding UserName, Mode=TwoWay}&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                              &lt;/span&gt;&lt;span&gt;Placeholder&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Enter your name&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;tv:CommandCell&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;About&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                &lt;/span&gt;&lt;span&gt;Command&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;{Binding AboutCommand}&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                                &lt;/span&gt;&lt;span&gt;ShowArrow&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;True&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;tv:TableSection&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;tv:TableRoot&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;tv:TableView&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;ContentPage&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;when-to-use-it&quot;&gt;When to Use It&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Good fit:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Settings and preferences pages&lt;/li&gt;
&lt;li&gt;Form-style data entry screens&lt;/li&gt;
&lt;li&gt;Profile editing UIs&lt;/li&gt;
&lt;li&gt;Any structured, section-based list with mixed control types&lt;/li&gt;
&lt;li&gt;Apps targeting iOS, Android, and Mac Catalyst from a single codebase&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Not the best fit:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Long scrolling lists with hundreds or thousands of items (no virtualization)&lt;/li&gt;
&lt;li&gt;Data grids or spreadsheet-style layouts&lt;/li&gt;
&lt;li&gt;Chat interfaces or feed-style UIs&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;get-started&quot;&gt;Get Started&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;dotnet&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;package&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Shiny.Maui.TableView&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Full documentation at &lt;a href=&quot;https://shinylib.net/tableview/&quot;&gt;shinylib.net/tableview&lt;/a&gt; and the &lt;a href=&quot;https://github.com/shinyorg/shinytableview&quot;&gt;GitHub repository&lt;/a&gt; has the complete source, sample app, and issue tracker.&lt;/p&gt;</content:encoded><category>TableView</category></item><item><title>Shiny Mediator &amp; AOT - Zero Reflection, Full Speed</title><link>https://www.shinylib.net/blog/shinymediator-aot/</link><guid isPermaLink="true">https://www.shinylib.net/blog/shinymediator-aot/</guid><pubDate>Tue, 10 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Native AOT and trimming are no longer “nice to have” — they’re table stakes for modern .NET apps.  iOS has never allowed JIT.  Blazor WebAssembly ships every byte to the browser.  ASP.NET minimal APIs are racing toward sub-10ms cold starts.  If your library leans on reflection, you’re the bottleneck.&lt;/p&gt;
&lt;p&gt;Shiny Mediator took this personally.&lt;/p&gt;
&lt;p&gt;Starting in v5 and fully realized in v6, Shiny Mediator has waged a &lt;strong&gt;war on reflection&lt;/strong&gt;.  Every piece of runtime introspection has been replaced with compile-time source generation.  The result?  A mediator pipeline that is 100% AOT-safe, fully trimmable, and frankly… faster than it has any right to be.&lt;/p&gt;
&lt;p&gt;Let’s walk through every source generator and design decision that makes this possible.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;1-handler--middleware-registration--mediatorsingleton--mediatorscoped&quot;&gt;1. Handler &amp;#x26; Middleware Registration — &lt;code dir=&quot;auto&quot;&gt;[MediatorSingleton]&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;[MediatorScoped]&lt;/code&gt;&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The old way of registering handlers meant scanning assemblies, resolving open generics, and hoping the DI container could figure it all out at runtime.  That’s… not AOT-friendly.&lt;/p&gt;
&lt;p&gt;Shiny Mediator replaces all of that with two attributes:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;MediatorSingleton&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GetUserHandler&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;IRequestHandler&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;GetUserRequest&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;UserResponse&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;UserResponse&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;Handle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;GetUserRequest&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;IMediatorContext&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ct&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// your handler logic&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;At compile time, the source generator discovers every class decorated with &lt;code dir=&quot;auto&quot;&gt;[MediatorSingleton]&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;[MediatorScoped]&lt;/code&gt; and emits all the DI registration code for you.  No assembly scanning.  No &lt;code dir=&quot;auto&quot;&gt;typeof()&lt;/code&gt; gymnastics.  No reflection.&lt;/p&gt;
&lt;p&gt;The generated code feeds into a &lt;strong&gt;module initializer registry&lt;/strong&gt; — a static registry that collects every handler and middleware registration across your entire solution.  To wire it all up:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;services.&lt;/span&gt;&lt;span&gt;AddShinyMediator&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; x.&lt;/span&gt;&lt;span&gt;AddMediatorRegistry&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;One line.  Every handler.  Every middleware.  All generated at compile time.&lt;/p&gt;
&lt;p&gt;These attributes also handle middleware registration — so if you have a custom middleware class, slap &lt;code dir=&quot;auto&quot;&gt;[MediatorSingleton]&lt;/code&gt; on it and it joins the party automatically.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;2-request--stream-executors&quot;&gt;2. Request &amp;#x26; Stream Executors&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here’s a fun .NET limitation: you can’t easily resolve a generic type at runtime without knowing the type parameters at compile time.  Previous versions of Shiny Mediator used reflection to bridge that gap.  It worked, but it was slow and it was the single biggest AOT blocker.&lt;/p&gt;
&lt;p&gt;Starting in v5, source generators create &lt;strong&gt;typed executors&lt;/strong&gt; for every request and stream request in your project.  These executors know the exact types at compile time, so the mediator can dispatch directly without any &lt;code dir=&quot;auto&quot;&gt;MakeGenericType&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;Activator.CreateInstance&lt;/code&gt; shenanigans.&lt;/p&gt;
&lt;p&gt;You don’t need to do anything extra — if you’re using &lt;code dir=&quot;auto&quot;&gt;[MediatorSingleton]&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;[MediatorScoped]&lt;/code&gt;, the executor generation comes along for the ride.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;3-json-converter-source-generation&quot;&gt;3. JSON Converter Source Generation&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;AOT’s nemesis is &lt;code dir=&quot;auto&quot;&gt;System.Text.Json&lt;/code&gt; with reflection-based serialization.  The standard fix is &lt;code dir=&quot;auto&quot;&gt;JsonSerializerContext&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;[JsonSerializable]&lt;/code&gt; — but here’s the problem: &lt;strong&gt;you can’t chain source generators&lt;/strong&gt;.  Shiny Mediator’s source generator runs first, and &lt;code dir=&quot;auto&quot;&gt;System.Text.Json&lt;/code&gt;’s source generator can’t see the types that were just generated.&lt;/p&gt;
&lt;p&gt;So Shiny Mediator built its own JSON serialization source generator.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;for-http-contracts-openapi&quot;&gt;For HTTP Contracts (OpenAPI)&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;When you generate HTTP clients from OpenAPI specs, just flip one switch:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;ItemGroup&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;MediatorHttp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Include&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;MyApi&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                  &lt;/span&gt;&lt;span&gt;Uri&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;https://api.example.com/openapi.json&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                  &lt;/span&gt;&lt;span&gt;Namespace&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;MyApp.Api&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                  &lt;/span&gt;&lt;span&gt;GenerateJsonConverters&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;true&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                  &lt;/span&gt;&lt;span&gt;Visible&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;false&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;ItemGroup&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Setting &lt;code dir=&quot;auto&quot;&gt;GenerateJsonConverters=&quot;true&quot;&lt;/code&gt; tells the source generator to emit high-performance, AOT-safe JSON converters for every contract and response type it produces.  No reflection.  No &lt;code dir=&quot;auto&quot;&gt;JsonSerializerContext&lt;/code&gt; registration.  The &lt;code dir=&quot;auto&quot;&gt;[JsonConverter]&lt;/code&gt; attribute is placed directly on each type.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;for-your-own-types&quot;&gt;For Your Own Types&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Got your own classes that need serialization — maybe for offline storage, caching, or custom contracts?  Use the &lt;code dir=&quot;auto&quot;&gt;[SourceGenerateJsonConverter]&lt;/code&gt; attribute:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;SourceGenerateJsonConverter&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;partial&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WeatherForecast&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;? &lt;/span&gt;&lt;span&gt;City&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Temperature&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;DateTime&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The class must be &lt;code dir=&quot;auto&quot;&gt;partial&lt;/code&gt; (the generator needs to attach code to it).  That’s it — you get a compile-time JSON converter without ever touching &lt;code dir=&quot;auto&quot;&gt;System.Text.Json&lt;/code&gt; source generation configuration.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;4-contract-key-source-generation&quot;&gt;4. Contract Key Source Generation&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Middleware like caching, offline storage, and stream replay all need a &lt;strong&gt;key&lt;/strong&gt; to identify unique requests.  The “old school” way was implementing &lt;code dir=&quot;auto&quot;&gt;IContractKey&lt;/code&gt; on your contract:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Before — manual, tedious, error-prone&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SearchRequest&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;IRequest&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;SearchResult&lt;/span&gt;&lt;span&gt;&gt;, &lt;/span&gt;&lt;span&gt;IContractKey&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;? &lt;/span&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;? &lt;/span&gt;&lt;span&gt;Page&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;DateTime&lt;/span&gt;&lt;span&gt;? &lt;/span&gt;&lt;span&gt;Since&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GetKey&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;SearchRequest&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (Query &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;) key &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;$&quot;_&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (Page &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;) key &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;$&quot;_&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;Page&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (Since &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;) key &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;$&quot;_&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;Since&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;yyyyMMdd&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; key;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Writing null checks and format strings for every property on every contract gets old fast.  And the default &lt;code dir=&quot;auto&quot;&gt;IContractKeyProvider&lt;/code&gt; used reflection to build keys when you didn’t implement the interface.&lt;/p&gt;
&lt;p&gt;The source-generated version:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// After — one attribute, zero reflection&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;ContractKey&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;SearchRequest_{Query}_{Page}_{Since:yyyyMMdd}&quot;&lt;/span&gt;&lt;span&gt;)]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;partial&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SearchRequest&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;IRequest&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;SearchResult&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;? &lt;/span&gt;&lt;span&gt;Query&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;? &lt;/span&gt;&lt;span&gt;Page&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;DateTime&lt;/span&gt;&lt;span&gt;? &lt;/span&gt;&lt;span&gt;Since&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The source generator handles null checks, &lt;code dir=&quot;auto&quot;&gt;ToString()&lt;/code&gt; calls, format strings — everything.  If a property is null, that portion of the key is replaced with an empty string.  The class must be &lt;code dir=&quot;auto&quot;&gt;partial&lt;/code&gt;, and you can use the same format specifiers you’d use in string interpolation.&lt;/p&gt;
&lt;p&gt;Leave the format string blank and it uses &lt;strong&gt;all public instance properties&lt;/strong&gt; automatically.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;5-middleware-attribute-source-generation&quot;&gt;5. Middleware Attribute Source Generation&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This one is subtle but critical.  Many of Shiny Mediator’s middleware components are driven by attributes on handler methods:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;partial&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GetProductHandler&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;IRequestHandler&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;GetProductRequest&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;Product&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Cache&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;MaxAgeSeconds&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;300&lt;/span&gt;&lt;span&gt;)]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;OfflineAvailable&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Product&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;Handle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;GetProductRequest&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;IMediatorContext&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ct&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// fetch product from API&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Reading attributes from methods at runtime requires deep reflection — &lt;code dir=&quot;auto&quot;&gt;MethodInfo.GetCustomAttributes()&lt;/code&gt; and friends.  In an AOT world, that can fail silently or crash spectacularly.&lt;/p&gt;
&lt;p&gt;Shiny Mediator’s source generator scans handler methods at compile time, extracts every attribute that inherits from &lt;code dir=&quot;auto&quot;&gt;MediatorMiddlewareAttribute&lt;/code&gt;, and emits code that makes them available via &lt;code dir=&quot;auto&quot;&gt;context.GetHandlerAttribute&amp;#x3C;T&gt;()&lt;/code&gt; — no reflection needed at runtime.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Your handler class must be &lt;code dir=&quot;auto&quot;&gt;partial&lt;/code&gt;&lt;/strong&gt; for this to work.  That’s the one rule.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;custom-middleware-attributes&quot;&gt;Custom Middleware Attributes&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Best of all, this works with your own attributes too.  Just inherit from &lt;code dir=&quot;auto&quot;&gt;MediatorMiddlewareAttribute&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuditLogAttribute&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;MediatorMiddlewareAttribute&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Category&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;General&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;partial&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CreateOrderHandler&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;IRequestHandler&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;CreateOrderRequest&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;Order&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;AuditLog&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Category&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Orders&quot;&lt;/span&gt;&lt;span&gt;)]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Order&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;Handle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;CreateOrderRequest&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;IMediatorContext&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ct&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// In your middleware — zero reflection&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AuditLogMiddleware&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;TRequest&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;TResult&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;IRequestMiddleware&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;TRequest&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;TResult&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;where&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TRequest&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;IRequest&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;TResult&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;TResult&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;Process&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;IMediatorContext&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;RequestHandlerDelegate&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;TResult&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;next&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ct&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; context.&lt;/span&gt;&lt;span&gt;GetHandlerAttribute&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;AuditLogAttribute&lt;/span&gt;&lt;span&gt;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (attr &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// log with attr.Category&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;next&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;6-openapi-http-client-generation&quot;&gt;6. OpenAPI HTTP Client Generation&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The HTTP extension’s OpenAPI source generator is arguably the crown jewel of Shiny Mediator’s AOT story.  From a single OpenAPI spec, it generates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Request contracts&lt;/strong&gt; with proper HTTP verb, route, query, header, and body annotations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Response types&lt;/strong&gt; matching the API schema&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A typed request handler&lt;/strong&gt; that uses &lt;code dir=&quot;auto&quot;&gt;HttpClient&lt;/code&gt; under the hood&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JSON converters&lt;/strong&gt; (when &lt;code dir=&quot;auto&quot;&gt;GenerateJsonConverters=&quot;true&quot;&lt;/code&gt;) for every generated type&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DI registration&lt;/strong&gt; via &lt;code dir=&quot;auto&quot;&gt;.AddGeneratedOpenApiClient()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All from a csproj item:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;ItemGroup&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;MediatorHttp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Include&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;PetStore&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                  &lt;/span&gt;&lt;span&gt;Uri&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;https://petstore.swagger.io/v2/swagger.json&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                  &lt;/span&gt;&lt;span&gt;Namespace&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;MyApp.PetStore&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                  &lt;/span&gt;&lt;span&gt;ContractPostfix&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;HttpRequest&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                  &lt;/span&gt;&lt;span&gt;GenerateJsonConverters&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;true&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                  &lt;/span&gt;&lt;span&gt;Visible&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;false&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;ItemGroup&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;services.&lt;/span&gt;&lt;span&gt;AddShinyMediator&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;x.&lt;/span&gt;&lt;span&gt;AddMediatorRegistry&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;x.&lt;/span&gt;&lt;span&gt;AddGeneratedOpenApiClient&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Now every HTTP call flows through the mediator pipeline — which means caching, offline, validation, performance logging, and every other middleware you’ve configured &lt;strong&gt;automatically applies&lt;/strong&gt; to your API calls.  And it’s all AOT-safe because every type is known at compile time.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;7-aspnet-endpoint-source-generation&quot;&gt;7. ASP.NET Endpoint Source Generation&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;On the server side, Shiny Mediator can source-generate minimal API endpoints directly from your handlers:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;MediatorScoped&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;partial&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CreateUserHandler&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;IRequestHandler&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;CreateUserRequest&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;UserResponse&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Post&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/api/users&quot;&lt;/span&gt;&lt;span&gt;)]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;UserResponse&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;Handle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;CreateUserRequest&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;IMediatorContext&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ct&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// create user&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The source generator emits the &lt;code dir=&quot;auto&quot;&gt;app.MapPost(&quot;/api/users&quot;, ...)&lt;/code&gt; call and the DI wiring.  No controller classes.  No manual endpoint mapping.  Just your handler with an HTTP attribute and the generator does the rest.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;the-big-picture&quot;&gt;The Big Picture&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here’s what Shiny Mediator source-generates at compile time — and what it &lt;strong&gt;doesn’t&lt;/strong&gt; do at runtime:&lt;/p&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Feature&lt;/th&gt;&lt;th&gt;Compile Time (Source Gen)&lt;/th&gt;&lt;th&gt;Runtime (Reflection)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Handler DI Registration&lt;/td&gt;&lt;td&gt;✅ &lt;code dir=&quot;auto&quot;&gt;[MediatorSingleton]&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;[MediatorScoped]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;❌ No scanning&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Request/Stream Executors&lt;/td&gt;&lt;td&gt;✅ Typed dispatch&lt;/td&gt;&lt;td&gt;❌ No &lt;code dir=&quot;auto&quot;&gt;MakeGenericType&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;JSON Serialization&lt;/td&gt;&lt;td&gt;✅ &lt;code dir=&quot;auto&quot;&gt;[SourceGenerateJsonConverter]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;❌ No reflection-based serializers&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Contract Keys&lt;/td&gt;&lt;td&gt;✅ &lt;code dir=&quot;auto&quot;&gt;[ContractKey]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;❌ No property reflection&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Middleware Attributes&lt;/td&gt;&lt;td&gt;✅ &lt;code dir=&quot;auto&quot;&gt;[Cache]&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;[OfflineAvailable]&lt;/code&gt;, custom&lt;/td&gt;&lt;td&gt;❌ No &lt;code dir=&quot;auto&quot;&gt;GetCustomAttributes&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;HTTP Clients&lt;/td&gt;&lt;td&gt;✅ &lt;code dir=&quot;auto&quot;&gt;MediatorHttp&lt;/code&gt; MSBuild item&lt;/td&gt;&lt;td&gt;❌ No runtime proxy generation&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ASP.NET Endpoints&lt;/td&gt;&lt;td&gt;✅ Handler method attributes&lt;/td&gt;&lt;td&gt;❌ No controller discovery&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The result is a mediator library that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ships on iOS&lt;/strong&gt; without fighting the linker&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Runs in Blazor WASM&lt;/strong&gt; without bloating the download&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cold-starts fast&lt;/strong&gt; on ASP.NET because there’s nothing to scan or JIT&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Trims clean&lt;/strong&gt; because every code path is statically reachable&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;getting-started-with-aot&quot;&gt;Getting Started with AOT&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you’re already using Shiny Mediator, the path to full AOT is straightforward:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Make handler and contract classes &lt;code dir=&quot;auto&quot;&gt;partial&lt;/code&gt;&lt;/strong&gt; — the source generators need this&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Add &lt;code dir=&quot;auto&quot;&gt;[MediatorSingleton]&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;[MediatorScoped]&lt;/code&gt;&lt;/strong&gt; to your handlers and middleware&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use &lt;code dir=&quot;auto&quot;&gt;[ContractKey]&lt;/code&gt;&lt;/strong&gt; instead of implementing &lt;code dir=&quot;auto&quot;&gt;IContractKey&lt;/code&gt; manually&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Set &lt;code dir=&quot;auto&quot;&gt;GenerateJsonConverters=&quot;true&quot;&lt;/code&gt;&lt;/strong&gt; on your &lt;code dir=&quot;auto&quot;&gt;MediatorHttp&lt;/code&gt; items&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use &lt;code dir=&quot;auto&quot;&gt;[SourceGenerateJsonConverter]&lt;/code&gt;&lt;/strong&gt; on custom types that need serialization&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Call &lt;code dir=&quot;auto&quot;&gt;x.AddMediatorRegistry()&lt;/code&gt;&lt;/strong&gt; in your startup instead of manual registration&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That’s it.  No reflection.  No runtime surprises.  Just compile-time confidence.&lt;/p&gt;
&lt;hr&gt;
&lt;div&gt;&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;AOT and trimming aren’t just performance checkboxes — they’re the foundation of where .NET is heading.  Shiny Mediator has gone all-in on source generation to make sure you can take your mediator pipeline anywhere .NET runs: mobile, browser, server, and beyond.&lt;/p&gt;
&lt;p&gt;If reflection was the training wheels, source generation is the carbon fiber frame.  Time to ride.&lt;/p&gt;
&lt;p&gt;Check out the full &lt;a href=&quot;https://shinylib.net/mediator/sourcegeneration&quot;&gt;source generation docs&lt;/a&gt; and the &lt;a href=&quot;https://shinylib.net/mediator/extensions/http&quot;&gt;HTTP extension docs&lt;/a&gt; for all the details.&lt;/p&gt;</content:encoded><category>Mediator</category></item><item><title>What&apos;s New in Shiny Mediator 6</title><link>https://www.shinylib.net/blog/shinymediator-whats-new-v6/</link><guid isPermaLink="true">https://www.shinylib.net/blog/shinymediator-whats-new-v6/</guid><pubDate>Tue, 10 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Shiny Mediator v6 has landed and it’s packed with some exciting new features and improvements. This release is a big step forward for the library, focusing on better source generation, AOT readiness, ASP.NET interop, and some slick new middleware.  Let’s dive in.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;net-8--10-minimum-targets&quot;&gt;.NET 8 &amp;#x26; 10 Minimum Targets&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The biggest (and breaking) change is that Shiny Mediator v6 now targets .NET 8 and .NET 10 as minimum versions.  This allows us to take advantage of the latest runtime features for performance, trimming, and AOT compilation.  If you’re still on older versions, you’ll need to stay on v5.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;revamped-http-client-source-generation&quot;&gt;Revamped HTTP Client Source Generation&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The HTTP client source generation has been completely overhauled.  In v5, the OpenAPI generation would produce contracts, but you’d still need to wire up handlers and serialization yourself.  In v6, the source generator now produces &lt;strong&gt;everything&lt;/strong&gt; — handlers, contracts, JSON converters, and dependency injection registration.&lt;/p&gt;
&lt;p&gt;This is a breaking change because you’ll need to call a new registration method, but the setup through &lt;code dir=&quot;auto&quot;&gt;MediatorHttpItem&lt;/code&gt; in your csproj remains the same:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;ItemGroup&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;MediatorHttp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Include&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;OpenApiRemote&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                  &lt;/span&gt;&lt;span&gt;Uri&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;https://yourapi.com/openapi.json&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                  &lt;/span&gt;&lt;span&gt;Namespace&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;My.Namespace&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                  &lt;/span&gt;&lt;span&gt;ContractPostfix&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;HttpRequest&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                  &lt;/span&gt;&lt;span&gt;GenerateJsonConverters&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;true&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                  &lt;/span&gt;&lt;span&gt;Visible&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;false&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;ItemGroup&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;And then in your startup:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;builder.Services.&lt;/span&gt;&lt;span&gt;AddShinyMediator&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; x.&lt;/span&gt;&lt;span&gt;AddGeneratedOpenApiClient&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That’s it.  Full HTTP client with AOT-safe serialization, middleware support, and zero boilerplate.&lt;/p&gt;
&lt;p&gt;Another nice improvement: if your OpenAPI spec doesn’t have an &lt;code dir=&quot;auto&quot;&gt;OperationId&lt;/code&gt;, the source generator now &lt;strong&gt;infers&lt;/strong&gt; a name using the HTTP verb and path.  For example, &lt;code dir=&quot;auto&quot;&gt;GET /user/list&lt;/code&gt; becomes &lt;code dir=&quot;auto&quot;&gt;GetUserList&lt;/code&gt;.  No more missing contracts because someone forgot to set an operation ID.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;directhttprequest-is-gone&quot;&gt;DirectHttpRequest is Gone&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;DirectHttpRequest&lt;/code&gt; type has been removed.  You can still create your own HTTP objects with the &lt;code dir=&quot;auto&quot;&gt;[Http]&lt;/code&gt; attribute which will source generate a handler for you.  This was cleaned up to reduce confusion between the direct approach and the more powerful generated approach.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;publishtobackground&quot;&gt;PublishToBackground&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;One of the more subtle but impactful additions is &lt;code dir=&quot;auto&quot;&gt;PublishToBackground&lt;/code&gt; on &lt;code dir=&quot;auto&quot;&gt;IMediator&lt;/code&gt;.  If you’ve ever tried to fire-and-forget an event in ASP.NET, you’ve probably run into scoping issues where the &lt;code dir=&quot;auto&quot;&gt;HttpContext&lt;/code&gt; or scoped services get disposed before your event handlers finish.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;PublishToBackground&lt;/code&gt; solves this by spawning a &lt;strong&gt;new child scope&lt;/strong&gt;, so you can fire events without worrying about disposal:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyController&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;IMediator&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mediator&lt;/span&gt;&lt;span&gt;) : &lt;/span&gt;&lt;span&gt;ControllerBase&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;HttpPost&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;IActionResult&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;DoSomething&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// this returns immediately and the event handlers&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// run in a separate scope&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; mediator.&lt;/span&gt;&lt;span&gt;PublishToBackground&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SomethingHappenedEvent&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Ok&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;You can still use the standard &lt;code dir=&quot;auto&quot;&gt;Publish&lt;/code&gt; to await all event handlers in the same scope.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;async-enumerable-responses--server-sent-events&quot;&gt;Async Enumerable Responses &amp;#x26; Server-Sent Events&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Stream requests now support &lt;code dir=&quot;auto&quot;&gt;IAsyncEnumerable&amp;#x3C;T&gt;&lt;/code&gt; responses that can be wired directly to &lt;strong&gt;Server-Sent Events&lt;/strong&gt; endpoints.  Just mark your contract with &lt;code dir=&quot;auto&quot;&gt;IServerSentEventsStream&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/api/sse&quot;&lt;/span&gt;&lt;span&gt;)]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ServerSentEventsRequest&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;IStreamRequest&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;EventItem&lt;/span&gt;&lt;span&gt;&gt;, &lt;/span&gt;&lt;span&gt;IServerSentEventsStream&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;If your endpoint does &lt;strong&gt;not&lt;/strong&gt; support server-sent events, you can still use &lt;code dir=&quot;auto&quot;&gt;IStreamRequest&amp;#x3C;T&gt;&lt;/code&gt; — just drop the &lt;code dir=&quot;auto&quot;&gt;IServerSentEventsStream&lt;/code&gt; interface and you get a standard async enumerable response.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;event-throttling&quot;&gt;Event Throttling&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The new &lt;code dir=&quot;auto&quot;&gt;[Throttle]&lt;/code&gt; attribute implements a &lt;strong&gt;debounce pattern&lt;/strong&gt; for event handlers.  This is huge for scenarios like search-as-you-type, sensor data processing, or any rapid-fire event where you only care about the latest value:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;services.&lt;/span&gt;&lt;span&gt;AddShinyMediator&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cfg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; cfg.&lt;/span&gt;&lt;span&gt;AddMediatorRegistry&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;AddThrottleEventMiddleware&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;MediatorSingleton&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;partial&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SearchHandler&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;IEventHandler&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;SearchChangedEvent&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Throttle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;300&lt;/span&gt;&lt;span&gt;)] &lt;/span&gt;&lt;span&gt;// wait 300ms after last event&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Handle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;SearchChangedEvent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@event&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;IMediatorContext&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ct&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// only fires once the user stops typing for 300ms&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;results&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; searchService.&lt;/span&gt;&lt;span&gt;Search&lt;/span&gt;&lt;span&gt;(@event.Query, ct);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;How it works:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When an event is published, a timer starts&lt;/li&gt;
&lt;li&gt;If the same event fires again before the timer expires, the timer resets and the previous event is discarded&lt;/li&gt;
&lt;li&gt;Only after the full delay with no new events does the handler execute with the latest data&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Your handler class must be &lt;code dir=&quot;auto&quot;&gt;partial&lt;/code&gt; for source generation to work.&lt;/p&gt;
&lt;aside aria-label=&quot;Tip&quot;&gt;&lt;p aria-hidden=&quot;true&quot;&gt;Tip&lt;/p&gt;&lt;div&gt;&lt;p&gt;Know what else is epic?  That &lt;code dir=&quot;auto&quot;&gt;[MediatorSingleton]&lt;/code&gt; &amp;#x26; &lt;code dir=&quot;auto&quot;&gt;[MediatorScoped]&lt;/code&gt; also work on middleware!&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;div&gt;&lt;h2 id=&quot;middleware-ordering&quot;&gt;Middleware Ordering&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Previously, middleware executed in DI registration order.  That’s fine for simple scenarios, but as your pipeline grows you need more control.  The new &lt;code dir=&quot;auto&quot;&gt;[MiddlewareOrder]&lt;/code&gt; attribute gives you explicit ordering:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;MiddlewareOrder&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;)] &lt;/span&gt;&lt;span&gt;// runs first (outermost)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ValidationMiddleware&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;TRequest&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;TResult&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;IRequestMiddleware&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;TRequest&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;TResult&lt;/span&gt;&lt;span&gt;&gt; { ... }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;MiddlewareOrder&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)] &lt;/span&gt;&lt;span&gt;// default&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LoggingMiddleware&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;TRequest&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;TResult&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;IRequestMiddleware&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;TRequest&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;TResult&lt;/span&gt;&lt;span&gt;&gt; { ... }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;MiddlewareOrder&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;)] &lt;/span&gt;&lt;span&gt;// runs last (closest to handler)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CachingMiddleware&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;TRequest&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;TResult&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;IRequestMiddleware&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;TRequest&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;TResult&lt;/span&gt;&lt;span&gt;&gt; { ... }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This produces: &lt;strong&gt;Validation → Logging → Caching → Handler → Caching → Logging → Validation&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Lower values run first.  If you don’t use the attribute, everything works exactly as before — it’s fully opt-in.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;http-response-cache-middleware&quot;&gt;HTTP Response Cache Middleware&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A new built-in middleware that caches HTTP responses based on &lt;code dir=&quot;auto&quot;&gt;CacheControl&lt;/code&gt; &lt;code dir=&quot;auto&quot;&gt;MaxAge&lt;/code&gt; headers.  If your API sends proper cache headers, Shiny Mediator will respect them automatically without any extra configuration on your part.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;additional-improvements--fixes&quot;&gt;Additional Improvements &amp;#x26; Fixes&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Beyond the headline features, v6 includes a steady stream of refinements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;EventStream redesigned&lt;/strong&gt; — &lt;code dir=&quot;auto&quot;&gt;IMediator.EventStream&lt;/code&gt; was dropping events under load.  It’s been completely redesigned for speed and reliability.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RuntimeEventRegister is now thread-safe&lt;/strong&gt; — dynamic event handler registration no longer has race conditions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BaseHttpRequestHandler now handles commands&lt;/strong&gt; — not just requests.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenAPI source generation fixes&lt;/strong&gt; — proper URI path generation for PUT/POST, fixed nullable support, TimeSpan handling, and duplicate type issues.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prism RegionNavigationCommand&lt;/strong&gt; — contributed by &lt;a href=&quot;https://github.com/codelisk&quot;&gt;codelisk&lt;/a&gt; for region-based navigation in Prism.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;migration-tips&quot;&gt;Migration Tips&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you’re coming from v5, here’s the key things to watch for:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Update your target framework&lt;/strong&gt; to .NET 8 or .NET 10&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTP client registration&lt;/strong&gt; — call the new &lt;code dir=&quot;auto&quot;&gt;AddGeneratedOpenApiClient()&lt;/code&gt; method&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Remove DirectHttpRequest usages&lt;/strong&gt; — switch to &lt;code dir=&quot;auto&quot;&gt;[Http]&lt;/code&gt; attribute-based contracts&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenAPI contracts&lt;/strong&gt; may have slightly different names due to the automatic &lt;code dir=&quot;auto&quot;&gt;HttpRequest&lt;/code&gt; postfix&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Shiny Mediator v6 continues the push toward zero-boilerplate, AOT-ready mediation for .NET.  The revamped HTTP source generation alone is worth the upgrade — and features like event throttling and middleware ordering bring capabilities that would take significant effort to build yourself.&lt;/p&gt;
&lt;p&gt;Check out the &lt;a href=&quot;https://shinylib.net/mediator/&quot;&gt;full documentation&lt;/a&gt; and the &lt;a href=&quot;https://shinylib.net/release-notes/mediator/&quot;&gt;complete release notes&lt;/a&gt; for all the details.&lt;/p&gt;</content:encoded><category>Mediator</category></item><item><title>Shiny Mediator - Getting Started</title><link>https://www.shinylib.net/blog/shinymediator-gettingstarted/</link><guid isPermaLink="true">https://www.shinylib.net/blog/shinymediator-gettingstarted/</guid><pubDate>Fri, 30 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Mediator patterns have been getting a lot of attention lately, and for good reason. They help to decouple components in an application, making it easier to manage complexity and improve maintainability.  Mediator patterns also go by the names “Vertical slice architecture” and CQRS (Command Query Responsibility Segregation).
The big guy in .NET mediation is obviously &lt;a href=&quot;https://mediatr.io/&quot;&gt;MediatR&lt;/a&gt; by Jimmy Bogard.  It’s an amazing library that has been used by an absolute ton of applications.  It recently went to a paid model, which is understandable given the amount of work that goes into maintaining a library of that size.&lt;/p&gt;
&lt;p&gt;The reason for building Shiny Mediator was to create a mediation library, but make sure it works with apps built on platforms like .NET MAUI and Blazor WebAssembly while also including some more “batteries included” features.  This doesn’t mean I’ve neglected things like ASPNET support, but we’ll get to that in a future article.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-haters&quot;&gt;The Haters&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Many engineers will call mediators “over engineering” or “an anti-pattern”.  I disagree.  In fact, I think mediators are one of the best patterns for building complex applications without going full bat stuff crazy with microservices out of the gate.  You can run in a monolith while still keeping things decoupled and manageable thereby making it easy to “SLICE” a piece out and move it to a microservice as your application traffic grows.
Another advantage of a mediation pattern is can remove the “dependency injection hell” of adding a services for things like logging, caching, and other cross cutting concerns.  Instead of having to add these services to every handler, you can just add them to the mediator pipeline, configure them or stick an attribute on a handler.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyViewModel&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;IDataService&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ICacheManager&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cache&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;IConfiguration&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;configuration&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ILogger&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;MyViewModel&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;logger&lt;/span&gt;&lt;span&gt;)  {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LoadData&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.Data &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cache.&lt;/span&gt;&lt;span&gt;TryGet&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;MyKey&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ct&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; data.&lt;/span&gt;&lt;span&gt;GetData&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The above code doesn’t look too bad, but&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Imagine you have 20 view models that all need these services.&lt;/li&gt;
&lt;li&gt;How do I make sure I’m grabbing from the same cache everytime or clearing it for that matter?&lt;/li&gt;
&lt;li&gt;Building cache keys can be a pain&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;ContractKey&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;{UserId}-{IsActive}&quot;&lt;/span&gt;&lt;span&gt;)]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GetDataRequest&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IsActive&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;UserId&lt;/span&gt;&lt;span&gt;) : &lt;/span&gt;&lt;span&gt;IRequest&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;GetDataResponse&lt;/span&gt;&lt;span&gt;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyViewModel&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;IMediator&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mediator&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Cache&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;300&lt;/span&gt;&lt;span&gt;)]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LoadData&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; mediator.&lt;/span&gt;&lt;span&gt;Send&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GetDataRequest&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.Data &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; response.Result;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Homegrown/built in mediation patterns are often added, but require you do the growing.  Middleware management is also not a simple process.  Creating keys for caching requests, another whamo of complexity.  These are all things solved by Shiny Mediator.
AI generated code solutions is another one I hear.  “Just use AI to generate the code for you”.  AI is great at boilerplate or when you give it a very specific input &amp;#x26; output, but architecture… it will throw “slop” at you.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;some-of-the-current-challenges&quot;&gt;Some of the Current Challenges&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;As with all patterns, there are some trade offs.  Mediators map objects to handlers (methods or controllers).  It is currently not tool friendly to find a corresponding handler for a contract, so you want to make sure you structure your solution and projects well to find
handlers without too much effort.  Another problem that can occur is middleware executes in a pipeline, so if you have a lot of middleware, you can end up with performance issues if you’re not careful about managing the middleware to ensure quick execution.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;experience-of-the-past&quot;&gt;Experience of the Past&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;I’ve seen some large .NET MAUI applications
that had so many engineers working on them, there was constant “fire drills” (the running joke that came to be).  Team A would change something that would break Team B’s work.  Team C would have to make changes to Team A’s code to update navigation or data retrieval.&lt;br&gt;
Quite often, these breaking changes wouldn’t even be picked up until a regression test.&lt;/p&gt;
&lt;p&gt;In this post, we’ll explore how to get started with Shiny Mediator, a library that implements the Mediator pattern with a focus on apps written with .NET.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-are-some-of-the-batteries-included&quot;&gt;What are some of the “batteries included”?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Most mediator libraries hand you a pipe and say “good luck”.  Shiny Mediator hands you a pipe, a toolbox, a hard hat, and a coffee.  Here’s what’s in the box:&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;offline-mode&quot;&gt;Offline Mode&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Your users don’t always have internet. Shocking, I know. But building offline support from scratch is the kind of soul-crushing work that makes devs question their career choices. With Shiny Mediator, slap an attribute on your handler and you’re done:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;partial&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GetOrdersHandler&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;IRequestHandler&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;GetOrdersRequest&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;IReadOnlyList&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Order&lt;/span&gt;&lt;span&gt;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;OfflineAvailable&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;IReadOnlyList&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Order&lt;/span&gt;&lt;span&gt;&gt;&gt; &lt;/span&gt;&lt;span&gt;Handle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;GetOrdersRequest&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;IMediatorContext&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ct&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// When online: calls your API, stores the result&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// When offline: returns the last stored result&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// You did nothing. You&apos;re welcome.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; api.&lt;/span&gt;&lt;span&gt;GetOrders&lt;/span&gt;&lt;span&gt;(ct);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;You can also configure it via &lt;code dir=&quot;auto&quot;&gt;appsettings.json&lt;/code&gt; if attributes aren’t your thing:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;Mediator&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;Offline&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;MyNamespace.GetOrdersRequest&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Want to know if the data came from the offline store?  The context tells you:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; mediator.&lt;/span&gt;&lt;span&gt;Request&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GetOrdersRequest&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;offline&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; response.Context.&lt;/span&gt;&lt;span&gt;Offline&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (offline &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// data is from offline store&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// offline.Timestamp tells you WHEN it was stored&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// maybe show a &quot;stale data&quot; indicator to the user&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;caching&quot;&gt;Caching&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Caching is one of the two hardest problems in computer science (the other being naming things and off-by-one errors).  Shiny Mediator makes it embarrassingly easy:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;partial&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GetProductsHandler&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;IRequestHandler&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;GetProductsRequest&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Product&lt;/span&gt;&lt;span&gt;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Cache&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;AbsoluteExpirationSeconds&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;300&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;SlidingExpirationSeconds&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;60&lt;/span&gt;&lt;span&gt;)]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;Product&lt;/span&gt;&lt;span&gt;&gt;&gt; &lt;/span&gt;&lt;span&gt;Handle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;GetProductsRequest&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;IMediatorContext&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ct&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// This only runs when cache misses.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// No cache key management. No IMemoryCache injection. No tears.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; db.&lt;/span&gt;&lt;span&gt;GetProducts&lt;/span&gt;&lt;span&gt;(ct);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Setup is one line:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;services.&lt;/span&gt;&lt;span&gt;AddShinyMediator&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; x.&lt;/span&gt;&lt;span&gt;AddMemoryCaching&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Need a cache that survives app restarts?  The MAUI and Uno extensions have a &lt;strong&gt;persistent cache&lt;/strong&gt; that writes to disk.  Same attribute, same config — it just doesn’t evaporate when the user kills your app:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;services.&lt;/span&gt;&lt;span&gt;AddShinyMediator&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; x.&lt;/span&gt;&lt;span&gt;AddMauiPersistentCache&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;You can also force a cache refresh when you need fresh data (pull-to-refresh, anyone?):&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; mediator.&lt;/span&gt;&lt;span&gt;Request&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GetProductsRequest&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CancellationToken.None,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;ctx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; ctx.&lt;/span&gt;&lt;span&gt;ForceCacheRefresh&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// response.Result is guaranteed fresh&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;validation&quot;&gt;Validation&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Validation: where developers go to argue about whether to throw exceptions or return error objects.  We support both.  Pick your poison.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Data Annotations&lt;/strong&gt; (built-in, no extra package):&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;services.&lt;/span&gt;&lt;span&gt;AddShinyMediator&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cfg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; cfg.&lt;/span&gt;&lt;span&gt;AddDataAnnotations&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Validate&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CreateUserCommand&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;ICommand&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Required&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Name&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Range&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;150&lt;/span&gt;&lt;span&gt;)] &lt;/span&gt;&lt;span&gt;// optimistic about human lifespans&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Age&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;FluentValidation&lt;/strong&gt; (for the overachievers):&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;services.&lt;/span&gt;&lt;span&gt;AddShinyMediator&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cfg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; cfg.&lt;/span&gt;&lt;span&gt;AddFluentValidation&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Validate&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CreateUserCommand&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;ICommand&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;? &lt;/span&gt;&lt;span&gt;Name&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CreateUserValidator&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;AbstractValidator&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;CreateUserCommand&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CreateUserValidator&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;RuleFor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; x.Name).&lt;/span&gt;&lt;span&gt;NotEmpty&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;WithMessage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;A user needs a name. Even &apos;Bob&apos; will do.&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Validation runs &lt;em&gt;before&lt;/em&gt; your handler ever sees the request.  Invalid data never touches your business logic.  It’s like a bouncer for your code.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;performance-logging&quot;&gt;Performance Logging&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Want to know which handler is being a lazy bum? Performance logging middleware tracks execution time through built-in diagnostics via &lt;code dir=&quot;auto&quot;&gt;Microsoft.Extensions.Diagnostics&lt;/code&gt;.  No extra setup, no third-party APM required.  Your handlers are already emitting telemetry — you just need to listen.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;http-client-generation&quot;&gt;HTTP Client Generation&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;This one deserves its own blog post (coming soon), but the short version: point Shiny Mediator at your OpenAPI spec and it generates contracts, handlers, JSON converters, and DI registration.  No HttpClientFactory plumbing. No System.Text.Json serialization contexts.  No Polly setup.  Just:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;ItemGroup&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;MediatorHttp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Include&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;MyApi&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                  &lt;/span&gt;&lt;span&gt;Uri&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;https://myapi.com/openapi.json&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                  &lt;/span&gt;&lt;span&gt;Namespace&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;MyApp.Api&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                  &lt;/span&gt;&lt;span&gt;ContractPostfix&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;HttpRequest&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                  &lt;/span&gt;&lt;span&gt;GenerateJsonConverters&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;true&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                  &lt;/span&gt;&lt;span&gt;Visible&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;false&quot;&lt;/span&gt;&lt;span&gt; /&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x3C;/&lt;/span&gt;&lt;span&gt;ItemGroup&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;services.&lt;/span&gt;&lt;span&gt;AddShinyMediator&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; x.&lt;/span&gt;&lt;span&gt;AddGeneratedOpenApiClient&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Now just use it like any other mediator request&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;users&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; mediator.&lt;/span&gt;&lt;span&gt;Request&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GetUsersHttpRequest&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;All your middleware (caching, offline, validation, resilience) works with HTTP requests too.  One attribute to cache API calls.  One attribute for offline fallback.  Your API client just became the most resilient thing in your entire codebase — and you wrote zero infrastructure code.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Enough talk.  Let’s build something.  I’ll walk you through a .NET MAUI setup since that’s where Shiny Mediator really flexes, but this works just as well with Blazor, ASP.NET, or plain old console apps (we don’t judge).&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;step-1-install-the-packages&quot;&gt;Step 1: Install the packages&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;dotnet&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;package&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Shiny.Mediator&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;dotnet&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;package&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Shiny.Mediator.Maui&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;For Blazor, swap &lt;code dir=&quot;auto&quot;&gt;Shiny.Mediator.Maui&lt;/code&gt; for &lt;code dir=&quot;auto&quot;&gt;Shiny.Mediator.Blazor&lt;/code&gt;.  For ASP.NET or console apps, just &lt;code dir=&quot;auto&quot;&gt;Shiny.Mediator&lt;/code&gt; on its own is fine.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;step-2-define-your-contracts&quot;&gt;Step 2: Define your contracts&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Contracts are just plain C# records or classes that implement one of the mediator interfaces.  Think of them as the “what” — what data goes in, what data comes out.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Shiny&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Mediator&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// A command - fire and forget, no return value&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CreateTodoCommand&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Title&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Description&lt;/span&gt;&lt;span&gt;) : &lt;/span&gt;&lt;span&gt;ICommand&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// A request - send something in, get something back&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GetTodosRequest&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IncludeCompleted&lt;/span&gt;&lt;span&gt;) : &lt;/span&gt;&lt;span&gt;IRequest&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;TodoItem&lt;/span&gt;&lt;span&gt;&gt;&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// An event - broadcast to anyone who&apos;s listening&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TodoCreatedEvent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TodoItem&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Item&lt;/span&gt;&lt;span&gt;) : &lt;/span&gt;&lt;span&gt;IEvent&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;step-3-create-your-handlers&quot;&gt;Step 3: Create your handlers&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Handlers are where the actual work happens.  One handler per command/request, but you can have multiple event handlers.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Shiny&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Mediator&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;MediatorSingleton&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;// source generator handles DI registration for you&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CreateTodoHandler&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;ICommandHandler&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;CreateTodoCommand&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IMyDatabase&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IMediator&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mediator&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CreateTodoHandler&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;IMyDatabase&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;IMediator&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mediator&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.db &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; db;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.mediator &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; mediator;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Handle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;CreateTodoCommand&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;IMediatorContext&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ct&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;item&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TodoItem&lt;/span&gt;&lt;span&gt;(command.Title, command.Description);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; db.&lt;/span&gt;&lt;span&gt;Insert&lt;/span&gt;&lt;span&gt;(item, ct);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// broadcast that a todo was created&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; mediator.&lt;/span&gt;&lt;span&gt;Publish&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TodoCreatedEvent&lt;/span&gt;&lt;span&gt;(item));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;MediatorSingleton&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;partial&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GetTodosHandler&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;IRequestHandler&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;GetTodosRequest&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;TodoItem&lt;/span&gt;&lt;span&gt;&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IMyDatabase&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GetTodosHandler&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;IMyDatabase&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.db &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; db;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Cache&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;AbsoluteExpirationSeconds&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;120&lt;/span&gt;&lt;span&gt;)]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;OfflineAvailable&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;TodoItem&lt;/span&gt;&lt;span&gt;&gt;&gt; &lt;/span&gt;&lt;span&gt;Handle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;GetTodosRequest&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;IMediatorContext&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ct&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// cached for 2 minutes AND available offline&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// two attributes, zero infrastructure code, infinite smugness&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; db.&lt;/span&gt;&lt;span&gt;GetTodos&lt;/span&gt;&lt;span&gt;(request.IncludeCompleted, ct);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;step-4-wire-it-up&quot;&gt;Step 4: Wire it up&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MauiProgram&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MauiApp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CreateMauiApp&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; MauiApp&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;CreateBuilder&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;UseMauiApp&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;builder.Services.&lt;/span&gt;&lt;span&gt;AddShinyMediator&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;x.&lt;/span&gt;&lt;span&gt;UseMaui&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;x.&lt;/span&gt;&lt;span&gt;AddMemoryCaching&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;x.&lt;/span&gt;&lt;span&gt;AddDataAnnotations&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// let the source generator register everything&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;builder.Services.&lt;/span&gt;&lt;span&gt;AddMediatorRegistry&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; builder.&lt;/span&gt;&lt;span&gt;Build&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;step-5-use-it&quot;&gt;Step 5: Use it&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TodoListViewModel&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;BaseViewModel&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;IEventHandler&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;TodoCreatedEvent&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;readonly&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IMediator&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mediator&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TodoListViewModel&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;IMediator&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mediator&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.mediator &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; mediator;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LoadTodos&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// one line. cached. offline-available. validated.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; mediator.&lt;/span&gt;&lt;span&gt;Request&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GetTodosRequest&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;IncludeCompleted&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.Todos &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; response.Result;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CreateTodo&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; mediator.&lt;/span&gt;&lt;span&gt;Send&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CreateTodoCommand&lt;/span&gt;&lt;span&gt;(title, description));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// don&apos;t reload manually - the event handler below will fire&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// this fires automatically when TodoCreatedEvent is published&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// no subscription. no unsubscription. no memory leaks. no drama.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Handle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;TodoCreatedEvent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@event&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;IMediatorContext&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ct&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LoadTodos&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Notice that the ViewModel implements &lt;code dir=&quot;auto&quot;&gt;IEventHandler&amp;#x3C;TodoCreatedEvent&gt;&lt;/code&gt; directly.  With the MAUI extension, your ViewModels and Pages automatically participate in event broadcasting without being registered in DI.  When the page is popped from navigation, it stops receiving events.  No &lt;code dir=&quot;auto&quot;&gt;MessagingCenter.Unsubscribe&lt;/code&gt; nightmares.  No &lt;code dir=&quot;auto&quot;&gt;WeakReferenceMessenger&lt;/code&gt; gymnastics.  It just works.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-the-road-ahead&quot;&gt;What’s the Road Ahead&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Shiny Mediator has come a long way from its v1 days.  We’re now on v6 with full AOT and trimming support baked into the source generators.  So what’s next?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Deeper ASP.NET integration&lt;/strong&gt; — we’ve already got minimal API endpoint generation from handlers, server-sent events from stream requests, and HTTP response caching middleware.  Expect more first-class server scenarios.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Event throttling &amp;#x26; middleware ordering&lt;/strong&gt; — both landed in v6.  &lt;code dir=&quot;auto&quot;&gt;[Throttle(300)]&lt;/code&gt; on your event handlers gives you debounce for free (goodbye, search-as-you-type headaches).  &lt;code dir=&quot;auto&quot;&gt;[MiddlewareOrder]&lt;/code&gt; gives you explicit pipeline control.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Better tooling&lt;/strong&gt; — finding the handler for a contract is still a navigation exercise.  We’re exploring IDE integrations to make this seamless.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;More source generation&lt;/strong&gt; — the goal is zero reflection, zero runtime surprises.  If a handler is missing or misconfigured, you should know at compile time, not when your app blows up in production at 2 AM on a Saturday.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Community contributions&lt;/strong&gt; — shout out to &lt;a href=&quot;https://github.com/codelisk&quot;&gt;codelisk&lt;/a&gt; for Prism region navigation and &lt;a href=&quot;https://github.com/JeremyBP&quot;&gt;JeremyBP&lt;/a&gt; for MAUI modal stack iteration.  Keep ‘em coming.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Shiny Mediator isn’t trying to replace MediatR.  It’s trying to be the mediator that gives app developers — especially those on MAUI, Blazor, and Uno — a batteries-included experience that “just works” out of the box.  Caching, offline, validation, HTTP clients, event broadcasting, source generated registration… all of it designed so you can focus on your actual business logic instead of plumbing.&lt;/p&gt;
&lt;p&gt;Give it a spin.  Check out the &lt;a href=&quot;https://shinylib.net/mediator/&quot;&gt;full docs&lt;/a&gt;, play with the &lt;a href=&quot;https://github.com/shinyorg/mediatorsample&quot;&gt;sample apps&lt;/a&gt;, and come yell at me on &lt;a href=&quot;https://bsky.app/profile/shinylib.net&quot;&gt;BlueSky&lt;/a&gt; if something doesn’t work.&lt;/p&gt;
&lt;p&gt;Happy coding.  May your caches always be warm and your pipelines always be clean.&lt;/p&gt;
&lt;p&gt;Source generation and full blown AOT.  AOT is becoming such an important part of .NET, especially with .NET MAUI and Blazor.  We’re working hard on a version 5 release that will be fully AOT compliant out of the box!&lt;/p&gt;</content:encoded><category>Mediator</category></item><item><title>Mediator v2.0</title><link>https://www.shinylib.net/blog/mediator2/</link><guid isPermaLink="true">https://www.shinylib.net/blog/mediator2/</guid><pubDate>Sun, 22 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;h1 id=&quot;the-road-here&quot;&gt;The Road Here&lt;/h1&gt;&lt;/div&gt;
&lt;p&gt;Only a little over 3 months, I released v1.0 of Mediator.  Since then, I’ve been adding features as fast as they come to mind.  The focus on making the Mediator
pattern upfront for apps (in my opinion) has been a huge success.  I’ve been using it in my MAUI apps for offline, caching, &amp;#x26; resiliency middleware.  The amount of time
it saves me is pretty crazy.&lt;/p&gt;
&lt;p&gt;Since version the v1 release, I’ve been adding a ton of features including an &lt;a href=&quot;https://www.shinylib.net/docs/client/mediator/extensions/aspnet&quot;&gt;ASP.NET extension&lt;/a&gt; for HTTP endpoints straight to Mediator request handlers.  Quite recently - I found a lot of mediated calls would end up just wrapping HTTP calls done with Refit or Kiota.  I decided to add my
own source generator to deal with this.  Check out the &lt;a href=&quot;https://www.shinylib.net/docs/client/mediator/extensions/http&quot;&gt;HTTP Extension&lt;/a&gt; for more on this.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-new-in-20&quot;&gt;What’s New in 2.0&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;So what does 2.0 bring that requires a major version bump?  The first major feature is that I’ve brought the offline, stream replay, and user error notification middleware to Blazor webassembly.  This middleware was already
present in MAUI, but Blazor needed a “connectivity” &amp;#x26; “storage” service to match MAUI.&lt;/p&gt;
&lt;p&gt;The second major feature is that I’ve been moving a lot of the middleware to be configured via Microsoft.Extensions.Configuration.  This allows you to configure
middleware and handlers in a global way and without polluting your code with attributes everywhere.  Below is an example of all configuration we offer now:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;Mediator&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;Http&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;My.Namespace.Contract&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;https://shinylib.net/newbase&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;My.Namespace.*&quot;&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;&quot;https://shinylib.net/therestofhtenamespace&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;*&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;https://shinylib.net/everythingelse&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;Performance&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;*&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;&quot;ErrorThresholdMilliseconds&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;5000&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;Offline&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;Sample.Handlers.OfflineRequestHandler&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;&quot;AvailableAcrossSessions&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;ReplayStream&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;Sample.Handlers.MyRequest&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;&quot;AvailableAcrossSessions&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;TimerRefresh&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;My.Contacts.HttpData&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;&quot;IntervalSeconds&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;Resilience&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;My.Namespace.ResilientContract&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;&quot;RetryCount&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;&quot;RetryDelay&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;500&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;Cache&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;My.Contacts.*&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;&quot;Priority&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;High&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;&quot;AbsoluteExpirationSeconds&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;300&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;&quot;SlidingExpirationSeconds&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;60&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;UserErrorNotifications&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;*&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;&quot;*&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                    &lt;/span&gt;&lt;span&gt;&quot;Title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;ERROR&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                    &lt;/span&gt;&lt;span&gt;&quot;Message&quot;&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;&quot;Failed to do something&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;&quot;fr-CA&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                    &lt;/span&gt;&lt;span&gt;&quot;Title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;ERREUR&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;                    &lt;/span&gt;&lt;span&gt;&quot;Message&quot;&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;&quot;Échec de faire quelque chose&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;So let’s unpack this.  The first thing to notice is that we have ”*” to “glob” a namespace or ALL calls.  If you want to attack a specific contract, just fully label it.
We will find the nearest namespace to your contract before giving up as “not enabled” for a feature to be disabled on a contract.&lt;/p&gt;
&lt;aside aria-label=&quot;Note&quot;&gt;&lt;p aria-hidden=&quot;true&quot;&gt;Note&lt;/p&gt;&lt;div&gt;&lt;p&gt;Contracts with attributes still work as before, but they are treated as “secondary” to configuration.  Also note, that we have removed a couple of attributes in this release&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;I’m pretty happy with this release, but I still have a ton of ideas.  All the stuff you can do around the handlers with middleware is really exciting.  Have an idea for the mediator,
head on over to &lt;a href=&quot;https://github.com/shinyorg/mediator&quot;&gt;GitHub&lt;/a&gt; and add a feature request.  I’m always looking for new ideas.&lt;/p&gt;</content:encoded><category>Release</category><category>Mediator</category></item><item><title>Mediator v1.0</title><link>https://www.shinylib.net/blog/mediator1/</link><guid isPermaLink="true">https://www.shinylib.net/blog/mediator1/</guid><pubDate>Mon, 10 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;h2 id=&quot;what-is-it&quot;&gt;What is it?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Shiny Mediator is something new I’ve been working on.  I love Jimmy Bogard’s MediatR library on the server, but I just couldn’t
get it to fit the way I wanted for Apps… especially Blazor &amp;#x26; .NET MAUI apps.&lt;/p&gt;
&lt;p&gt;What is a mediator? It’s a small in-process version of a message bus.  MAUI has MessagingCenter, the Community toolkit has the weak message center, and Prism
offers an event aggregator.  They’re all great, but they lack in areas that I want “more”.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If an event errors, the whole chain dies in the publish&lt;/li&gt;
&lt;li&gt;Events are fired in a foreach (mostly)&lt;/li&gt;
&lt;li&gt;You have to tie into them and unsubscribe from them or you can leak memory&lt;/li&gt;
&lt;li&gt;They don’t provide the concept of command or request/response models (a command can only be responded to by a single handler)&lt;/li&gt;
&lt;li&gt;They don’t provide any sort of middleware (pre &amp;#x26; post handling)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sure - they aren’t geared for these things, but the question is “what is”?&lt;/p&gt;
&lt;p&gt;Some might say “this is overengineering” or “too complex”. I would counter that comment by saying that this can actually simplify your architecture
overall by removing a lot of complex plumbing around services, references, and navigation.&lt;/p&gt;
&lt;p&gt;Let’s go over some of the problems that we use mediator to solve within our apps&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-does-it-solve&quot;&gt;What Does It Solve&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;We believe that Shiny Mediator is the answer to these problems.  It’s a simple, yet powerful library that allows you to create a mediator in your app&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;problem-1---service--reference-hell&quot;&gt;Problem #1 - Service &amp;#x26; Reference Hell&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Does this look familiar to you?&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyViewModel&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;IConnectivity&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;conn&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;IDataService&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;IAuthService&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;auth&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;IDialogsService&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dialogs&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;ILogger&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;MyViewModel&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;logger&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;try {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (conn.IsConnected)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;myData&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; data.&lt;/span&gt;&lt;span&gt;GetDataRequest&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;else {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;dialogs.&lt;/span&gt;&lt;span&gt;Show&lt;/span&gt;&lt;span&gt;(&quot;&lt;/span&gt;&lt;span&gt;No&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Connection&lt;/span&gt;&lt;span&gt;&quot;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// cache?&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;Exception&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ex&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;dialogs.&lt;/span&gt;&lt;span&gt;Show&lt;/span&gt;&lt;span&gt;(ex.Message);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;logger.&lt;/span&gt;&lt;span&gt;LogError&lt;/span&gt;&lt;span&gt;(ex);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;With a bit of our middleware and some events, you can get here:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyViewModel&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;IMediator&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mediator&lt;/span&gt;&lt;span&gt;) : &lt;/span&gt;&lt;span&gt;IEventHandler&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;ConnectivityChangedEvent&lt;/span&gt;&lt;span&gt;&gt;, &lt;/span&gt;&lt;span&gt;IEventHandler&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;AuthChangedEvent&lt;/span&gt;&lt;span&gt;&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;myData&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; mediator.&lt;/span&gt;&lt;span&gt;Request&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GetDataRequest&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// logging, exception handling, offline caching can all be bundle into one nice clean call without the need for coupling&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;problem-2---messages-everywhere--leaks&quot;&gt;Problem #2 - Messages EVERYWHERE (+ Leaks)&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Do you use the MessagingCenter in Xamarin.Forms?  It’s a great tool, but it can lead to some memory leaks if you’re not careful.  It also doesn’t have
a pipeline, so any errors in any of the responders will crash the entire chain.  It doesn’t have a request/response style setup (not that it was meant for it), but
this means you still require other services.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyViewModel&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyViewModel&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;MessagingCenter.&lt;/span&gt;&lt;span&gt;Subscribe&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;SomeEvent1&lt;/span&gt;&lt;span&gt;&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;@event&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// do something&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;MessagingCenter.&lt;/span&gt;&lt;span&gt;Subscribe&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;SomeEvent2&lt;/span&gt;&lt;span&gt;&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;@event&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// do something&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;MessagingCenter.&lt;/span&gt;&lt;span&gt;Send&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SomeEvent1&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;MessagingCenter.&lt;/span&gt;&lt;span&gt;Send&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SomeEvent2&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// and don&apos;t forget to unsubscribe&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;MessagingCenter.&lt;/span&gt;&lt;span&gt;Unsubscribe&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;SomeEvent1&lt;/span&gt;&lt;span&gt;&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;MessagingCenter.&lt;/span&gt;&lt;span&gt;Unsubscribe&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;SomeEvent2&lt;/span&gt;&lt;span&gt;&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Let’s take a look at our mediator in action for this scenarios&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyViewModel&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;IEventHandler&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;SomeEvent1&lt;/span&gt;&lt;span&gt;&gt;, &lt;/span&gt;&lt;span&gt;IEventHandler&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;SomeEvent2&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyViewModel&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;IMediator&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mediator&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// no need to unsubscribe&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;mediator.&lt;/span&gt;&lt;span&gt;Publish&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SomeEvent1&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;mediator.&lt;/span&gt;&lt;span&gt;Publish&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SomeEvent2&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;problem-3---strongly-typed-navigation-with-strongly-typed-arguments&quot;&gt;Problem #3 - Strongly Typed Navigation with Strongly Typed Arguments&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Our amazing friends over in Prism offer the “best in class” MVVM framework.  We’ll them upsell you beyond that, but one
of their amazing features is ‘Modules’.  Modules help break up your navigation registration, services, etc.&lt;/p&gt;
&lt;p&gt;What they don’t solve is providing a strongly typed nature for this stuff (not their job though).  We think we can help
addon to their beautiful solution.&lt;/p&gt;
&lt;p&gt;A normal call to a navigation service might look like this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;_navigationService.&lt;/span&gt;&lt;span&gt;NavigateAsync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;MyPage&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;NavigationParameters&lt;/span&gt;&lt;span&gt; { { &lt;/span&gt;&lt;span&gt;&quot;MyArg&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;MyValue&quot;&lt;/span&gt;&lt;span&gt; } });&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This is great.  It works, but I don’t know the type OR argument requirements of “MyPage” without going to look it up.  In a small project
with a small dev team, this is fine.  In a large project with a large dev team, this can be difficult.&lt;/p&gt;
&lt;p&gt;Through our Shiny.Framework library we offer a GlobalNavigationService that can be used to navigate to any page in your app from anywhere, however,
for the nature of this example, we’ll pass our navigation service FROM our viewmodel through the mediator request to ensure proper scope.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;record&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyPageNavigatonRequest&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;INavigationService&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;navigator&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyArg&lt;/span&gt;&lt;span&gt;) : &lt;/span&gt;&lt;span&gt;IRequest&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyPageNavigationHandler&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;IRequestHandler&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;MyPageNavigatonRequest&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Handle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;MyPageNavigatonRequest&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cancellationToken&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; request.navigator.&lt;/span&gt;&lt;span&gt;NavigateAsync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;MyPage&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;NavigationParameters&lt;/span&gt;&lt;span&gt; { { &lt;/span&gt;&lt;span&gt;&quot;MyArg&quot;&lt;/span&gt;&lt;span&gt;, request.MyArg } });&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Now, in your viewmodel, you can do this:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyViewModel&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyViewModel&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;IMediator&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mediator&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;mediator.&lt;/span&gt;&lt;span&gt;Request&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyPageNavigatonRequest&lt;/span&gt;&lt;span&gt;(_navigationService, &lt;/span&gt;&lt;span&gt;&quot;MyValue&quot;&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Strongly typed.  No page required page knowledge from the module upfront.  The other dev team of the module can define HOW things work.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;closing&quot;&gt;Closing&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Lastly, we offer some absolutely epic middleware that you can use to do things like logging, caching, error handling, etc.&lt;br&gt;
Check out our &lt;a href=&quot;https://www.shinylib.net/client/mediator/middleware&quot;&gt;middleware documentation for more information&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Check out our overall &lt;a href=&quot;https://www.shinylib.net/client/mediator&quot;&gt;documentation&lt;/a&gt; for more information on how to use the mediator in your app.  We think you’ll love it!&lt;/p&gt;</content:encoded><category>Release</category><category>Mediator</category></item><item><title>April Mad Releases</title><link>https://www.shinylib.net/blog/april2024/</link><guid isPermaLink="true">https://www.shinylib.net/blog/april2024/</guid><pubDate>Mon, 08 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;h2 id=&quot;shiny-v33&quot;&gt;Shiny v3.3&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This release was mainly a large bugfixing release with some cool additions around Push Notifications&lt;/p&gt;
&lt;p&gt;However, we did decide to remove two modules from the Shiny offering&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Shiny.Logging.AppCenter - this was an easy one.  Microsoft is shutting down AppCenter in 2025.&lt;/li&gt;
&lt;li&gt;Shiny.SpeechRecognition - this was a tough one.  The plugin was never really that great and it was a pain to maintain.  The MAUI community toolkit recently released a plugin here that you can use.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We’ve improved the push delegate to handle MORE native stuff, more events like UnRegister to centralize your registration &amp;#x26; now the unregistration process.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.shinylib.net/release-notes/client/v30/&quot;&gt;Release Notes&lt;/a&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;apple-privacy-info---may-1-2024-deadline-is-approaching&quot;&gt;Apple Privacy Info - May 1, 2024 Deadline is Approaching&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Apple has a new privacy requirement that you must disclose what you are doing with the user’s data.  This is a requirement for all apps in the App Store.  The Shiny templates have been updated to include
a new file under the iOS platform that you can fill out to help you comply with this requirement.&lt;/p&gt;
&lt;p&gt;We’ve introduced two “guestimate” type helpers to get users rolling.  Our Shiny Templates and our &lt;a href=&quot;https://www.shinylib.net/client/appbuilder&quot;&gt;App Builder&lt;/a&gt; tool will help you generate these files.  We generate what most user app requirements
will be in terms of user identity info, as well as all of the necessary Shiny &amp;#x26; .NET BCL requirements.  We also insert any necessary location permissions if you select one of our location based components.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;shiny-templates-v25&quot;&gt;Shiny Templates v2.5&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The templates have been great for Shiny discovery.  Wiring up all of the permissions with Android &amp;#x26; iOS is difficult.  I find that I’m constantly using
&lt;a href=&quot;https://www.shinylib.net/client/appbuilder&quot;&gt;App Builder&lt;/a&gt; here on shinylib.net to generate them all for me because I constantly forget them all.  Cutting a new proof-of-concept or test with
Shiny or any of the whack of 3rd party libraries that are included with the library is a breeze.&lt;/p&gt;
&lt;p&gt;This release got the following love:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Removal of AppCenter - maybe this is the opposite of love - but don’t use it since it is officially done in 2025&lt;/li&gt;
&lt;li&gt;Updated to the latest versions of many nuget packages&lt;/li&gt;
&lt;li&gt;Updated some of the Shiny stuff to 3.3&lt;/li&gt;
&lt;li&gt;NEW base template for the upcoming Apple Privacy Manifests under Platforms/iOS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The MAUI Project template is so large that it takes 3 screenshots to get it all.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.shinylib.net/images/template_1.png&quot; alt=&quot;1&quot;&gt;
&lt;img src=&quot;https://www.shinylib.net/images/template_2.png&quot; alt=&quot;2&quot;&gt;
&lt;img src=&quot;https://www.shinylib.net/images/template_3.png&quot; alt=&quot;3&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/shinyorg/templates&quot;&gt;Check It Out&lt;/a&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;push-notification-end-to-end-tester-for-azure-notification-hubs--firebase&quot;&gt;Push Notification End-to-End Tester for Azure Notification Hubs &amp;#x26; Firebase&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Setting up push notifications with Apple &amp;#x26; Google can be a real pain. This tool will help you test your setup end-to-end.  It’s a
simple mobile app that will send a push notification to your app using Azure Notification Hubs or Firebase Cloud Messaging.  It’s a great way to test your setup without having to write a bunch of code.&lt;/p&gt;
&lt;p&gt;It isn’t pretty, but it does a good job of helping you test your setup without having to wire up a backend or write a bunch of code.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/shinyorg/pushtester&quot;&gt;GitHub&lt;/a&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;firebase---specifically-ios&quot;&gt;Firebase - Specifically iOS&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;There is work being done to create fresh new slim bindings for Firebase on iOS.  I’m working with Jon Dick on some SLIM Bindings over at &lt;a href=&quot;https://github.com/Redth/DotNet.Platform.SlimBindings&quot;&gt;https://github.com/Redth/DotNet.Platform.SlimBindings&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Slim bindings are revolutionary by any means, but they allow you to control the native API surface that you have to bind to.  With Swift becoming increasingly popular and not having a true native binding solution with
.NET at this time, Slim bindings allow us to control this narrative by building swift code, but still marking the code properly with objective-C headers to be able to bind within .NET&lt;/p&gt;
&lt;p&gt;If you’re a user of Shiny.Push.FirebaseMessaging, the good news is that there will be a direct nuget package update in v3 that will allow you to continue using Firebase messaging on iOS without any other code updates.  This update
for Shiny will be available long before the Firebase June 2024 deadline&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;shiny-40-roadmap&quot;&gt;Shiny 4.0 Roadmap&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;We are starting to plan out Shiny 4.0.  We are looking for feedback on what you would like to see in the next major version of Shiny.&lt;/p&gt;
&lt;p&gt;Currently on the roadmap are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Simplifying the Job framework&lt;/li&gt;
&lt;li&gt;Allow for more native configuration of Push &amp;#x26; Local Notifications&lt;/li&gt;
&lt;li&gt;More BLE methods to help with some of the fun edge cases that have been rolling in&lt;/li&gt;
&lt;li&gt;Removing the old Xamarin/Netstandard targets.  This will help with new targets moving forward&lt;/li&gt;
&lt;li&gt;Continue to improve the hosting model to work with other platform vendors like Uno&lt;/li&gt;
&lt;li&gt;Trying to get RX out of the core for those that don’t want the fat (MAYBE)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So it’s a “smaller” major release.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;bonus---acr-user-dialogs&quot;&gt;Bonus - ACR User Dialogs&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;There are still people using my old dinosaur of a package, so I updated it to the latest AndroidX stuff and .NET 8.&lt;br&gt;
Unfortunately, classic Xamarin and netstandard targets are now gone, but at least it pushes the needle forward if you’re still using
this package.&lt;/p&gt;
&lt;p&gt;We may introduce Windows support back into the mix in the future, but it’s not a priority right now.&lt;/p&gt;
&lt;a href=&quot;https://www.nuget.org/packages/Acr.UserDialogs&quot; target=&quot;_NEWWINDOW&quot;&gt;&lt;img src=&quot;https://img.shields.io/nuget/v/Acr.UserDialogs?style=for-the-badge&amp;#x26;logo=nuget&quot;&gt;&lt;/a&gt;</content:encoded><category>Release</category></item><item><title>Version 3.2 Release</title><link>https://www.shinylib.net/blog/v32/</link><guid isPermaLink="true">https://www.shinylib.net/blog/v32/</guid><pubDate>Sun, 10 Dec 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Today, we released version 3.2 of Shiny that targets .NET 8 along with Classic Xamarin targets.  We decided to move with the supported version of MAUI.  This also makes things easier
for specific support targets with AndroidX which can be very difficult to multitarget with.&lt;/p&gt;
&lt;p&gt;This release also includes the ability to check for the current setup permission without requesting from the permission from the user.  This is surprisingly difficult to implement for all modules, so for
now, the following libraries have this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Shiny.Locations / IGeofenceManager.CurrentAccess&lt;/li&gt;
&lt;li&gt;Shiny.Locations / IGeofenceManager.GetCurrentAccess(GpsRequest)&lt;/li&gt;
&lt;li&gt;Shiny.BluetoothLE / IPeripheralManager.CurrentAccess&lt;/li&gt;
&lt;li&gt;Shiny.BluetoothLE.Hosting / IBleHostingManager.CurrentAdvertisingAccess &amp;#x26; IBleHostingManager.CurrentGattAccess&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are other minor fixes in this release.  Be sure to checkout our &lt;a href=&quot;https://www.shinylib.net/release-notes/client/v30&quot;&gt;full release notes&lt;/a&gt;&lt;/p&gt;</content:encoded><category>Release</category></item><item><title>Version 3 Release</title><link>https://www.shinylib.net/blog/v3/</link><guid isPermaLink="true">https://www.shinylib.net/blog/v3/</guid><pubDate>Tue, 05 Sep 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We took our time, but we feel version 3 touched everything and we did it in our free time, so its a pretty huge release.  Everything from new
platforms, new features, new architecture, smaller footprints and new documentation.  We hope you like it.&lt;/p&gt;
&lt;p&gt;There’s a ton to talk about and we’ll only scratch the surface here.  Be sure to check out our Shiny NEW &lt;a href=&quot;https://shinylib.net&quot;&gt;Shiny documentation&lt;/a&gt; for more information.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;features&quot;&gt;Features&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;maui-support--net-7--mac-catalyst&quot;&gt;MAUI Support + .NET 7 + Mac Catalyst&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;One of our biggest features was our own hosting framework to allow Shiny to plugin into any of the .NET ecosystem including classic Xamarin targets because even some of my
applications aren’t moved yet.  We’ve also added Mac Catalyst support to almost all of the Shiny modules.&lt;/p&gt;
&lt;p&gt;Obviously, our prime target with v3 though… was .NET MAUI.  .NET MAUI makes it easier than ever to get going with Shiny or any other ecosystem plugin.&lt;/p&gt;
&lt;p&gt;To get Shiny working with MAUI, install Shiny.Hosting.Maui from nuget and add the following to your MauiProgram.cs&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Shiny&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;namespace&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyApp&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MauiProgram&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MauiApp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CreateMauiApp&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; MauiApp&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;CreateBuilder&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;UseMauiApp&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// this is the important line - this wires in all of the lifecycle and base services&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;UseShiny&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ConfigureFonts&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;fonts&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;fonts.&lt;/span&gt;&lt;span&gt;AddFont&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;OpenSans-Regular.ttf&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;OpenSansRegular&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;fonts.&lt;/span&gt;&lt;span&gt;AddFont&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;OpenSans-Semibold.ttf&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;OpenSansSemibold&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// now add your Shiny services by installing the module nugets and running their setup&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// ie. builder.Services.AddBleClient();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; builder.&lt;/span&gt;&lt;span&gt;Build&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;http-transfers&quot;&gt;HTTP Transfers&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;HTTP Transfers is honestly, one of the most key components to most apps these days.  Any apps that are sending photos, videos, or other large files need to be able to do this in the background as users tend to
“fire and forget” (add the photo, return to home screen).  I’m also surprised how many apps do downloads these days, but show indeterministic spinners…!?  Let me know how long you think things are going to take and how much
progress has been made.  This is another mechanism that is available as part of v3.&lt;/p&gt;
&lt;p&gt;As much as you would like to just open up an HTTP client, download a file or upload a stream to a directory… it doesn’t quite work like that on native platforms.  iOS &amp;#x26; Android
will both kill your transfer within seconds of the app going to the background… unless you play by the rules.  On iOS, you hand over to a native process to let it do the hard work.&lt;br&gt;
On Android, well… you get the HTTP client, but you need to do something to keep it alive.  There is a built in download manager in Android, but it’s old and doesn’t offer a ton of features, so
we ditched it.&lt;/p&gt;
&lt;p&gt;Let’s take a look at how to setup just basic background transfer for now.  We’ll cover other features in the docs and future blog posts:&lt;/p&gt;
&lt;p&gt;First create your delegate&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;namespace&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;YourNamespace&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;partial&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyHttpTransferDelegate&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;Shiny&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Net&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Http&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;IHttpTransferDelegate&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OnCompleted&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;HttpTransferRequest&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// do something when the transfer completes - send a notification, call the server, etc&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OnError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;HttpTransferRequest&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;Exception&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ex&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// check the error, maybe retry?&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ANDROID&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;partial&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyHttpTransferDelegate&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;Shiny&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;IAndroidForegroundServiceDelegate&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ConfigureNotification&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;AndroidX&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Core&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NotificationCompat&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Builder&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SetContentTitle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;MyApp&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SetContentText&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;My App is sending images&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SetSmallIcon&lt;/span&gt;&lt;span&gt;(Resource.Mipmap.myNotificationIconWhateverThatIs);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;endif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;aside aria-label=&quot;Note&quot;&gt;&lt;p aria-hidden=&quot;true&quot;&gt;Note&lt;/p&gt;&lt;div&gt;&lt;p&gt;See that partial and the #IF ANDROID ?.  That’s there so you can customize the Android foreground notification (that must exist).
This same technique is applied to GPS and Beacon delegates&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Next, add it to your MAUI app builder or host builder setup&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;builder.Services.&lt;/span&gt;&lt;span&gt;AddHttpTransfers&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;MyHttpTransferDelegate&lt;/span&gt;&lt;span&gt;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Finally, queue up your transfer(s) for upload or download - let them carry on in the background without a worry&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Shiny&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Net&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Transfers&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;IHttpManager&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;manager&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// resolve it, inject it, etc&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; manager.&lt;/span&gt;&lt;span&gt;Queue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;your identifer - must be unique&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;https://myserver.com/api/transfer&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;// true for upload&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;path &lt;/span&gt;&lt;span&gt;// local file path of WHAT to upload OR WHERE to download to&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;gps--geofencing&quot;&gt;GPS &amp;#x26; Geofencing&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;MAUI/Xamarin Essentials are great.  They hit all of the platforms.  Once you get past the essentials though and get into big enterprise applications,
you need some stronger issues.  The only provider currently offering full blown background GPS tracking for Android &amp;#x26; iOS on .NET.&lt;/p&gt;
&lt;p&gt;Geofencing hasn’t changed much from v3 API wise and still continues to be the only known geofence provider for .NET mobile.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;bluetoothle&quot;&gt;BluetoothLE&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;With BLE, we reevaluated the entire API surface for simplicity while shedding some of the fat.&lt;/p&gt;
&lt;p&gt;First off, When working with a peripheral, all methods are fired from the peripheral instead of digging down into the service and characterisitic objects.  Undernearth the hood, we can
keep track of all the characteristics properly across connections which is notoriously a difficult problem with BLE.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;IPeripheral&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;peripheral&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// scan it, select it, etc&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;peripheral.&lt;/span&gt;&lt;span&gt;GetCharacteristic&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Known Service UUID&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;Known Characteristic UUID&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;Subscribe&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;peripheral.&lt;/span&gt;&lt;span&gt;WriteCharacteristic&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Known Service UUID&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;Known Characteristic UUID&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;byte&lt;/span&gt;&lt;span&gt;[] { &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt; }).&lt;/span&gt;&lt;span&gt;Subscribe&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;peripheral.&lt;/span&gt;&lt;span&gt;ReadCharacteristic&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Known Service UUID&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;Known Characteristic UUID&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;Subscribe&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;As part of our simplified API, we’ve also introduced many Task/Async based methods where you don’t need the full power of reactive extensions.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;AccessState&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;RequestAccessAsync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IBleManager&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;manager&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ConnectAsync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IPeripheral&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;peripheral&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ConnectionConfig&lt;/span&gt;&lt;span&gt;? &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cancelToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;TimeSpan&lt;/span&gt;&lt;span&gt;? &lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WaitForCharacteristicSubscriptionAsync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IPeripheral&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;peripheral&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;serviceUuid&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;characteristicUuid&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;IReadOnlyList&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BleServiceInfo&lt;/span&gt;&lt;span&gt;&gt;&gt; &lt;/span&gt;&lt;span&gt;GetServicesAsync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IPeripheral&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;peripheral&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cancelToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BleServiceInfo&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;GetServiceAsync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IPeripheral&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;peripheral&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;serviceUuid&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cancelToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;IReadOnlyList&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BleCharacteristicInfo&lt;/span&gt;&lt;span&gt;&gt;&gt; &lt;/span&gt;&lt;span&gt;GetCharacteristicsAsync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IPeripheral&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;peripheral&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;serviceUuid&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cancelToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;TimeSpan&lt;/span&gt;&lt;span&gt;? &lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BleCharacteristicInfo&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;GetCharacteristicAsync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IPeripheral&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;peripheral&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;serviceUuid&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;characteristicUuid&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cancelToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;IReadOnlyList&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BleCharacteristicInfo&lt;/span&gt;&lt;span&gt;&gt;&gt; &lt;/span&gt;&lt;span&gt;GetAllCharacteristicsAsync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IPeripheral&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;peripheral&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cancelToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;TimeSpan&lt;/span&gt;&lt;span&gt;? &lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;IReadOnlyList&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BleDescriptorInfo&lt;/span&gt;&lt;span&gt;&gt;&gt; &lt;/span&gt;&lt;span&gt;GetDescriptorsAsync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IPeripheral&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;peripheral&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;serviceUuid&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;characteristicUuid&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cancelToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BleCharacteristicResult&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;WriteCharacteristicAsync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IPeripheral&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;peripheral&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;serviceUuid&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;characteristicUuid&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;byte&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;withResponse&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cancelToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;timeoutMs&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3000&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BleCharacteristicResult&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;ReadCharacteristicAsync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IPeripheral&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;peripheral&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;serviceUuid&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;characteristicUuid&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cancelToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;timeoutMs&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3000&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BleDescriptorResult&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;ReadDescriptorAsync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IPeripheral&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;peripheral&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;serviceUuid&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;characteristicUuid&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;descriptorUuid&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cancelToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;timeoutMs&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3000&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BleDescriptorResult&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;WriteDescriptorAsync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IPeripheral&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;peripheral&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;serviceUuid&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;characteristicUuid&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;descriptorUuid&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;byte&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cancelToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;timeoutMs&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3000&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;DeviceInfo&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;ReadDeviceInformationAsync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IPeripheral&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;peripheral&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cancelToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;timeoutMs&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3000&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;ReadRssiAsync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IPeripheral&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;peripheral&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cancelToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;timeoutMs&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3000&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BleCharacteristicResult&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;ReadCharacteristicAsync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IPeripheral&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;peripheral&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;BleCharacteristicInfo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;info&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cancelToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;timeoutMs&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3000&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BleCharacteristicResult&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;WriteCharacteristicAsync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IPeripheral&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;peripheral&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;BleCharacteristicInfo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;info&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;byte&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;withoutResponse&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cancelToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;timeoutMs&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3000&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;IReadOnlyList&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BleDescriptorInfo&lt;/span&gt;&lt;span&gt;&gt;&gt; &lt;/span&gt;&lt;span&gt;GetDescriptorsAsync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IPeripheral&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;peripheral&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;BleCharacteristicInfo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;info&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cancelToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BleDescriptorResult&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;WriteDescriptorAsync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IPeripheral&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;peripheral&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;BleDescriptorInfo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;info&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;byte&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cancelToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;timeoutMs&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3000&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;BleDescriptorResult&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;ReadDescriptorAsync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IPeripheral&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;peripheral&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;BleDescriptorInfo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;info&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cancelToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;timeoutMs&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3000&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;You’ll notice all of the methods above use strings for UUID arguments.  In version 2, we used GUIDs which made it harder for users to work with 16bit UUIDs.&lt;/p&gt;
&lt;p&gt;And lastly, in version 2, we have the Managed Peripheral that took care of things like reconnecting, rehooking characteristics, and funky stuff that generally makes BLE hard.  This is now
all built into the regular peripheral.  As long as you hook a characteristic, we’ll restore it across connection blips and disconnections.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;IPeripheral&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;peripheral&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// scan it, select it, etc&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sub&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; peripheral&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NotifyCharacteristic&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Known Service UUID&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;Known Characteristic UUID&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Subscribe&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// do something with args&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;args.Characteristic &lt;/span&gt;&lt;span&gt;// characteristic info&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;args.Data &lt;/span&gt;&lt;span&gt;// byte array containing the data&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// make sure to keep a reference to the subscription and dispose when you&apos;re done&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sub.&lt;/span&gt;&lt;span&gt;Dispose&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;BEFORE - you would have had to monitor the IPeripheral.WhenConnected(), then get the characteristic, and lastly hook it.  This sucked and it wasn’t without its pain.&lt;/p&gt;
&lt;p&gt;We’ve also taken the time to review and remove a lot of junk code that just wasn’t needed in our efforts to reduce our footprint.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;bluetoothle-hosting&quot;&gt;BluetoothLE Hosting&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;We’ve made all of our events async which is a big deal in the fact that everything is async these days.  We also added a new “managed model” that makes setting up a hosted characteristic dead simple.  Check it out:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;BleGattCharacteristic&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;My Service UUID&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;My Characteristic UUID&quot;&lt;/span&gt;&lt;span&gt;)]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyManagedCharacteristics&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;BleGattCharacteristic&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyManagedCharacteristics&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// DI all the things&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OnStart&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// need to init before the characterisitic starts?&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// start a timer that writes, you have access to the Characteristic notify here&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OnStop&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// do some cleanup?&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// override to add and advertise Read capability&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;GattResult&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;OnRead&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ReadRequest&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// override to add and advertise Write capability&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OnWrite&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;WriteRequest&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// override to add and advertise Notify capability&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;OnSubscriptionChanged&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;IPeripheral&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;peripheral&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;subscribed&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// this is a special method that puts a write request and emits a notify response - do not override write if you use this&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;GattResult&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;Request&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;WriteRequest&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// in your MauiProgram.cs or hot build registration&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;builder.Services.&lt;/span&gt;&lt;span&gt;AddBleHostedCharacteristic&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;MyManagedCharacteristics&lt;/span&gt;&lt;span&gt;&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// now inject/resolve the Shiny.BluetoothLE.Hosting.IBleHostingManager and now you can toggle on/off&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Shiny&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;BluetoothLE&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Hosting&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;IBleHostingManager&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;hostingManager&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (hostingManager.IsRegisteredServicesAttached)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;hostingManager.&lt;/span&gt;&lt;span&gt;DetachRegisteredServices&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;else&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; hostingManager.&lt;/span&gt;&lt;span&gt;AttachRegisteredServices&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;periodic-job&quot;&gt;Periodic Job&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Periodic Jobs as a whole looks the same, but under the hood - we’ve made a bunch of improvements to cleaning out old jobs (type was deleted or moved will now remove the job) and ensuring system
jobs are registered “fresh” every time to ensure consistency.  We’ve also made jobs work much like our &lt;a href=&quot;https://www.shinylib.net/client/other/statefulservices&quot;&gt;stateful services&lt;/a&gt; where you can make your job look like a viewmodel
and have its state persisted across app restarts and runs with nothing more than a simple get/set mvvm style.&lt;/p&gt;
&lt;p&gt;We’ve also added a base job that you can have to record things&lt;/p&gt;
&lt;p&gt;Example of stateful job&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyJob&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;Shiny&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Jobs&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Job&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MyJob&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ILogger&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;MyJob&lt;/span&gt;&lt;span&gt;&gt; &lt;/span&gt;&lt;span&gt;logger&lt;/span&gt;&lt;span&gt;) : &lt;/span&gt;&lt;span&gt;base&lt;/span&gt;&lt;span&gt;(logger) {}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;? &lt;/span&gt;&lt;span&gt;myString&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;? &lt;/span&gt;&lt;span&gt;MyString&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.myString;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ref&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.myString, value);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;protected&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;override&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Task&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Run&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CancellationToken&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cancelToken&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.MyString &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// this will now be set for every subsequent run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.MyString &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Hello World&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;push-notifications&quot;&gt;Push Notifications&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;This is one of our “smaller” modules, but we still love it.  Push providers come and go, but the underlying push mechanisms remain the same.  As such,
we’ve split the provider from the native mechansim and thus allowing you to work with the native mechanism the same way all the time, but plugging in a provider “on top” that Shiny
will call into.  Take a look at our &lt;a href=&quot;https://www.shinylib.net/client/push/providers&quot;&gt;Push Provider Documentation&lt;/a&gt; for more info.&lt;/p&gt;
&lt;p&gt;Out of the box, we obviously support native, but we also support basic features for Azure Notification Hubs and Firebase (Android Native, but also iOS)&lt;/p&gt;
&lt;p&gt;Push is small in terms of feature set.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ability to swap providers in one line of code&lt;/li&gt;
&lt;li&gt;We handle all of the underneath complexity to receive notifications &amp;#x26; manage the incoming entry (user tapped on the notification)&lt;/li&gt;
&lt;li&gt;We deal with all of the registration/deregistration of the native tokens (as well as when they update).  All of those weird corner cases are covered&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;local-notifications&quot;&gt;Local Notifications&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;We believe we offer the best of breed in local notifications for Xamarin/MAUI ecosystem.  Local notifications are more of a “bonus feature” with Shiny, but you end needing them in so many
background scenarios that we continue to build on our module.  This is actually one of the more feature rich modules due to the vast amount of features (and complexities) in the native notification
API surfaces.&lt;/p&gt;
&lt;p&gt;In v3, we added a bunch of new features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Geofencing Notifications for Android &amp;#x26; iOS&lt;/li&gt;
&lt;li&gt;Repeat Interval Notifications - Great for reminders&lt;/li&gt;
&lt;li&gt;Android 13 Permissions&lt;/li&gt;
&lt;li&gt;On Android, we moved off our job engine and on to the Android alarm manager to allow for precision times on notifications&lt;/li&gt;
&lt;li&gt;Native Arguments on iOS &amp;#x26; Android (great in multitarget projects) - Honestly… there is so many argument differences between iOS &amp;#x26; Android, we had to do this.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre dir=&quot;ltr&quot;&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Shiny&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Notifications&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;INotificationManager&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;manager&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// inject, resolve, etc&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;IOS&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MACCATALYST&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;manager.&lt;/span&gt;&lt;span&gt;Send&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AppleNotification&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Subtitle &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Nice subtitle&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;RelevanceScore &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1.0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;elif&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ANDROID&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;manager.&lt;/span&gt;&lt;span&gt;Send&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AndroidNotification&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Ticker &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Ticker value&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;UseBigTextStyle &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;endif&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;app-background-centric-logging&quot;&gt;App Background Centric Logging&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Nothing special here - just the entire way you log your backend.  Background services, unlike foreground debugging, are hard to hit.  Thus, you need to write to an online service like
AppCenter or perhaps a local SQLite database so you can read it back later.  We built our loggers against the excellent Microsoft.Extensions.Logging.&lt;/p&gt;
&lt;p&gt;Our AppCenter logger hasn’t changed at all for v3, but we added Shiny.Logging.SQLite for local test logging.  Our templates include a page for reading the SQLite logs when you install the SQLite logger.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;app-centric-logging&quot;&gt;App Centric Logging&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Shiny.Extensions.Configuration adds platform embedded JSON configuration to the Microsoft.Extensions.Configuration library.  What’s really cool for this library is that you can configure for all or on a per platform basis.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AppSettings.json - used by ALL platforms&lt;/li&gt;
&lt;li&gt;AppSettings.apple.json - used by iOS &amp;#x26; Mac Catalyst&lt;/li&gt;
&lt;li&gt;AppSettings.ios.json - iOS only&lt;/li&gt;
&lt;li&gt;AppSettings.maccatalyst.json - Mac Catalyst only&lt;/li&gt;
&lt;li&gt;AppSettings.android.json - Android only&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;other-features&quot;&gt;Other Features&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;We still have our speech-to-text module (Shiny.SpeechRecognition), but now that this exists in the Maui.CommunityToolkit, we will be deprecating this module in the future.
We also have our iBeacon library.  We’ve updated it to use Android foreground services when monitoring.  For the most part, Beacons seem to be dying off.  We aren’t deprecating
beacons at this time since it isn’t a ton of work to keep around.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;architecture&quot;&gt;Architecture&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A nasty comment we hear from the “not so nice” consumer base is how “Shiny tries to do too much”.  This actually isn’t true.  Our Core in v2 did have a lot of support functionality for our modules, but it was trimmed when
it wasn’t needed.  We’ve taken this a step further by separating support modules out to secondary libraries and linking them into modules where they are needed.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;hosting-model&quot;&gt;Hosting Model&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Our Core module now only contains our truly core functionality including hosting.  Some will ask, well why hosting if MAUI already has it?  Well - the .NET application ecosystem is fragmented right now.  Some users are stuck on Xamarin Forms,
Xamarin Full Native, some are moving to MAUI, and some are even looking at newer pastures like &lt;a href=&quot;https://platform.uno/&quot;&gt;Uno Platform&lt;/a&gt;.  Our hosting model allows us to plugin into this different ecosystems and gives us 1 plugin base to rule them all.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;dependency-injection-all-the-way&quot;&gt;Dependency Injection All The Way&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;We believe strongly in dependency injection for clean architecture.  Many users have certain logging and configuration needs, along with their own sets of services
that they make use of in Shiny delegates.  This would be very hard to enable without dependency injection.  It allows for a very pluggable &amp;#x26; testable model.  We realize some don’t like this, but this is
unfortunately a hill we will die on.  For those places where DI can’t reach, Shiny has Shiny.Hosting.Host.GetService for you.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;reactive-extensions&quot;&gt;Reactive Extensions&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Core parts of Shiny are built with Reactive programming especially our BluetoothLE module where things like configurable timeout, method chaining, and event structure matter.
We understand this can sometimes present new paradigms to users, so we add async equivalent methods where applicable.  Please review our BluetoothLE feature set above.&lt;/p&gt;
&lt;p&gt;Some won’t like Reactive programming and that’s fine - as with any free open source, you can use other libraries.  We don’t like events, they leak memory and lack functionality that is easily
available in RX.  Even the C# inventors have said they regret events.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;documentation&quot;&gt;Documentation&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;We have new documentation - you’re in it already :)  We think its starting to look pretty awesome.  We’ve built it using the amazing &lt;a href=&quot;https://astro.build&quot;&gt;Astro Build&lt;/a&gt; for those who are curious.&lt;/p&gt;
&lt;p&gt;There will be some gaps at the time of our release, but we continue to improve things.  Feel free to contribute as we can use all the help we can get!&lt;/p&gt;
&lt;p&gt;Have an existing app and just need to get a list of what configuration you need to plug Shiny into your app, take a look at our new
&lt;a href=&quot;https://www.shinylib.net/client/appbuilder&quot;&gt;App Builder&lt;/a&gt; that will give a list of every piece of boilerplate you will need to add.  Simply select the Shiny libraries you want and let it show you the rest.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;templates&quot;&gt;Templates&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Our templates make starting net new apps a breeze with support for many popular Xamarin/MAUI 3rd party libraries including Shiny.  Our template
takes care of setting up ALL of the boilerplate plists, manifests, projects, etc and works for Visual Studio for Windows &amp;#x26; Mac (2022)&lt;/p&gt;
&lt;p&gt;To Install - simply run the following from command line&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;dotnet new install Shiny.Templates&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Check out all of the options in this monster
&lt;img src=&quot;https://www.shinylib.net/images/template_vs4mac.png&quot; alt=&quot;Template&quot;&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;samples&quot;&gt;Samples&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Our samples do fall under the “kitchen sink” category, but we cover almost everything you can do with the library.  We use Prism &amp;#x26; ReactiveUI within our samples, but you don’t need them.  It just makes life
easier for us to build the samples, but you don’t need them to use Shiny.  Our samples also include some basic APIs to show you how to use ASP.NET Core to receive and send HTTP transfers.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/shinyorg/shiny/tree/master/samples&quot;&gt;Check out our Samples&lt;/a&gt;&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-road-ahead-roadmap&quot;&gt;The Road Ahead (Roadmap)&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;We have a ton of modules that we haven’t released to the wild and are considering bringing out piece by piece.  Shiny was a platform to build on top as our its modules.&lt;/p&gt;
&lt;p&gt;We have already started work on WebAssembly along with some preliminary work on Windows.  Windows is not a platform that I currently work on, so any contributors are certainly
welcome!&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;be-kind&quot;&gt;Be Kind&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Remember, this is all given away for free. Don’t like something, offer constructive and POLITE feedback.  Please also understand that feedback doesn’t mean we’ll change something, but
we want to make something MOST people will like.  I also make mistakes, so bugs, oversights, missed features will happen!&lt;/p&gt;
&lt;p&gt;Have some of that constructive &amp;#x26; polite feedback - go here and let us know &lt;a href=&quot;https://github.com/shinyorg/shiny/issues/new/choose&quot;&gt;GitHub Issues&lt;/a&gt;&lt;/p&gt;</content:encoded><category>Release</category></item></channel></rss>