<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: JuDev13</title>
    <description>The latest articles on DEV Community by JuDev13 (@judev13).</description>
    <link>https://dev.to/judev13</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3945474%2F478f0cf4-1b6b-4fd8-983a-40b30e4e4f43.png</url>
      <title>DEV Community: JuDev13</title>
      <link>https://dev.to/judev13</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/judev13"/>
    <language>en</language>
    <item>
      <title>5 Blazor Web App (.NET 10) gotchas I learned building an admin dashboard from scratch</title>
      <dc:creator>JuDev13</dc:creator>
      <pubDate>Fri, 22 May 2026 07:03:41 +0000</pubDate>
      <link>https://dev.to/judev13/5-blazor-web-app-net-10-gotchas-i-learned-building-an-admin-dashboard-from-scratch-41c8</link>
      <guid>https://dev.to/judev13/5-blazor-web-app-net-10-gotchas-i-learned-building-an-admin-dashboard-from-scratch-41c8</guid>
      <description>&lt;p&gt;I spent 3 weekends building a full admin dashboard template with Blazor Web App (.NET 10). No component library, pure C# and CSS. Here are the 5 things that tripped me up — and that I couldn't find clearly documented anywhere.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. &lt;code&gt;@typeparam T&lt;/code&gt; conflicts with the Razor source generator
&lt;/h2&gt;

&lt;p&gt;If you create a generic component with a single-letter type parameter like &lt;code&gt;T&lt;/code&gt;, you'll get a cryptic &lt;code&gt;CS0305&lt;/code&gt; error pointing at a generated &lt;code&gt;.g.cs&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error CS0305: Using the generic type 'DataGrid&amp;lt;T&amp;gt;' requires 1 type arguments
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix is simple — rename your type parameter to something longer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@* ❌ Breaks *@
@typeparam T

@* ✅ Works *@
@typeparam TItem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And update all references in the component accordingly. Not obvious from the error message at all.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. You cannot pass &lt;code&gt;RenderFragment&lt;/code&gt; to an InteractiveServer component
&lt;/h2&gt;

&lt;p&gt;This one cost me a few hours. If you try to pass &lt;code&gt;@Body&lt;/code&gt; or any &lt;code&gt;RenderFragment&lt;/code&gt; parameter to a component with &lt;code&gt;@rendermode InteractiveServer&lt;/code&gt;, you get this at runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;InvalidOperationException: Cannot pass the parameter 'Body' to component 
'MyLayout' with rendermode 'InteractiveServerRenderMode'. This is because 
the parameter is of the delegate type 'RenderFragment', which is arbitrary 
code and cannot be serialized.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The rule:&lt;/strong&gt; layouts must stay static. Only leaf components (components with no children passed as parameters) can be interactive.&lt;/p&gt;

&lt;p&gt;This means you can't put &lt;code&gt;@rendermode InteractiveServer&lt;/code&gt; on your &lt;code&gt;MainLayout&lt;/code&gt;. Instead, put it on individual page components and child components.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. &lt;code&gt;event Action?&lt;/code&gt; on a singleton service is always null
&lt;/h2&gt;

&lt;p&gt;A common pattern for notifying Blazor components from a singleton service is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ThemeService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;event&lt;/span&gt; &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;OnChange&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Toggle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;IsDark&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;IsDark&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;OnChange&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// ← this is null more often than you think&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem: if the component subscribes with &lt;code&gt;OnChange += StateHasChanged&lt;/code&gt; in &lt;code&gt;OnInitialized&lt;/code&gt;, but the layout is static (see point 2), &lt;code&gt;OnInitialized&lt;/code&gt; never fires on the client. The event stays null.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix&lt;/strong&gt; — use a &lt;code&gt;List&amp;lt;Action&amp;gt;&lt;/code&gt; with explicit Subscribe/Unsubscribe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ThemeService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_listeners&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Action&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_listeners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Unsubscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Action&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_listeners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Toggle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;IsDark&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;IsDark&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_listeners&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;l&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you full control and avoids the null event trap.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. &lt;code&gt;StateHasChanged&lt;/code&gt; from a background thread needs &lt;code&gt;InvokeAsync&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;When a singleton service notifies a component from a &lt;code&gt;Task.Delay&lt;/code&gt; or a timer callback, you're on a background thread. Calling &lt;code&gt;StateHasChanged&lt;/code&gt; directly will throw or silently do nothing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ May fail silently&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnServiceChanged&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;StateHasChanged&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Correct&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnServiceChanged&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StateHasChanged&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Also fine for fire-and-forget callbacks&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnServiceChanged&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StateHasChanged&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;async void&lt;/code&gt; is generally discouraged in C#, but for Blazor component callbacks it's acceptable — the component lifecycle handles the exceptions.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Bootstrap class names conflict with your custom components
&lt;/h2&gt;

&lt;p&gt;Blazor project templates ship with Bootstrap by default. Bootstrap defines &lt;code&gt;.toast&lt;/code&gt;, &lt;code&gt;.modal&lt;/code&gt;, &lt;code&gt;.badge&lt;/code&gt; and others — with &lt;code&gt;display: none&lt;/code&gt; or specific styling that will override or hide your custom components if you use the same class names.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* Bootstrap does this internally */&lt;/span&gt;
&lt;span class="nc"&gt;.toast&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* your toast will never show */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The fix&lt;/strong&gt; — prefix all your custom component classes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@* ❌ Conflicts with Bootstrap *@
&amp;lt;div class="toast"&amp;gt;...&amp;lt;/div&amp;gt;

@* ✅ Safe *@
&amp;lt;div class="ak-toast"&amp;gt;...&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple but easy to miss when you're focused on the component logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;These lessons came out of building &lt;strong&gt;AdminKit&lt;/strong&gt; — a Blazor Web App admin dashboard template with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generic &lt;code&gt;DataGrid&amp;lt;TItem&amp;gt;&lt;/code&gt; with sorting, search, pagination and row selection&lt;/li&gt;
&lt;li&gt;Line, Bar and Donut charts via Chart.js&lt;/li&gt;
&lt;li&gt;Toast notification system (4 levels, auto-dismiss)&lt;/li&gt;
&lt;li&gt;Generic Modal component&lt;/li&gt;
&lt;li&gt;Light/dark theme with pure CSS variables&lt;/li&gt;
&lt;li&gt;Login, Profile, Forms and 404 pages&lt;/li&gt;
&lt;li&gt;Mobile responsive with hamburger menu&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://adminkit-demonstration-exg2ezcma0gfd0dq.francecentral-01.azurewebsites.net" rel="noopener noreferrer"&gt;https://adminkit-demonstration-exg2ezcma0gfd0dq.francecentral-01.azurewebsites.net&lt;/a&gt;&lt;br&gt;&lt;br&gt;
(Login: &lt;a href="mailto:admin@demo.com"&gt;admin@demo.com&lt;/a&gt; / admin)&lt;/p&gt;

&lt;p&gt;If you're building something with Blazor and want a head start, I released it as a paid template: &lt;a href="https://dub.sh/adminkit.gr" rel="noopener noreferrer"&gt;https://dub.sh/adminkit.gr&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy to answer questions about any of these gotchas in the comments.&lt;/p&gt;

</description>
      <category>blazor</category>
      <category>dotnet</category>
      <category>csharp</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
