<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Jan Hjørdie</title>
    <description>The latest articles on Forem by Jan Hjørdie (@janhjordie).</description>
    <link>https://forem.com/janhjordie</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%2F851201%2F328eb895-6bc9-4ae2-8048-5894c493b9be.jpeg</url>
      <title>Forem: Jan Hjørdie</title>
      <link>https://forem.com/janhjordie</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/janhjordie"/>
    <language>en</language>
    <item>
      <title>Integrating Campaign Monitor Smart Emails (Transactional) — Minimal Guide</title>
      <dc:creator>Jan Hjørdie</dc:creator>
      <pubDate>Tue, 25 Nov 2025 13:58:42 +0000</pubDate>
      <link>https://forem.com/janhjordie/integrating-campaign-monitor-smart-emails-transactional-minimal-guide-4dj7</link>
      <guid>https://forem.com/janhjordie/integrating-campaign-monitor-smart-emails-transactional-minimal-guide-4dj7</guid>
      <description>&lt;p&gt;This short guide describes a minimal, robust integration pattern for sending Campaign Monitor Smart (transactional) emails from a .NET application. It documents practical pitfalls and how the example &lt;code&gt;CampaignMonitorService&lt;/code&gt; addresses them.&lt;/p&gt;

&lt;p&gt;Note: all examples below use placeholders (no real API keys, email addresses, or list IDs).&lt;/p&gt;




&lt;h2&gt;
  
  
  Authentication
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Campaign Monitor uses Basic Auth for all API calls. Use the API key as the username and any non-empty string (for example &lt;code&gt;x&lt;/code&gt;) as the password.&lt;/li&gt;
&lt;li&gt;There is no separate transactional API key; the same key used for classic APIs also works for transactional Smart Email endpoints.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example header (curl style):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# placeholder values&lt;/span&gt;
&lt;span class="nv"&gt;API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;YOUR_API_KEY&amp;gt;
&lt;span class="nv"&gt;SMART_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;YOUR_SMART_EMAIL_ID&amp;gt;

