<?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: Polymind</title>
    <description>The latest articles on DEV Community by Polymind (@polymind).</description>
    <link>https://dev.to/polymind</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%2Forganization%2Fprofile_image%2F12885%2Fb1a89aa9-d1e5-468d-aac0-a0a83be81baa.png</url>
      <title>DEV Community: Polymind</title>
      <link>https://dev.to/polymind</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/polymind"/>
    <language>en</language>
    <item>
      <title>OpenApiWeaver: Generate Type-Safe C# Clients from OpenAPI at Compile Time with Source Generators</title>
      <dc:creator>Tatsuro Shibamura</dc:creator>
      <pubDate>Fri, 10 Apr 2026 06:59:59 +0000</pubDate>
      <link>https://dev.to/polymind/openapiweaver-generate-type-safe-c-clients-from-openapi-at-compile-time-with-source-generators-5e79</link>
      <guid>https://dev.to/polymind/openapiweaver-generate-type-safe-c-clients-from-openapi-at-compile-time-with-source-generators-5e79</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I built &lt;strong&gt;OpenApiWeaver&lt;/strong&gt;, a C# library that generates HTTP clients from OpenAPI definitions at &lt;strong&gt;compile time&lt;/strong&gt; using Source Generators. Just drop your OpenAPI file into your project, add one &lt;code&gt;ItemGroup&lt;/code&gt; entry, and build — no codegen CLI, no reflection at runtime, full NRT and &lt;code&gt;required&lt;/code&gt; support, and clean handling of string-based enums via &lt;code&gt;readonly record struct&lt;/code&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/shibayan" rel="noopener noreferrer"&gt;
        shibayan
      &lt;/a&gt; / &lt;a href="https://github.com/shibayan/openapi-weaver" rel="noopener noreferrer"&gt;
        openapi-weaver
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      OpenAPI documents into strongly typed C# HTTP clients at build time with an incremental Roslyn source generator.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;OpenApiWeaver&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/shibayan/openapi-weaver/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/shibayan/openapi-weaver/actions/workflows/ci.yml/badge.svg" alt="CI"&gt;&lt;/a&gt;
&lt;a href="https://www.nuget.org/packages/OpenApiWeaver/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/be008e002c946c2526eb9b50222631cdc5c5a6973a1c136578a914ebca2321cc/68747470733a2f2f62616467656e2e6e65742f6e756765742f64742f4f70656e417069576561766572" alt="Downloads"&gt;&lt;/a&gt;
&lt;a href="https://www.nuget.org/packages/OpenApiWeaver" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/67dba0420bd7a5429b07b735645e71c19a5dac4b3b4d14246326a87ab6105ebb/68747470733a2f2f696d672e736869656c64732e696f2f6e756765742f762f4f70656e417069576561766572" alt="NuGet"&gt;&lt;/a&gt;
&lt;a href="https://github.com/shibayan/openapi-weaver/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/d747b780724c413b5075cdcd04f89094a38a2e4d87c4623ee60b24c0786b00f5/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f736869626179616e2f6f70656e6170692d776561766572" alt="License"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;OpenApiWeaver&lt;/strong&gt; is an incremental Roslyn source generator that turns OpenAPI 3.x documents, including OpenAPI 3.2, into strongly typed C# HTTP clients at build time. No runtime code generation, no reflection - just plain C# emitted during compilation.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Quick Start&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;1. Install the package&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight highlight-text-xml notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&amp;lt;&lt;span class="pl-ent"&gt;ItemGroup&lt;/span&gt;&amp;gt;
  &amp;lt;&lt;span class="pl-ent"&gt;PackageReference&lt;/span&gt; &lt;span class="pl-e"&gt;Include&lt;/span&gt;=&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;OpenApiWeaver&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; &lt;span class="pl-e"&gt;Version&lt;/span&gt;=&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;x.y.z&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; &lt;span class="pl-e"&gt;PrivateAssets&lt;/span&gt;=&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;all&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; /&amp;gt;
&amp;lt;/&lt;span class="pl-ent"&gt;ItemGroup&lt;/span&gt;&amp;gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;2. Add your OpenAPI document&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight highlight-text-xml notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&amp;lt;&lt;span class="pl-ent"&gt;ItemGroup&lt;/span&gt;&amp;gt;
  &amp;lt;&lt;span class="pl-ent"&gt;OpenApiWeaverDocument&lt;/span&gt; &lt;span class="pl-e"&gt;Include&lt;/span&gt;=&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;openapi\petstore.yaml&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
                         &lt;span class="pl-e"&gt;ClientName&lt;/span&gt;=&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;PetstoreClient&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
                         &lt;span class="pl-e"&gt;Namespace&lt;/span&gt;=&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;Contoso.Generated&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; /&amp;gt;
&amp;lt;/&lt;span class="pl-ent"&gt;ItemGroup&lt;/span&gt;&amp;gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Use &lt;code&gt;OpenApiWeaverDocument&lt;/code&gt; rather than &lt;code&gt;AdditionalFiles&lt;/code&gt;; the package's MSBuild targets project these items into compiler inputs automatically.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Use the generated client&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight highlight-source-cs notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;var&lt;/span&gt; &lt;span class="pl-s1"&gt;client&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;new&lt;/span&gt; &lt;span class="pl-smi"&gt;PetstoreClient&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;accessToken&lt;/span&gt;&lt;span class="pl-c1"&gt;:&lt;/span&gt; &lt;span class="pl-s"&gt;"your-token"&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-c"&gt;// Operations are grouped by OpenAPI tag&lt;/span&gt;
&lt;span class="pl-k"&gt;var&lt;/span&gt; &lt;span class="pl-s1"&gt;pet&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;await&lt;/span&gt; &lt;span class="pl-s1"&gt;client&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-s1"&gt;Pets&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;GetAsync&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;petId&lt;/span&gt;&lt;span class="pl-c1"&gt;:&lt;/span&gt; &lt;span class="pl-c1"&gt;1&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/shibayan/openapi-weaver" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Why another OpenAPI client generator?
&lt;/h2&gt;

&lt;p&gt;If you've needed a C# client from an OpenAPI definition, you've probably reached for OpenAPI Generator, AutoRest, or NSwag. They all work, but each has its quirks, and I've often found myself compromising on one thing or another.&lt;/p&gt;

&lt;p&gt;What I really wanted was dead simple: &lt;strong&gt;drop an OpenAPI file in my project, and have the client appear seamlessly at build time&lt;/strong&gt; — no external tools, no checked-in generated files, no separate codegen step in CI. That's exactly what Source Generators are for, so I built &lt;strong&gt;OpenApiWeaver&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design goals
&lt;/h2&gt;

&lt;p&gt;The core idea is to push as much work as possible into compile time via Source Generators. This gives us two nice properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No runtime reflection&lt;/strong&gt; in the generated code itself&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type-safe output&lt;/strong&gt; that takes full advantage of modern C# features&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because the generated code leans on recent language features, consumers need &lt;strong&gt;.NET 8 or later&lt;/strong&gt;. In practice, this shouldn't be a real constraint for anyone today.&lt;/p&gt;

&lt;p&gt;There are plenty of similar libraries out there, so I put a lot of effort into the &lt;strong&gt;quality of the generated code&lt;/strong&gt;. Specifically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nullable reference types and &lt;code&gt;required&lt;/code&gt; modifiers are applied faithfully based on the OpenAPI schema&lt;/li&gt;
&lt;li&gt;Enums — particularly string-based ones — are generated in a form that's actually pleasant to use from C#&lt;/li&gt;
&lt;li&gt;XML doc comments are emitted from OpenAPI &lt;code&gt;description&lt;/code&gt; and &lt;code&gt;summary&lt;/code&gt; fields, so IntelliSense just works&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The generated client code itself contains no reflection. However, because it relies on &lt;code&gt;System.Text.Json&lt;/code&gt;, reflection is still used internally there. That means &lt;strong&gt;NativeAOT is not supported yet&lt;/strong&gt; — but once the &lt;code&gt;System.Text.Json&lt;/code&gt; source generator can be composed cleanly with this one, it should be achievable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;Usage is about as simple as I could make it: install the &lt;code&gt;OpenApiWeaver&lt;/code&gt; package from NuGet, and you're done. Once installed, you get a new MSBuild item type called &lt;code&gt;OpenApiWeaverDocument&lt;/code&gt;. Point its &lt;code&gt;Include&lt;/code&gt; attribute at your OpenAPI definition file:&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;Project&lt;/span&gt; &lt;span class="na"&gt;Sdk=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.NET.Sdk"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;OutputType&amp;gt;&lt;/span&gt;Exe&lt;span class="nt"&gt;&amp;lt;/OutputType&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;net10.0&lt;span class="nt"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ImplicitUsings&amp;gt;&lt;/span&gt;enable&lt;span class="nt"&gt;&amp;lt;/ImplicitUsings&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Nullable&amp;gt;&lt;/span&gt;enable&lt;span class="nt"&gt;&amp;lt;/Nullable&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"OpenApiWeaver"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"1.0.0"&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;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;OpenApiWeaverDocument&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"petstore.json"&lt;/span&gt; &lt;span class="na"&gt;ClientName=&lt;/span&gt;&lt;span class="s"&gt;"PetStoreClient"&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;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;OpenApiWeaverDocument&lt;/code&gt; also accepts &lt;code&gt;ClientName&lt;/code&gt; and &lt;code&gt;Namespace&lt;/code&gt; attributes, which let you override the generated client's class name and namespace.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get after a build
&lt;/h2&gt;

&lt;p&gt;That's the whole setup. Build the project, and the client is generated from the OpenAPI definition. For larger specs, the generator splits clients per OpenAPI &lt;strong&gt;Tag&lt;/strong&gt;, so you end up with one client class per logical grouping in your API — which keeps things manageable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnprp8sh7umc68e7ef5sr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnprp8sh7umc68e7ef5sr.png" alt="Generated clients split per OpenAPI tag" width="438" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The sample &lt;code&gt;petstore.json&lt;/code&gt; has &lt;code&gt;Pet&lt;/code&gt;, &lt;code&gt;Store&lt;/code&gt;, and &lt;code&gt;User&lt;/code&gt; tags, and you can see a client was generated for each.&lt;/p&gt;