curl &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$API_KEY&lt;/span&gt;&lt;span class="s2"&gt;:x"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"To":["&amp;lt;recipient_placeholder&amp;gt;"],"Data":{"Subject":"Test"},"ConsentToTrack":"Yes"}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://api.createsend.com/api/v3.3/transactional/smartEmail/&lt;span class="nv"&gt;$SMART_ID&lt;/span&gt;/send
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key implementation notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Authentication precedence: prefer an explicit transactional API key if present, otherwise fall back to the general API key or the first configured client key.&lt;/li&gt;
&lt;li&gt;Base URL normalization: some environments reference &lt;code&gt;transact.createsend.com&lt;/code&gt; which can cause TLS subjectAltName mismatches. Normalize transactional calls to &lt;code&gt;api.createsend.com&lt;/code&gt; to avoid local TLS errors.&lt;/li&gt;
&lt;li&gt;Development TLS helper: a &lt;code&gt;SkipCertificateValidation&lt;/code&gt; option can be enabled only in development to bypass name-mismatch TLS errors. Do not enable in production.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Smart Email payload shape
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Smart Email sends are POSTs to &lt;code&gt;/transactional/smartEmail/{SMART_ID}/send&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The JSON payload should contain:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;To&lt;/code&gt;: array of recipient addresses&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Data&lt;/code&gt;: object with variables available to the Smart Email template (Liquid)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ConsentToTrack&lt;/code&gt;: commonly required by templates/accounts (values: &lt;code&gt;"Yes"&lt;/code&gt; or &lt;code&gt;"No"&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Example payload (placeholder values):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"To"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;recipient_placeholder&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Subject"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My Subject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"EventsList"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;html string&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ConsentToTrack"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Yes"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Sending strategy implemented in &lt;code&gt;CampaignMonitorService&lt;/code&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;SendTransactionalEmailWithEventsAsync&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If a Smart Email id is supplied (or configured globally), it sends to the Smart Email transactional endpoint using the &lt;code&gt;Data&lt;/code&gt; payload.&lt;/li&gt;
&lt;li&gt;If no Smart Email id exists, it falls back to a legacy transactional API path.&lt;/li&gt;
&lt;li&gt;The method handles empty response bodies (202 Accepted) and attempts to parse returned JSON when available.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;code&gt;SendTransactionalEmailWithEventsToListAsync&lt;/code&gt; (bulk list send):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pages the classic API endpoint &lt;code&gt;lists/{listId}/active.json&lt;/code&gt; to collect active subscribers.&lt;/li&gt;
&lt;li&gt;De-duplicates recipients and chunks them into batches (default 200 recipients per batch).&lt;/li&gt;
&lt;li&gt;Sends each chunk as a separate Smart Email request.&lt;/li&gt;
&lt;li&gt;Returns a summary response containing counts of sent/failed batches and the last raw response body for troubleshooting.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Robust subscriber parsing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Some Campaign Monitor responses contain fields in unexpected formats. A common example is the &lt;code&gt;Date&lt;/code&gt; field in list subscribers: it may be an empty string or a non-ISO value.&lt;/li&gt;
&lt;li&gt;To avoid deserialization failures, parse the &lt;code&gt;Results&lt;/code&gt; array manually and only attempt to parse &lt;code&gt;Date&lt;/code&gt; when the value is non-empty and parseable. Malformed or empty dates should be treated as &lt;code&gt;null&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  HTML generation for list data
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;If you need to provide rendered HTML to a Smart Email template (for example a generated events list), produce a small, escaped HTML fragment (for example a &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; with encoded text) and pass it as a Liquid variable in &lt;code&gt;Data&lt;/code&gt; (the example uses &lt;code&gt;EventsList&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Batching, throttling and retries
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Default batch size in the example is 200 recipients. This keeps payloads manageable.&lt;/li&gt;
&lt;li&gt;If you observe 429 or 5xx responses, add a delay between batches (or exponential backoff) and/or retries for failed batches.&lt;/li&gt;
&lt;li&gt;Log batch-level results to spot systemic failures and to decide if retrying is appropriate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simple backoff example (concept):&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;// between batches&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromMilliseconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// implement retry with exponential backoff for failed batch responses&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Logging and diagnostics
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Log which credential source is used (but mask keys in logs).&lt;/li&gt;
&lt;li&gt;Log the transactional client's Authorization header presence (mask preview) and the target BaseAddress.&lt;/li&gt;
&lt;li&gt;Preserve &lt;code&gt;RawBody&lt;/code&gt; of responses when present to aid debugging.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Example (C#) — sanitized
&lt;/h2&gt;

&lt;p&gt;Below is a minimal example showing how a caller might use the helper that sends to a list. All values are placeholders.&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;// assumed injected: CampaignMonitorService cmService&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Enumerable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;CampaignMonitorService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EventInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Today&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddDays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;$"Event &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;$"Desc &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&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;listId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;LIST_ID_PLACEHOLDER&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;cmService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendTransactionalEmailWithEventsToListAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;listId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;listId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Upcoming Events"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;smartEmailId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// use configured default if null&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Optional plain text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;batchSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Send result: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"No recipients found or send failed."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing helpers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use a dry-run script or curl to validate payload shapes before executing real sends.&lt;/li&gt;
&lt;li&gt;Keep &lt;code&gt;ConsentToTrack&lt;/code&gt; in the payload during testing to avoid 400 errors when the template expects consent.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;This pattern and the defensive parsing/normalization steps make a Smart Email integration more resilient to real-world quirks: TLS host oddities, empty response bodies, non-standard JSON fields, and the need to send to multiple recipients in a controlled, observable way.&lt;/p&gt;

</description>
      <category>email</category>
      <category>campaign</category>
      <category>monitor</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Real Blazor WebAssembly Production Pitfalls</title>
      <dc:creator>Jan Hjørdie</dc:creator>
      <pubDate>Sat, 22 Nov 2025 20:08:04 +0000</pubDate>
      <link>https://forem.com/janhjordie/real-blazor-webassembly-production-pitfalls-3hmf</link>
      <guid>https://forem.com/janhjordie/real-blazor-webassembly-production-pitfalls-3hmf</guid>
      <description>&lt;h1&gt;
  
  
  Real Blazor WebAssembly Production Pitfalls
&lt;/h1&gt;

&lt;h3&gt;
  
  
  (With Google Maps as a Case Study)
&lt;/h3&gt;

&lt;p&gt;Blazor WebAssembly works beautifully in development — but the moment you publish a &lt;strong&gt;Release&lt;/strong&gt; build with &lt;strong&gt;PublishTrimmed=true&lt;/strong&gt;, you start seeing an entirely different runtime behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Features that worked locally stop working
&lt;/li&gt;
&lt;li&gt;External SDKs fail without console errors
&lt;/li&gt;
&lt;li&gt;JS interop calls disappear into the void
&lt;/li&gt;
&lt;li&gt;Reflection stops working
&lt;/li&gt;
&lt;li&gt;Maps, charts, and 3rd‑party UI break silently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This blog post is a &lt;strong&gt;deep, technical&lt;/strong&gt; walkthrough of the &lt;em&gt;real&lt;/em&gt; pitfalls you hit when you move Blazor WASM into production — with Google Maps (Advanced Markers + MapId/StyleId + lazy load) as a concrete case study.&lt;/p&gt;

&lt;p&gt;This is not theory.&lt;br&gt;&lt;br&gt;
This is what actually breaks when you ship.&lt;/p&gt;


&lt;h1&gt;
  
  
  1. Trimming Removes .NET Methods Used by JS Interop
&lt;/h1&gt;

&lt;p&gt;Blazor’s linker (ILLink) removes all methods and types that appear unused.&lt;br&gt;&lt;br&gt;
This includes methods you call &lt;strong&gt;from JavaScript&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  ❌ Symptom
&lt;/h3&gt;

&lt;p&gt;In Debug:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;DotNet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invokeMethodAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MyApp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OnMarkerClicked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;→ Works.&lt;/p&gt;

&lt;p&gt;In Release with trimming:&lt;br&gt;&lt;br&gt;
→ No error.&lt;br&gt;&lt;br&gt;
→ Nothing happens.&lt;br&gt;&lt;br&gt;
→ The method was removed.&lt;/p&gt;
&lt;h3&gt;
  
  
  ✔ Fix
&lt;/h3&gt;

&lt;p&gt;Mark all JS‑callable .NET methods:&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;JSInvokable&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnMarkerClicked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you rely on reflection/dynamic serializers:&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="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;DynamicDependency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DynamicallyAccessedMemberTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;All&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MyDto&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;Serialize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or root the assembly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;TrimmerRootAssembly&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"$(AssemblyName)"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  2. External Libraries Load Faster in Production
&lt;/h1&gt;

&lt;p&gt;JS bundles load &lt;em&gt;faster&lt;/em&gt; after trimming + minification.&lt;br&gt;&lt;br&gt;
This reveals race conditions you never see locally.&lt;/p&gt;
&lt;h3&gt;
  
  
  Google Maps example:
&lt;/h3&gt;

&lt;p&gt;In Debug:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your script loads
&lt;/li&gt;
&lt;li&gt;Then Google Maps loads
&lt;/li&gt;
&lt;li&gt;Then you call &lt;code&gt;AdvancedMarkerElement&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Release:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your code runs before the Maps library exists
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AdvancedMarkerElement&lt;/code&gt; is undefined
&lt;/li&gt;
&lt;li&gt;No errors from Google Maps (silent fail)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  ✔ Proper Fix (Google's recommended way)
&lt;/h3&gt;

&lt;p&gt;Use &lt;strong&gt;importLibrary()&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mapsLib&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;importLibrary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;maps&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;markerLib&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;importLibrary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;marker&lt;/span&gt;&lt;span class="dl"&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 removes &lt;strong&gt;100%&lt;/strong&gt; of all race conditions.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. JS Exceptions Are Hidden in Minified Release
&lt;/h1&gt;

&lt;p&gt;Many JavaScript errors disappear after minification.&lt;br&gt;&lt;br&gt;
Especially inside Google Maps, Stripe, and other SDKs.&lt;/p&gt;
&lt;h3&gt;
  
  
  ❌ Symptom
&lt;/h3&gt;

&lt;p&gt;Everything “just stops working” — no console errors.&lt;/p&gt;
&lt;h3&gt;
  
  
  ✔ Fix
&lt;/h3&gt;

&lt;p&gt;Wrap interop calls in try/catch and surface errors to .NET:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;safe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;JS Error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;e&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;Then call via:&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;await&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokeVoidAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"safe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"setMarkers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now errors show up again.&lt;/p&gt;




&lt;h1&gt;
  
  
  4. JSON Serialization + Trimming = Missing Properties
&lt;/h1&gt;

&lt;p&gt;If your DTOs are used dynamically — especially when loading external data (API → map markers) — trimming removes properties.&lt;/p&gt;

&lt;h3&gt;
  
  
  ❌ Symptom
&lt;/h3&gt;

&lt;p&gt;Objects arrive missing fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"lat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"lng"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ✔ Fix
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;PreserveReferencesHandling&lt;/code&gt; or &lt;code&gt;DynamicDependency&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;DynamicallyAccessedMembers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DynamicallyAccessedMemberTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PublicProperties&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;class&lt;/span&gt; &lt;span class="nc"&gt;MarkerDto&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;Or root your shared models assembly.&lt;/p&gt;




&lt;h1&gt;
  
  
  5. External APIs Fail Only in Production
&lt;/h1&gt;

&lt;p&gt;Google Maps, Stripe, Firebase, and many others check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;referrer domains&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTPS&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;billing enabled&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CORS&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CSP rules&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ❌ Symptom
&lt;/h3&gt;

&lt;p&gt;Everything works locally.&lt;br&gt;&lt;br&gt;
In prod — nothing loads.&lt;/p&gt;
&lt;h3&gt;
  
  
  ✔ Fix Checklist
&lt;/h3&gt;

&lt;p&gt;Allow these in production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;https://*.yourdomain.com/*&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;HTTPS only
&lt;/li&gt;
&lt;li&gt;CSP rules:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;script-src maps.googleapis.com maps.gstatic.com 'self'
connect-src https://maps.googleapis.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And verify billing + quotas.&lt;/p&gt;


&lt;h1&gt;
  
  
  6. “Silent Failures” From Adblockers and Privacy Extensions
&lt;/h1&gt;

&lt;p&gt;In production environments, up to 40% of users run blockers that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Block Maps scripts
&lt;/li&gt;
&lt;li&gt;Block geolocation
&lt;/li&gt;
&lt;li&gt;Block telemetry endpoints
&lt;/li&gt;
&lt;li&gt;Block Blazor's own &lt;code&gt;_framework&lt;/code&gt; files (rare, but happens)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  ✔ Fix
&lt;/h3&gt;

&lt;p&gt;Detect Maps load failure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;importLibrary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;maps&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;showFallbackMap&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;h1&gt;
  
  
  7. Hosting Pitfalls: Caching, Routing &amp;amp; Fingerprints
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Typical issues:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;CDN caching old versions of &lt;code&gt;_framework/*.wasm&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;index.html&lt;/code&gt; cached too long
&lt;/li&gt;
&lt;li&gt;404s for lazy-loaded modules
&lt;/li&gt;
&lt;li&gt;Wrong MIME types for &lt;code&gt;.dll&lt;/code&gt; and &lt;code&gt;.wasm&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ✔ Fix
&lt;/h3&gt;

&lt;p&gt;Add correct headers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_cache-control: no-store for index.html
_cache-control: immutable for .dll, .wasm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And SPA fallback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rewrite ^/(.*)$ /index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  8. Google Maps Case Study: What Was Actually Broken
&lt;/h1&gt;

&lt;p&gt;A real-world case:&lt;/p&gt;

&lt;h3&gt;
  
  
  ❌ Local debug:
&lt;/h3&gt;

&lt;p&gt;Everything works.&lt;/p&gt;

&lt;h3&gt;
  
  
  ❌ Production:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;No map
&lt;/li&gt;
&lt;li&gt;No markers
&lt;/li&gt;
&lt;li&gt;No console errors
&lt;/li&gt;
&lt;li&gt;Nothing loads&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Root Causes
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Google Maps library loaded &lt;strong&gt;after&lt;/strong&gt; app code
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AdvancedMarkerElement&lt;/code&gt; accessed before marker library existed
&lt;/li&gt;
&lt;li&gt;DTO properties trimmed away
&lt;/li&gt;
&lt;li&gt;JS → .NET callbacks trimmed
&lt;/li&gt;
&lt;li&gt;CSP blocked maps scripts&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  ✔ Final Working Solution
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Inline &lt;strong&gt;Google Maps bootstrap loader&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Lazy load via &lt;code&gt;importLibrary()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;All JS-callable .NET methods marked &lt;code&gt;[JSInvokable]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Assembly rooted
&lt;/li&gt;
&lt;li&gt;CSP + referrer config fixed
&lt;/li&gt;
&lt;li&gt;Wrapped all JS interop in safe error forwarding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;→ 100% stable in production.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Blazor WebAssembly is powerful — but once you publish with trimming, you are no longer running the same application as in Debug.&lt;/p&gt;

&lt;p&gt;If something works locally but breaks silently in prod, it is almost always one of these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trimming removed something
&lt;/li&gt;
&lt;li&gt;JS executed too early
&lt;/li&gt;
&lt;li&gt;External SDK blocked
&lt;/li&gt;
&lt;li&gt;CSP/Referrer issues
&lt;/li&gt;
&lt;li&gt;DTOs missing due to ILLink
&lt;/li&gt;
&lt;li&gt;JS exceptions swallowed by minification&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use the patterns in this post, and your WASM apps will behave &lt;em&gt;identically&lt;/em&gt; in Debug and Release.&lt;/p&gt;

</description>
      <category>blazor</category>
      <category>webassembly</category>
    </item>
  </channel>
</rss>