&lt;p&gt;Each client exposes its operations as methods, so for the most part you can drive development purely through IntelliSense. (The naming of auto-generated methods still has room for improvement, I'll admit.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq1l5qbii2rvfskr5xsos.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq1l5qbii2rvfskr5xsos.png" alt="IntelliSense showing generated client methods" width="415" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the OpenAPI definition includes &lt;code&gt;description&lt;/code&gt; or &lt;code&gt;summary&lt;/code&gt; fields, those flow through to XML doc comments on the generated classes and methods, so you get nice tooltips in the editor:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxgl8h4ud7jq9l5gynpae.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxgl8h4ud7jq9l5gynpae.png" alt="Tooltip showing XML doc comments from OpenAPI descriptions" width="800" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The generated type definitions
&lt;/h2&gt;

&lt;p&gt;Operations are relatively straightforward, but the type definitions generated from &lt;code&gt;Schemas&lt;/code&gt; are where I spent most of my effort. Take the &lt;code&gt;Pet&lt;/code&gt; class from the sample — here's what actually gets generated:&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;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// Pet&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Pet&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// Id&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;JsonIgnore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Condition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonIgnoreCondition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WhenWritingNull&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;JsonPropertyName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// Name&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;JsonPropertyName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// Category&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;JsonIgnore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Condition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonIgnoreCondition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WhenWritingNull&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;JsonPropertyName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// PhotoUrls&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;JsonPropertyName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"photoUrls"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;PhotoUrls&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// Tags&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;JsonIgnore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Condition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonIgnoreCondition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WhenWritingNull&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;JsonPropertyName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Tag&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;?&lt;/span&gt; &lt;span class="n"&gt;Tags&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// Status&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;remarks&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// pet status in the store&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/remarks&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;JsonIgnore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Condition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonIgnoreCondition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WhenWritingNull&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;JsonPropertyName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Pet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusEnum&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="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// Pet.StatusEnum&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;remarks&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// pet status in the store&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/remarks&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;JsonConverter&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;StatusEnumJsonConverter&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;readonly&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;struct&lt;/span&gt; &lt;span class="nf"&gt;StatusEnum&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;Value&lt;/span&gt;&lt;span class="p"&gt;)&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;readonly&lt;/span&gt; &lt;span class="n"&gt;StatusEnum&lt;/span&gt; &lt;span class="n"&gt;Available&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="s"&gt;"available"&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;readonly&lt;/span&gt; &lt;span class="n"&gt;StatusEnum&lt;/span&gt; &lt;span class="n"&gt;Pending&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="s"&gt;"pending"&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;readonly&lt;/span&gt; &lt;span class="n"&gt;StatusEnum&lt;/span&gt; &lt;span class="n"&gt;Sold&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="s"&gt;"sold"&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;override&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;ToString&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;Value&lt;/span&gt;&lt;span class="p"&gt;;&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;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StatusEnumJsonConverter&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;JsonConverter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;StatusEnum&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&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;override&lt;/span&gt; &lt;span class="n"&gt;StatusEnum&lt;/span&gt; &lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="n"&gt;Utf8JsonReader&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;typeToConvert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonSerializerOptions&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&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;new&lt;/span&gt; &lt;span class="nf"&gt;StatusEnum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;()!);&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;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Utf8JsonWriter&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StatusEnum&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JsonSerializerOptions&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteStringValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&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;A few things worth pointing out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NRT and &lt;code&gt;required&lt;/code&gt; are applied correctly, so mistakes get caught at compile time&lt;/li&gt;
&lt;li&gt;String-based enums are &lt;strong&gt;not&lt;/strong&gt; mapped to C# &lt;code&gt;enum&lt;/code&gt;. Instead they become &lt;code&gt;readonly record struct&lt;/code&gt;, which lets you treat them as strings while still keeping named well-known values like &lt;code&gt;Available&lt;/code&gt;, &lt;code&gt;Pending&lt;/code&gt;, &lt;code&gt;Sold&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This string-enum pattern has become pretty common in modern .NET libraries, and I think it strikes a good balance between type safety and the reality that string enums in OpenAPI often need to tolerate unknown values.&lt;/p&gt;

&lt;p&gt;There are a few other niceties I won't cover here — the full documentation goes into more detail:&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://shibayan.github.io/openapi-weaver/" rel="noopener noreferrer"&gt;OpenApiWeaver documentation&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on the OpenAPI parser
&lt;/h2&gt;

&lt;p&gt;OpenApiWeaver only owns the "generate a client from an already-parsed OpenAPI document" half of the problem. Parsing itself is delegated to &lt;strong&gt;&lt;code&gt;Microsoft.OpenApi&lt;/code&gt;&lt;/strong&gt;, which keeps the surface area small. The docs say OpenAPI 3.x is supported, but really it's whatever that library supports:&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://devblogs.microsoft.com/openapi/openapi-net-release-announcements/" rel="noopener noreferrer"&gt;OpenAPI.NET release announcements (Microsoft DevBlogs)&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing note: this was built with AI coding tools
&lt;/h2&gt;

&lt;p&gt;One last thing worth sharing. This entire library — Source Generator, runtime pieces, and documentation — was built heavily with AI coding assistance. I did the PoC in Codex with GPT-5.4, then moved to GitHub Copilot Chat using GPT-5.4 and Opus 4.6 High for the actual implementation.&lt;/p&gt;

&lt;p&gt;Honestly, I've always considered hand-writing Source Generator code to be a painful endeavor, and I'd been putting off projects like this for exactly that reason. Leaning on AI coding tools let me focus on &lt;strong&gt;design decisions&lt;/strong&gt; — API shape, code quality goals, naming — while the assistant handled most of the mechanical work of emitting C# source.&lt;/p&gt;

&lt;p&gt;From PoC to release, including docs, it took about &lt;strong&gt;three days&lt;/strong&gt; of real work. That still surprises me.&lt;/p&gt;

&lt;p&gt;If you try OpenApiWeaver out, I'd love to hear what you think — issues and PRs welcome on the repo.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>openapi</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Built a Lightweight GitHub Action for Deploying to Azure Static Web Apps</title>
      <dc:creator>Tatsuro Shibamura</dc:creator>
      <pubDate>Mon, 06 Apr 2026 06:17:15 +0000</pubDate>
      <link>https://dev.to/polymind/built-a-lightweight-github-action-for-deploying-to-azure-static-web-apps-1ame</link>
      <guid>https://dev.to/polymind/built-a-lightweight-github-action-for-deploying-to-azure-static-web-apps-1ame</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I created &lt;a href="https://github.com/marketplace/actions/deploy-azure-static-web-apps" rel="noopener noreferrer"&gt;&lt;code&gt;shibayan/swa-deploy&lt;/code&gt;&lt;/a&gt; — a lightweight GitHub Action that &lt;strong&gt;only deploys&lt;/strong&gt; to Azure Static Web Apps, without the Docker-based build overhead of the official action. It wraps the same &lt;code&gt;StaticSitesClient&lt;/code&gt; that SWA CLI uses internally, includes automatic caching, and supports both Deployment Token and &lt;code&gt;azure/login&lt;/code&gt; authentication.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with the Official Action
&lt;/h2&gt;

&lt;p&gt;When deploying static sites (built with Astro, Vite, etc.) to Azure Static Web Apps, the standard approach is to use the official &lt;a href="https://github.com/marketplace/actions/azure-static-web-apps-deploy" rel="noopener noreferrer"&gt;&lt;code&gt;Azure/static-web-apps-deploy&lt;/code&gt;&lt;/a&gt; action that gets auto-generated when you link a GitHub repo to your SWA resource.&lt;/p&gt;

&lt;p&gt;Unlike other Azure deployment actions (e.g., for App Service or Azure Functions), this action uses &lt;strong&gt;Oryx&lt;/strong&gt; — the build engine used across Azure App Service — to build your application internally. It runs inside Docker, which means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The build process is a black box with hidden behaviors&lt;/li&gt;
&lt;li&gt;Docker image creation adds significant overhead&lt;/li&gt;
&lt;li&gt;Customizing the build pipeline is limited&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While Oryx is a capable multi-platform build engine, the Docker-based approach makes deploys slower than they need to be, especially when you just want to push pre-built static files.&lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;skip_app_build&lt;/code&gt; Option Isn't Enough
&lt;/h2&gt;

&lt;p&gt;You can set &lt;code&gt;skip_app_build: true&lt;/code&gt; in the official action to skip the application build step. This lets you build in GitHub Actions and only deploy through the action. However, it still needs to pull/build the Docker image, so the overhead remains.&lt;/p&gt;

&lt;p&gt;For reference, the official documentation covers this configuration:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/static-web-apps/build-configuration?tabs=aat&amp;amp;pivots=github-actions" rel="noopener noreferrer"&gt;Build configuration for Azure Static Web Apps | Microsoft Learn&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Using SWA CLI: Better, But Not Ideal
&lt;/h2&gt;

&lt;p&gt;To avoid the Docker overhead, I switched to using the &lt;a href="https://learn.microsoft.com/en-us/azure/static-web-apps/static-web-apps-cli-deploy" rel="noopener noreferrer"&gt;Static Web Apps CLI&lt;/a&gt; for deploy-only workflows. This approach works well — it's lighter than Docker — but comes with its own trade-offs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Installation time&lt;/strong&gt;: &lt;code&gt;npm install -g @azure/static-web-apps-cli&lt;/code&gt; takes a while&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache complexity&lt;/strong&gt;: To speed things up, you need to add npm global cache steps, which adds more workflow YAML than the actual deploy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cognitive overhead&lt;/strong&gt;: You end up thinking more about caching npm packages than deploying your app&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Solution: &lt;code&gt;shibayan/swa-deploy&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;I finally decided to build a dedicated GitHub Action that does one thing well: &lt;strong&gt;deploy to Azure Static Web Apps&lt;/strong&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/shibayan" rel="noopener noreferrer"&gt;
        shibayan
      &lt;/a&gt; / &lt;a href="https://github.com/shibayan/swa-deploy" rel="noopener noreferrer"&gt;
        swa-deploy
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A GitHub Action to deploy prebuilt frontend assets and Azure Functions APIs to Azure Static Web Apps
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Deploy Azure Static Web Apps&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/shibayan/swa-deploy/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/shibayan/swa-deploy/actions/workflows/ci.yml/badge.svg" alt="CI"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer" href="https://github.com/shibayan/swa-deploy/./badges/coverage.svg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fshibayan%2Fswa-deploy%2FHEAD%2F.%2Fbadges%2Fcoverage.svg" alt="Coverage"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A GitHub Action that deploys prebuilt frontend assets, Azure Functions APIs, and
&lt;code&gt;staticwebapp.config.json&lt;/code&gt; to
&lt;a href="https://learn.microsoft.com/azure/static-web-apps/" rel="nofollow noopener noreferrer"&gt;Azure Static Web Apps&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It follows the same deployment model as &lt;code&gt;swa deploy&lt;/code&gt; from the
&lt;a href="https://azure.github.io/static-web-apps-cli/" rel="nofollow noopener noreferrer"&gt;Azure Static Web Apps CLI&lt;/a&gt; —
download the &lt;code&gt;StaticSitesClient&lt;/code&gt; binary, resolve paths, and upload content using
a deployment token. When &lt;code&gt;deployment-token&lt;/code&gt; is omitted, this action can also
resolve the token at runtime through Azure Resource Manager after &lt;code&gt;azure/login&lt;/code&gt;
The binary is cached automatically across workflow runs.&lt;/p&gt;
&lt;div class="markdown-alert markdown-alert-note"&gt;
&lt;p class="markdown-alert-title"&gt;Note&lt;/p&gt;
&lt;p&gt;This action &lt;strong&gt;does not build&lt;/strong&gt; your application. Run your build step before
calling this action.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Deploy a built frontend&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-yaml notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-ent"&gt;name&lt;/span&gt;: &lt;span class="pl-s"&gt;Deploy&lt;/span&gt;
&lt;span class="pl-ent"&gt;on&lt;/span&gt;:
  &lt;span class="pl-ent"&gt;push&lt;/span&gt;:
    &lt;span class="pl-ent"&gt;branches&lt;/span&gt;: &lt;span class="pl-s"&gt;[master]&lt;/span&gt;

&lt;span class="pl-ent"&gt;jobs&lt;/span&gt;:
  &lt;span class="pl-ent"&gt;deploy&lt;/span&gt;:
    &lt;span class="pl-ent"&gt;runs-on&lt;/span&gt;: &lt;span class="pl-s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="pl-ent"&gt;steps&lt;/span&gt;:
      - &lt;span class="pl-ent"&gt;uses&lt;/span&gt;: &lt;span class="pl-s"&gt;actions/checkout@v4&lt;/span&gt;

      - &lt;span class="pl-ent"&gt;name&lt;/span&gt;: &lt;span class="pl-s"&gt;Build&lt;/span&gt;
        &lt;span class="pl-ent"&gt;run&lt;/span&gt;: &lt;span class="pl-s"&gt;npm ci &amp;amp;&amp;amp; npm run build&lt;/span&gt;

      - &lt;span class="pl-ent"&gt;name&lt;/span&gt;: &lt;span class="pl-s"&gt;Deploy to Azure Static Web Apps&lt;/span&gt;
        &lt;span class="pl-ent"&gt;id&lt;/span&gt;: &lt;span class="pl-s"&gt;deploy&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/shibayan/swa-deploy" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Under the hood, this action uses the same &lt;code&gt;StaticSitesClient&lt;/code&gt; binary that SWA CLI uses. It's essentially a thin wrapper around that CLI with automatic caching built in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using a Deployment Token
&lt;/h3&gt;

&lt;p&gt;The simplest approach — just pass your deployment token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Azure Static Web Apps&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shibayan/swa-deploy@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app-location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dist&lt;/span&gt;
    &lt;span class="na"&gt;deployment-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using &lt;code&gt;azure/login&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;If you prefer using &lt;code&gt;azure/login&lt;/code&gt; with a service principal or federated credentials, pass &lt;code&gt;app-name&lt;/code&gt; instead. The action will automatically look up the resource via the Azure Resource Manager API, retrieve the deployment token, and deploy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Azure login&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;azure/login@v2&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;client-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZURE_CLIENT_ID }}&lt;/span&gt;
    &lt;span class="na"&gt;tenant-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZURE_TENANT_ID }}&lt;/span&gt;
    &lt;span class="na"&gt;subscription-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZURE_SUBSCRIPTION_ID }}&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Azure Static Web Apps&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shibayan/swa-deploy@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app-location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dist&lt;/span&gt;
    &lt;span class="na"&gt;app-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-static-web-app&lt;/span&gt;
    &lt;span class="na"&gt;resource-group-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-resource-group&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The &lt;code&gt;resource-group-name&lt;/code&gt; parameter is optional. You only need it if you have multiple SWA resources with the same name across different resource groups in your subscription — since SWA names are unique at the resource group scope, not the subscription scope.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why Use This?
&lt;/h2&gt;

&lt;p&gt;Compared to the official action and SWA CLI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No Docker overhead&lt;/strong&gt; — no image build step&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic caching&lt;/strong&gt; — the &lt;code&gt;StaticSitesClient&lt;/code&gt; binary is cached automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple workflow YAML&lt;/strong&gt; — no npm cache boilerplate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lightweight&lt;/strong&gt; — does exactly one thing: deploy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I've migrated all of my SWA deployments to this action and it's been working great.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation Note
&lt;/h2&gt;

&lt;p&gt;The entire action was built using GitHub Copilot Chat with GPT-5.4 and Claude Opus 4.6, starting from the official template. AI-assisted development made it straightforward to implement and test.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>github</category>
      <category>actions</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Deep Dive into Azure Cosmos DB Physical Partitions</title>
      <dc:creator>Tatsuro Shibamura</dc:creator>
      <pubDate>Tue, 31 Mar 2026 14:21:33 +0000</pubDate>
      <link>https://dev.to/polymind/deep-dive-into-azure-cosmos-db-physical-partitions-49bb</link>
      <guid>https://dev.to/polymind/deep-dive-into-azure-cosmos-db-physical-partitions-49bb</guid>
      <description>&lt;h1&gt;
  
  
  Deep Dive into Azure Cosmos DB Physical Partitions
&lt;/h1&gt;

&lt;p&gt;When using Azure Cosmos DB effectively, the most important aspect is designing the partition key. However, when we talk about “partitions” in Cosmos DB, there are actually two types: &lt;strong&gt;logical partitions&lt;/strong&gt; and &lt;strong&gt;physical partitions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This topic is briefly covered in the official documentation, so it’s a good idea to review it first. In most cases, physical partitions are fully managed by Azure, so you rarely need to think about them.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://learn.microsoft.com/en-us/azure/cosmos-db/partitioning-overview#physical-partitions" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fmedia%2Fopen-graph-image.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://learn.microsoft.com/en-us/azure/cosmos-db/partitioning-overview#physical-partitions" rel="noopener noreferrer" class="c-link"&gt;
            Partitioning and horizontal scaling - Azure Cosmos DB | Microsoft Learn
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Learn about partitioning, logical, physical partitions in Azure Cosmos DB, best practices when choosing a partition key, and how to manage logical partitions.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
          learn.microsoft.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;When designing your data model, the partition key corresponds to logical partitions. Both logical and physical partitions have their own limits, but in most cases, you only need to care about the &lt;strong&gt;20 GB limit of logical partitions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Although you usually don’t need to think about physical partitions, understanding them can be very helpful during design. So here’s a concise summary based on my experience.&lt;/p&gt;




&lt;h2&gt;
  
  
  Basics of Physical Partitions
&lt;/h2&gt;

&lt;p&gt;As described in the official documentation, physical partitions are part of Cosmos DB’s internal implementation and represent the &lt;strong&gt;unit of compute that processes queries and operations&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Each physical partition has &lt;strong&gt;four replicas&lt;/strong&gt;, and quorum-based consistency is implemented across them.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://learn.microsoft.com/en-us/azure/cosmos-db/global-dist-under-the-hood" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fmedia%2Fopen-graph-image.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://learn.microsoft.com/en-us/azure/cosmos-db/global-dist-under-the-hood" rel="noopener noreferrer" class="c-link"&gt;
            Global Distribution With - Under the Hood - Azure Cosmos DB | Microsoft Learn
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            This article provides technical details relating to global distribution of Azure Cosmos DB
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
          learn.microsoft.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;In globally distributed configurations, replication is performed at the &lt;strong&gt;physical partition level&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A container always has at least one physical partition. However, Azure automatically increases the number of physical partitions when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Storage exceeds &lt;strong&gt;50 GB&lt;/strong&gt;, or&lt;/li&gt;
&lt;li&gt;Provisioned throughput exceeds &lt;strong&gt;10,000 RU/s&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Throughput and Physical Partitions
&lt;/h2&gt;

&lt;p&gt;Each physical partition has a maximum throughput of &lt;strong&gt;10,000 RU/s&lt;/strong&gt;. In practice, however, I’ve found that hitting the &lt;strong&gt;50 GB storage limit&lt;/strong&gt; happens more often than hitting the RU limit.&lt;/p&gt;

&lt;p&gt;A common issue occurs when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A container originally has &lt;strong&gt;4000 RU/s&lt;/strong&gt; on a single physical partition&lt;/li&gt;
&lt;li&gt;Storage exceeds 50 GB&lt;/li&gt;
&lt;li&gt;The system splits into &lt;strong&gt;2 physical partitions&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since RU is evenly distributed across physical partitions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 partition → 4000 RU&lt;/li&gt;
&lt;li&gt;2 partitions → 2000 RU each&lt;/li&gt;
&lt;li&gt;4 partitions → 1000 RU each&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This effectively &lt;strong&gt;reduces the throughput available per partition&lt;/strong&gt;, which can lead to sudden &lt;strong&gt;429 Too Many Requests&lt;/strong&gt; errors.&lt;/p&gt;

&lt;p&gt;In such cases, you should follow the official guidance and &lt;strong&gt;adjust RU accordingly&lt;/strong&gt;:&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://learn.microsoft.com/en-us/azure/cosmos-db/scaling-provisioned-throughput-best-practices" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fmedia%2Fopen-graph-image.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://learn.microsoft.com/en-us/azure/cosmos-db/scaling-provisioned-throughput-best-practices" rel="noopener noreferrer" class="c-link"&gt;
            Best Practices for Scaling Provisioned Throughput - Azure Cosmos DB | Microsoft Learn
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Learn about best practices for scaling manual and autoscale provisioned throughput for databases and containers.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
          learn.microsoft.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;If you already know your data will exceed 50 GB, it can be a good strategy to &lt;strong&gt;provision enough RU upfront&lt;/strong&gt; so that the required number of physical partitions are allocated early.&lt;/p&gt;

&lt;p&gt;Also note that when new physical partitions are created, data replication occurs, temporarily consuming additional RU. This can lead to &lt;strong&gt;temporary throughput degradation&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Connection Modes and Physical Partitions
&lt;/h2&gt;

&lt;p&gt;To improve performance, the C# and Java SDKs recommend using &lt;strong&gt;Direct mode&lt;/strong&gt;.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://learn.microsoft.com/en-us/azure/cosmos-db/sql/sql-sdk-connection-modes" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fmedia%2Fopen-graph-image.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://learn.microsoft.com/en-us/azure/cosmos-db/sql/sql-sdk-connection-modes" rel="noopener noreferrer" class="c-link"&gt;
            SQL SDK Connectivity Modes - Azure Cosmos DB | Microsoft Learn
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Learn about the different connectivity modes available on the Azure Cosmos DB SQL SDKs.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
          learn.microsoft.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;As the name suggests, Direct mode connects directly to backend physical partitions, reducing overhead.&lt;/p&gt;

&lt;p&gt;To make this work, the SDK must know &lt;strong&gt;which physical partition stores a given partition key&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you’ve enabled Application Insights, you may have seen requests to &lt;code&gt;pkranges&lt;/code&gt; during application startup. This API returns the mapping between partition keys and physical partitions:&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://learn.microsoft.com/en-us/rest/api/cosmos-db/get-partition-key-ranges" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fmedia%2Fopen-graph-image.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://learn.microsoft.com/en-us/rest/api/cosmos-db/get-partition-key-ranges" rel="noopener noreferrer" class="c-link"&gt;
            Get Partition Key Ranges - Azure Cosmos DB REST API | Microsoft Learn
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Get partition key ranges REST API syntax. Request and response headers, body, status codes and examples.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
          learn.microsoft.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;This API is not intended for direct use in user code.&lt;/p&gt;

&lt;p&gt;Because this mapping is fetched when a container is first accessed, the &lt;strong&gt;first request can be slower&lt;/strong&gt;. However, recent versions of the .NET SDK provide an API to explicitly initialize this mapping:&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.cosmos.cosmosclient.createandinitializeasync?view=azure-dotnet" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fmedia%2Fopen-graph-image.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.cosmos.cosmosclient.createandinitializeasync?view=azure-dotnet" rel="noopener noreferrer" class="c-link"&gt;
            CosmosClient.CreateAndInitializeAsync Method (Microsoft.Azure.Cosmos) - Azure for .NET Developers | Microsoft Learn
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Creates a new CosmosClient with the account endpoint URI string and TokenCredential. In addition to that it initializes the client with containers provided i.e The SDK warms up the caches and connections before the first call to the service is made. Use this to obtain lower latency while startup of your application. CosmosClient is thread-safe. Its recommended to maintain a single instance of CosmosClient per lifetime of the application which enables efficient connection management and performance. Please refer to the performance guide. 
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
          learn.microsoft.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;By calling this during application warm-up, you can &lt;strong&gt;minimize overhead for subsequent requests&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cross-Partition Queries and Physical Partitions
&lt;/h2&gt;

&lt;p&gt;In Cosmos DB design, &lt;strong&gt;cross-partition queries should generally be avoided&lt;/strong&gt;. However, this rule can be relaxed if all data fits within a single physical partition.&lt;/p&gt;

&lt;p&gt;In the following documentation, the term “partition” refers to &lt;strong&gt;physical partitions&lt;/strong&gt;:&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://learn.microsoft.com/en-us/azure/cosmos-db/how-to-query-container" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fmedia%2Fopen-graph-image.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://learn.microsoft.com/en-us/azure/cosmos-db/how-to-query-container" rel="noopener noreferrer" class="c-link"&gt;
            Query a Container - Azure Cosmos DB | Microsoft Learn
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Learn how to query containers in Azure Cosmos DB by using both in-partition and cross-partition queries.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
          learn.microsoft.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;If all logical partitions are contained within a single physical partition, indexes can be used effectively, making cross-partition queries relatively low-cost.&lt;/p&gt;

&lt;p&gt;Problems arise when a container spans multiple physical partitions, as queries must be executed across all of them, increasing cost.&lt;/p&gt;

&lt;p&gt;The most efficient cross-partition query is one that targets &lt;strong&gt;only partition keys within a single physical partition&lt;/strong&gt;, but this is not something you can control directly from user code.&lt;/p&gt;

&lt;p&gt;Instead, you should rely on SDK features. A good example is the &lt;code&gt;ReadMany&lt;/code&gt; API, which efficiently handles cross-partition access.&lt;/p&gt;

&lt;p&gt;That said, designing everything around cross-partition queries should always be avoided.&lt;/p&gt;




&lt;h2&gt;
  
  
  Change Feed and Physical Partitions
&lt;/h2&gt;

&lt;p&gt;Physical partitions are closely related to the &lt;strong&gt;Change Feed&lt;/strong&gt; feature.&lt;/p&gt;

&lt;p&gt;While Change Feed guarantees ordering at the logical partition level, its actual processing is based on &lt;strong&gt;physical partitions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is easier to understand when looking at the Pull model.&lt;/p&gt;

&lt;p&gt;In the Pull model, parallel processing is achieved using the &lt;code&gt;FeedRange&lt;/code&gt; class, which is assigned per physical partition. By distributing &lt;code&gt;FeedRange&lt;/code&gt; across multiple machines, you can process Change Feed in parallel.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://learn.microsoft.com/en-us/azure/cosmos-db/change-feed-pull-model?tabs=dotnet" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fmedia%2Fopen-graph-image.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://learn.microsoft.com/en-us/azure/cosmos-db/change-feed-pull-model?tabs=dotnet" rel="noopener noreferrer" class="c-link"&gt;
            Change Feed Pull Model - Azure Cosmos DB | Microsoft Learn
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Learn how to use the Azure Cosmos DB change feed pull model to read the change feed. Understand the differences between the change feed pull model and the change feed processor.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
          learn.microsoft.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.cosmos.feedrange?view=azure-dotnet" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fmedia%2Fopen-graph-image.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.cosmos.feedrange?view=azure-dotnet" rel="noopener noreferrer" class="c-link"&gt;
            FeedRange Class (Microsoft.Azure.Cosmos) - Azure for .NET Developers | Microsoft Learn
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Represents a unit of feed consumption that can be used as unit of parallelism. 
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
          learn.microsoft.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;As a result, even a single Change Feed Processor can only scale up to the number of physical partitions. In other words, processing is effectively &lt;strong&gt;single-threaded per physical partition&lt;/strong&gt;, which simplifies writing logic that depends on ordering.&lt;/p&gt;

&lt;p&gt;However, this also means that &lt;strong&gt;scale-out limits are reached relatively quickly&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To address this, you should minimize processing within each Change Feed Processor and instead create &lt;strong&gt;multiple smaller processors for different purposes&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Metrics and Physical Partitions
&lt;/h2&gt;

&lt;p&gt;By now, you should have a solid understanding of physical partitions, so let’s move on to monitoring.&lt;/p&gt;

&lt;p&gt;Even with careful partition key design, data can still become skewed toward specific physical partitions (so-called &lt;strong&gt;hot partitions&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;To identify them, you can split metrics by &lt;code&gt;PartitionKeyRangeId&lt;/code&gt;.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://learn.microsoft.com/en-us/azure/cosmos-db/monitor-normalized-request-units#how-to-monitor-for-hot-partitions" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fmedia%2Fopen-graph-image.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://learn.microsoft.com/en-us/azure/cosmos-db/monitor-normalized-request-units#how-to-monitor-for-hot-partitions" rel="noopener noreferrer" class="c-link"&gt;
            Monitor Normalized Request Units - Azure Cosmos DB | Microsoft Learn
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Learn how to monitor the normalized request unit usage of an operation in Azure Cosmos DB. Owners of an Azure Cosmos DB account can understand which operations are consuming more request units.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
          learn.microsoft.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Recently, new metrics such as &lt;strong&gt;Physical Partition Throughput&lt;/strong&gt; have been introduced, making it easier to troubleshoot partition-level issues.&lt;/p&gt;

&lt;p&gt;You can also view these metrics in &lt;strong&gt;Cosmos DB Insights in Azure Monitor&lt;/strong&gt;, which significantly improves observability.&lt;/p&gt;




&lt;h2&gt;
  
  
  Operations on Physical Partitions (Preview)
&lt;/h2&gt;

&lt;p&gt;At Build 2022, several Cosmos DB updates were announced, including preview features that allow &lt;strong&gt;direct operations on physical partitions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;One of them is &lt;strong&gt;Merge partitions&lt;/strong&gt;, which combines physical partitions.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://learn.microsoft.com/en-us/azure/cosmos-db/merge" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fmedia%2Fopen-graph-image.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://learn.microsoft.com/en-us/azure/cosmos-db/merge" rel="noopener noreferrer" class="c-link"&gt;
            Merge partitions (preview) - Azure Cosmos DB | Microsoft Learn
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Reduce the number of physical partitions used for your container with the merge capability in Azure Cosmos DB.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
          learn.microsoft.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Since RU is evenly distributed across physical partitions, fewer partitions can result in &lt;strong&gt;higher effective throughput per partition&lt;/strong&gt;. Merging excess partitions helps optimize performance.&lt;/p&gt;

&lt;p&gt;Another feature allows &lt;strong&gt;redistributing throughput across physical partitions&lt;/strong&gt;.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://learn.microsoft.com/en-us/azure/cosmos-db/sql/distribute-throughput-across-partitions" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flearn.microsoft.com%2Fen-us%2Fmedia%2Fopen-graph-image.png" height="420" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://learn.microsoft.com/en-us/azure/cosmos-db/sql/distribute-throughput-across-partitions" rel="noopener noreferrer" class="c-link"&gt;
            Redistribute Throughput Across Partitions (Preview) - Azure Cosmos DB | Microsoft Learn
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Learn how to redistribute throughput across partitions in Azure Cosmos DB to optimize performance. To improve partition performance, follow these step-by-step instructions and best practices.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
          learn.microsoft.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;No matter how carefully you design your partition key, data skew is inevitable in real-world scenarios. This feature allows you to assign more RU to hot partitions without increasing total RU, improving overall efficiency.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By understanding physical partitions, you can push Cosmos DB optimization further in terms of both performance and cost.&lt;/p&gt;

&lt;p&gt;However, this is &lt;strong&gt;not mandatory knowledge&lt;/strong&gt; for most use cases.&lt;/p&gt;

&lt;p&gt;Rather than focusing on physical partitions from the beginning, the most important thing is to &lt;strong&gt;design a good partition key&lt;/strong&gt; during data modeling.&lt;/p&gt;

&lt;p&gt;Most physical partition-related optimizations can be handled later, so invest your effort where it matters most: &lt;strong&gt;your initial data model design&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>cosmosdb</category>
    </item>
    <item>
      <title>Migrating Azure Functions Monitoring to OpenTelemetry</title>
      <dc:creator>Tatsuro Shibamura</dc:creator>
      <pubDate>Tue, 31 Mar 2026 14:05:40 +0000</pubDate>
      <link>https://dev.to/polymind/migrating-azure-functions-monitoring-to-opentelemetry-17o</link>
      <guid>https://dev.to/polymind/migrating-azure-functions-monitoring-to-opentelemetry-17o</guid>
      <description>&lt;p&gt;A while ago, version 3.0 of the Application Insights SDK was released, and its internal implementation was migrated to be based on OpenTelemetry. While it is generally recommended to use the OpenTelemetry Distro directly, being able to support OpenTelemetry just by updating the Application Insights SDK is a clear advantage.&lt;/p&gt;

&lt;p&gt;Along with this change, one of the Application Insights SDKs used in Azure Functions .NET Isolated—&lt;code&gt;Microsoft.ApplicationInsights.WorkerService&lt;/code&gt;—has also been updated to version 3.0. If you are managing both Azure Functions and ASP.NET Core projects in a single solution, you may often update them together.&lt;/p&gt;

&lt;p&gt;In a typical .NET Worker application, this update does not cause any issues. However, in Azure Functions, simply updating the package leads to runtime exceptions like the one below. Since the build succeeds without errors, this can be particularly tricky to notice.&lt;/p&gt;

&lt;p&gt;This issue occurs because the &lt;code&gt;Microsoft.Azure.Functions.Worker.ApplicationInsights&lt;/code&gt; package—required for Azure Functions' built-in Application Insights integration—has a strong dependency on the v2 SDK. While this would be resolved if the package were updated for v3, it appears from GitHub issues that there are no plans to provide a v3-compatible version. Therefore, in the long term, migrating to an OpenTelemetry-based approach will be necessary.&lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/Azure/azure-functions-dotnet-worker/issues/3322#issuecomment-4050282773" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        Comment for
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#3322&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/RohitRanjanMS" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F90008725%3Fu%3Dcef322ae933f4fb1cb94f6a94e20ae5f1a996ee9%26v%3D4" alt="RohitRanjanMS avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/RohitRanjanMS" rel="noopener noreferrer"&gt;RohitRanjanMS&lt;/a&gt;
        &lt;/strong&gt; commented on &lt;a href="https://github.com/Azure/azure-functions-dotnet-worker/issues/3322#issuecomment-4050282773" rel="noopener noreferrer"&gt;&lt;time&gt;Mar 12, 2026&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;We do not plan to release a new version of &lt;code&gt;Microsoft.Azure.Functions.Worker.ApplicationInsights&lt;/code&gt; that is compatible with the 3.0 release of &lt;code&gt;Microsoft.ApplicationInsights.WorkerService&lt;/code&gt;. Our current package relies heavily on Telemetry Initializers and Telemetry Processors, and the new 3.0 SDK no longer supports these extensibility points. Supporting it would require a full rewrite of the Functions worker package. The same limitation may also affect customers who depend on Initializers or Processors in their applications.
Our recommendation is to move to OpenTelemetry‑based monitoring. This is the direction we are investing in, and we are committed to improving observability by aligning with the OpenTelemetry ecosystem. You can follow Functions OpenTelemetry guidance here:
&lt;a href="https://learn.microsoft.com/en-us/azure/azure-functions/opentelemetry-howto?tabs=app-insights%2Cihostapplicationbuilder%2Cmaven&amp;amp;pivots=programming-language-csharp" rel="nofollow noopener noreferrer"&gt;https://learn.microsoft.com/en-us/azure/azure-functions/opentelemetry-howto?tabs=app-insights%2Cihostapplicationbuilder%2Cmaven&amp;amp;pivots=programming-language-csharp&lt;/a&gt;&lt;/p&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Azure/azure-functions-dotnet-worker/issues/3322#issuecomment-4050282773" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Azure Functions now supports OpenTelemetry, but if you create a project from the default template, it still uses the Application Insights SDK. Therefore, some additional steps are required to switch.&lt;/p&gt;

&lt;p&gt;You can follow the official documentation below for the migration steps, but there are a few pitfalls I encountered during the process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/azure-functions/opentelemetry-howto" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/azure/azure-functions/opentelemetry-howto&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Removing Application Insights Packages
&lt;/h2&gt;

&lt;p&gt;First, check your existing Azure Functions project. You will likely find the following two Application Insights-related packages installed. Remove them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Microsoft.ApplicationInsights.WorkerService&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Microsoft.Azure.Functions.Worker.ApplicationInsights&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These packages depend on the Application Insights SDK and are no longer needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding OpenTelemetry Packages
&lt;/h2&gt;

&lt;p&gt;Instead, install the following OpenTelemetry-related packages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Microsoft.Azure.Functions.Worker.OpenTelemetry&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Azure.Monitor.OpenTelemetry.Exporter&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OpenTelemetry.Extensions.Hosting&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In ASP.NET Core, a single package is often sufficient, but in Azure Functions, you need to install them individually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating Program.cs
&lt;/h2&gt;

&lt;p&gt;At this point, your project will no longer build. Remove the existing Application Insights initialization code in &lt;code&gt;Program.cs&lt;/code&gt; and replace it with the following:&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;using&lt;/span&gt; &lt;span class="nn"&gt;Azure.Monitor.OpenTelemetry.Exporter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Azure.Functions.Worker.Builder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Azure.Functions.Worker.OpenTelemetry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.DependencyInjection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Hosting&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;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FunctionsApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureFunctionsWebApplication&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenTelemetry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseFunctionsWorkerDefaults&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseAzureMonitorExporter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Run&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 need customization, you can pass options to &lt;code&gt;UseAzureMonitorExporter&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Local Development Pitfall
&lt;/h2&gt;

&lt;p&gt;After this change, the project builds successfully. However, when running locally, you may encounter an error indicating that the Application Insights connection string is missing, causing the application to fail at startup.&lt;/p&gt;

&lt;p&gt;With the Application Insights SDK, telemetry was automatically disabled if no connection string was provided. However, with the OpenTelemetry Distro, it is now required.&lt;/p&gt;

&lt;p&gt;To avoid forcing Application Insights in local development, you could disable it using preprocessor directives. However, I personally found that approach messy.&lt;/p&gt;

&lt;p&gt;Instead, I added a dummy connection string like this:&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;"IsEncrypted"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Values"&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;"AzureWebJobsStorage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UseDevelopmentStorage=true"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"FUNCTIONS_WORKER_RUNTIME"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dotnet-isolated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"APPLICATIONINSIGHTS_CONNECTION_STRING"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"InstrumentationKey=00000000-0000-0000-0000-000000000000"&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="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This allows the application to start correctly in a local development environment.&lt;/p&gt;
&lt;h2&gt;
  
  
  Verifying Telemetry
&lt;/h2&gt;

&lt;p&gt;Finally, connect to Application Insights and verify that telemetry is being sent correctly.&lt;/p&gt;

&lt;p&gt;You will notice that property names follow the OpenTelemetry schema. However, telemetry generated by the Azure Functions host remains unchanged.&lt;/p&gt;
&lt;h2&gt;
  
  
  Real-World Example
&lt;/h2&gt;

&lt;p&gt;Here is the Pull Request where I migrated Acmebot to an OpenTelemetry-based setup:&lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/polymind-inc/acmebot/pull/996" rel="noopener noreferrer"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        Add OpenTelemetry support and remove Application Insights initializer
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#996&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/shibayan" rel="noopener noreferrer"&gt;
        &lt;img class="github-liquid-tag-img" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F1356444%3Fv%3D4" alt="shibayan avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/shibayan" rel="noopener noreferrer"&gt;shibayan&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/polymind-inc/acmebot/pull/996" rel="noopener noreferrer"&gt;&lt;time&gt;Mar 20, 2026&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;This pull request updates the application's telemetry and monitoring stack by migrating from Application Insights to OpenTelemetry with Azure Monitor Exporter. This modernizes observability, aligns with current best practices, and simplifies related configuration and code. The most important changes are grouped below.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Migration to OpenTelemetry and Azure Monitor Exporter:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Removed dependencies on &lt;code&gt;Microsoft.ApplicationInsights.WorkerService&lt;/code&gt; and &lt;code&gt;Microsoft.Azure.Functions.Worker.ApplicationInsights&lt;/code&gt;, and added new dependencies for &lt;code&gt;Azure.Monitor.OpenTelemetry.Exporter&lt;/code&gt;, &lt;code&gt;Microsoft.Azure.Functions.Worker.OpenTelemetry&lt;/code&gt;, and &lt;code&gt;OpenTelemetry.Extensions.Hosting&lt;/code&gt; in &lt;code&gt;Acmebot.App.csproj&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Replaced Application Insights telemetry setup in &lt;code&gt;Program.cs&lt;/code&gt; with OpenTelemetry initialization using &lt;code&gt;AddOpenTelemetry()&lt;/code&gt;, &lt;code&gt;UseFunctionsWorkerDefaults()&lt;/code&gt;, and &lt;code&gt;UseAzureMonitorExporter()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Updated &lt;code&gt;host.json&lt;/code&gt; to set &lt;code&gt;"telemetryMode": "OpenTelemetry"&lt;/code&gt; and removed Application Insights-specific logging configuration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Codebase cleanup:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Removed the custom &lt;code&gt;ApplicationVersionInitializer&lt;/code&gt; class and its registration, as it was specific to Application Insights. [1] [2]
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;General code and dependency updates:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Updated &lt;code&gt;using&lt;/code&gt; statements in &lt;code&gt;Program.cs&lt;/code&gt; to remove Application Insights references and add OpenTelemetry/Azure Monitor references as needed.&lt;/li&gt;
&lt;/ul&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/polymind-inc/acmebot/pull/996" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;The changes are relatively simple—for example, removing &lt;code&gt;ITelemetryInitializer&lt;/code&gt;. Equivalent functionality is now built in, so it is no longer necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Migrating applications that rely heavily on &lt;code&gt;ITelemetryProcessor&lt;/code&gt; or other advanced customizations may require more effort. However, it seems feasible to migrate even filtering logic to OpenTelemetry.&lt;/p&gt;

&lt;p&gt;If I get the time, I plan to write another post covering those advanced scenarios.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>dotnet</category>
      <category>opentelemetry</category>
      <category>serverless</category>
    </item>
  </channel>
</rss>
