<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[ASP Today]]></title><description><![CDATA[Expert insights into Microsoft ASP.NET, classic ASP, and the latest web development trends.]]></description><link>https://www.asptoday.com</link><image><url>https://substackcdn.com/image/fetch/$s_!LonC!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa58c47c8-e39c-48e4-826a-d05e9ca9d537_509x509.jpeg</url><title>ASP Today</title><link>https://www.asptoday.com</link></image><generator>Substack</generator><lastBuildDate>Sat, 18 Jul 2026 09:04:32 GMT</lastBuildDate><atom:link href="https://www.asptoday.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Matthew Pomar]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[asptoday@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[asptoday@substack.com]]></itunes:email><itunes:name><![CDATA[Matthew Pomar]]></itunes:name></itunes:owner><itunes:author><![CDATA[Matthew Pomar]]></itunes:author><googleplay:owner><![CDATA[asptoday@substack.com]]></googleplay:owner><googleplay:email><![CDATA[asptoday@substack.com]]></googleplay:email><googleplay:author><![CDATA[Matthew Pomar]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Building Internal Developer Platforms with ASP.NET Core: Tools, APIs, and Automation]]></title><description><![CDATA[Discover how Internal Developer Platforms help teams ship software faster using ASP.NET Core, self-service APIs, automation, and reusable developer tools. #ASPToday #aspnetcore #platformengineering #devops #dotnet]]></description><link>https://www.asptoday.com/p/building-internal-developer-platforms</link><guid isPermaLink="false">https://www.asptoday.com/p/building-internal-developer-platforms</guid><dc:creator><![CDATA[Jenny Muralidharan]]></dc:creator><pubDate>Tue, 14 Jul 2026 15:02:02 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!kRwv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F773fce55-8ac3-493a-8c84-3cdf60faee11_1672x941.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As organizations grow, software development often becomes slower rather than faster. Teams spend increasing amounts of time requesting infrastructure, configuring environments, creating new projects, managing deployments, and solving repetitive operational problems. Internal Developer Platforms (IDPs) address these challenges by providing developers with self-service tools, standardized APIs, reusable templates, and automated workflows that reduce friction and accelerate delivery. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kRwv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F773fce55-8ac3-493a-8c84-3cdf60faee11_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kRwv!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F773fce55-8ac3-493a-8c84-3cdf60faee11_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!kRwv!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F773fce55-8ac3-493a-8c84-3cdf60faee11_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!kRwv!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F773fce55-8ac3-493a-8c84-3cdf60faee11_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!kRwv!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F773fce55-8ac3-493a-8c84-3cdf60faee11_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kRwv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F773fce55-8ac3-493a-8c84-3cdf60faee11_1672x941.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/773fce55-8ac3-493a-8c84-3cdf60faee11_1672x941.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2506893,&quot;alt&quot;:&quot;Developers use a self-service LEGO workshop to instantly build new software projects. &quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.asptoday.com/i/206658434?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F773fce55-8ac3-493a-8c84-3cdf60faee11_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Developers use a self-service LEGO workshop to instantly build new software projects. " title="Developers use a self-service LEGO workshop to instantly build new software projects. " srcset="https://substackcdn.com/image/fetch/$s_!kRwv!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F773fce55-8ac3-493a-8c84-3cdf60faee11_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!kRwv!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F773fce55-8ac3-493a-8c84-3cdf60faee11_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!kRwv!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F773fce55-8ac3-493a-8c84-3cdf60faee11_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!kRwv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F773fce55-8ac3-493a-8c84-3cdf60faee11_1672x941.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In this guide, we&#8217;ll explore how to build an Internal Developer Platform using <a href="https://www.asptoday.com/p/introduction-to-aspnet-core-whats-new-and-why-it-matters">ASP.NET Core</a> and create a foundation that allows development teams to focus on writing business features instead of managing infrastructure. </p><h2>Why Development Slows as Teams Grow</h2><p>A small startup might have:</p><ul><li><p>Five developers </p></li><li><p>One application </p></li><li><p>One deployment pipeline</p></li></ul><p>Everyone knows where everything is.</p><p>Need a database?</p><p>Someone creates one.</p><p>Need a new API?</p><p>Someone builds it.</p><p>Need production access?</p><p>Just ask.</p><p>Everything works.</p><p>Until it doesn&#8217;t.</p><p>As organizations grow, so do requests.</p><p>Developers begin asking questions like:</p><ul><li><p>Can someone create a Kubernetes namespace?</p></li><li><p>Can I get a SQL database?</p></li><li><p>Who creates <a href="https://www.asptoday.com/p/aspnet-core-and-azure-service-bus">Azure</a> resources?</p></li><li><p>Where do I request secrets?</p></li><li><p>How do I deploy to staging?</p></li><li><p>Which logging standard should I follow?</p></li></ul><p>Eventually engineers spend more time waiting than building. </p><h2>What Is an Internal Developer Platform?</h2><p>An Internal Developer Platform is software built for developers inside an organization.</p><p>Instead of manually requesting resources, developers use a platform to provision them themselves.</p><p>Think of it as an internal app store.</p><p>Instead of installing games, developers request:</p><ul><li><p>New projects</p></li><li><p>Databases</p></li><li><p>Storage accounts</p></li><li><p>API keys</p></li><li><p><a href="https://www.asptoday.com/p/aspnet-core-and-kubernetes-from-development">Kubernetes</a> environments</p></li><li><p>Deployment pipelines</p></li></ul><p>Everything follows company standards automatically. </p><h2>Why Organizations Build Internal Platforms</h2><p>The biggest goal is reducing developer friction.</p><p>Without an IDP:</p><p>Developer writes ticket.</p><p>Operations creates infrastructure.</p><p>Security reviews access.</p><p>Platform team configures deployment.</p><p>Developer waits.</p><p>With an IDP:</p><p>Developer clicks a button.</p><p>Platform performs everything automatically.</p><p>The developer continues working. </p><h2>Platform Engineering vs DevOps</h2><p>These terms are closely related.</p><p>DevOps focuses on collaboration between development and operations.</p><p>Platform Engineering creates products that help developers succeed.</p><p>Instead of manually helping every team, platform engineers build reusable tools.</p><p>Those tools become the Internal Developer Platform. </p><h2>Understanding Self-Service Development</h2><p>Imagine buying groceries.</p><p>Years ago, every item required asking a shop assistant.</p><p>Today we simply use self-checkout.</p><p>Internal platforms work the same way.</p><p>Instead of requesting resources manually, developers provision them independently.</p><p>The platform performs all the complicated work behind the scenes.</p><h2>What Can an Internal Developer Platform Provide?</h2><p>A mature platform might offer:</p><ul><li><p>Project templates</p></li><li><p>API creation</p></li><li><p>Infrastructure provisioning</p></li><li><p>Secret management</p></li><li><p>Logging configuration</p></li><li><p>Monitoring dashboards</p></li><li><p>CI/CD pipelines</p></li><li><p>Deployment approvals</p></li><li><p>Security scanning</p></li><li><p>Documentation</p></li></ul><p>Developers interact with one consistent interface. </p><h2>Why ASP.NET Core Is a Great Choice</h2><p>ASP.NET Core excels at building internal tools.</p><p>It offers:</p><ul><li><p>High performance</p></li><li><p>Cross-platform support</p></li><li><p>Excellent security</p></li><li><p>REST API development</p></li><li><p>Background services</p></li><li><p>Identity integration</p></li><li><p>Cloud-native capabilities</p></li></ul><p>These features make it ideal for building platform APIs. </p><h2>Designing the Platform</h2><p>Think of the platform as multiple services working together.</p><pre><code><code>Developer Portal
        &#9474;
        &#9660;
Platform API
        &#9474;
 &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9532;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
 &#9660;      &#9660;        &#9660;
Templates Deployments Infrastructure
        &#9474;
        &#9660;
Cloud Resources</code></code></pre><p>Each component has a specific responsibility.</p><p>This separation keeps the platform maintainable. </p><h2>Building the Platform API</h2><p>Everything begins with an API.</p><p>Developers request services through HTTP endpoints.</p><p>Example:</p><pre><code><code>POST /projects</code></code></pre><p>Payload:</p><pre><code><code>{
  "name": "InventoryService",
  "template": "webapi"
}</code></code></pre><p>The platform performs the remaining work automatically. </p><h2>Creating the API</h2><p>Example:</p><pre><code><code>app.MapPost("/projects",
(ProjectRequest request) =&gt;
{
    return Results.Ok(
        $"Creating {request.Name}");
});</code></code></pre><p>Simple APIs often orchestrate much larger workflows. </p><h2>Automating Project Creation</h2><p>Suppose every microservice requires:</p><ul><li><p>Standard folder structure</p></li><li><p>Logging</p></li><li><p>Health checks</p></li><li><p>Docker support</p></li><li><p>OpenTelemetry</p></li><li><p>Authentication</p></li></ul><p>Instead of manually configuring each project, developers choose a template.</p><p>The platform generates everything consistently. </p><h2>Project Templates</h2><p>Templates eliminate repetitive work.</p><p>A template might include:</p><ul><li><p>Authentication configured</p></li><li><p>Swagger enabled</p></li><li><p>Logging configured</p></li><li><p>Dockerfile included</p></li><li><p>CI/CD pipeline ready</p></li><li><p>Unit tests added</p></li></ul><p>Every new service begins with the same high-quality foundation.</p><h2>Managing Templates</h2><p>Templates evolve over time.</p><p>For example:</p><p>Version 1:</p><ul><li><p>.NET 8</p></li><li><p>Basic logging</p></li></ul><p>Version 2:</p><ul><li><p>OpenTelemetry</p></li><li><p>Health checks</p></li><li><p>Distributed tracing</p></li><li><p>Rate limiting</p></li></ul><p>Developers automatically benefit from platform improvements. </p><h2>Infrastructure as Code</h2><p>Infrastructure should never be created manually.</p><p>Instead use:</p><ul><li><p>Bicep</p></li><li><p>Terraform</p></li><li><p>ARM Templates</p></li></ul><p>The platform invokes these tools automatically.</p><p>Developers simply request resources. </p><h2>Provisioning Cloud Resources</h2><p>Imagine a developer needs:</p><ul><li><p>Azure SQL</p></li><li><p>Azure Storage</p></li><li><p>Service Bus</p></li><li><p>Key Vault</p></li></ul><p>Instead of opening multiple Azure portals, they submit one request.</p><p>The platform provisions everything. </p><h2>Background Processing</h2><p>Some operations take several minutes.</p><p>Examples:</p><ul><li><p>Creating Kubernetes clusters</p></li><li><p>Deploying infrastructure</p></li><li><p>Provisioning databases</p></li></ul><p>ASP.NET Core Background Services handle these long-running jobs efficiently.</p><p>Example:</p><pre><code><code>public class ProvisioningWorker
    : BackgroundService
{
    protected override async Task ExecuteAsync(
        CancellationToken token)
    {
        while (!token.IsCancellationRequested)
        {
            await Task.Delay(5000, token);
        }
    }
}</code></code></pre><p>Background workers keep APIs responsive. </p><h2>Building a Developer Portal</h2><p>The portal becomes the front door of the platform.</p><p>Developers can:</p><ul><li><p>Create projects</p></li><li><p>Request environments</p></li><li><p>View deployments</p></li><li><p>Browse documentation</p></li><li><p>Monitor applications</p></li></ul><p>Everything is available from one location. </p><h2>Authentication</h2><p>Only authorized developers should access platform resources.</p><p><a href="https://www.asptoday.com/p/understanding-aspnet-core-identity">ASP.NET Core Identity</a> integrates easily with:</p><ul><li><p>Microsoft Entra ID</p></li><li><p>Azure AD</p></li><li><p>OAuth</p></li><li><p>OpenID Connect</p></li></ul><p>Authentication ensures platform requests remain secure. </p><h2>Authorization</h2><p>Different developers require different permissions.</p><p>Examples:</p><p>Developers</p><ul><li><p>Create projects</p></li></ul><p>Team Leads</p><ul><li><p>Approve production deployments</p></li></ul><p>Platform Engineers</p><ul><li><p>Manage templates</p></li></ul><p>Administrators</p><ul><li><p>Configure infrastructure</p></li></ul><p>Policy-based authorization keeps permissions manageable.</p><p>This builds directly on concepts from our previous article on advanced authorization. </p><h2>API Versioning</h2><p>Internal platforms evolve continuously.</p><p><a href="https://www.asptoday.com/p/exploring-web-api-versioning-in-aspnet">Versioning</a> prevents breaking existing developer workflows.</p><p>Example:</p><pre><code><code>/api/v1/projects

/api/v2/projects</code></code></pre><p>Existing automation continues working while new features are introduced. </p><h2>Logging Platform Activity</h2><p>Everything the platform does should be logged.</p><p>Examples:</p><ul><li><p>Project created</p></li><li><p>Deployment started</p></li><li><p>Database provisioned</p></li><li><p>Secret generated</p></li></ul><p><a href="https://www.asptoday.com/p/building-audit-logging-systems-in">Audit logging</a> becomes especially valuable for compliance and troubleshooting.</p><p>As discussed in our previous article, audit logs provide accountability across complex systems. </p><h2>Monitoring Platform Health</h2><p>An Internal Developer Platform quickly becomes business-critical.</p><p>Monitor:</p><ul><li><p>API availability</p></li><li><p>Provisioning success rate</p></li><li><p>Deployment duration</p></li><li><p>Queue lengths</p></li><li><p>Failed workflows</p></li></ul><p><a href="https://www.asptoday.com/p/aspnet-core-health-checks-keeping">Health checks</a> and OpenTelemetry provide excellent visibility.</p><p>This connects naturally with our recent observability articles. </p><h2>Building for Reuse</h2><p>One of the biggest goals of an IDP is eliminating duplicated effort.</p><p>Instead of every team solving the same problems independently, the platform provides reusable solutions that everyone benefits from.</p><p>This creates consistency across the entire organization. </p><h2>Coming in Part 2</h2><p>Our Internal Developer Platform can now provide developers with self-service tools and standardized project creation, but there&#8217;s still more work to do before it becomes a truly enterprise-ready platform.</p><p>In Part 2, we&#8217;ll automate <a href="https://www.asptoday.com/p/utilizing-azure-devops-for-cicd-in">CI/CD pipelines</a>, provision development and production environments on demand, manage secrets securely, implement deployment approvals and governance, and explore how to scale the platform across multiple teams. We&#8217;ll finish by building a complete real-world Internal Developer Platform that helps developers deliver software faster while maintaining security, consistency, and operational excellence. </p><h2>Closing Thoughts</h2><p>An Internal Developer Platform is much more than a collection of internal tools. It becomes the foundation that enables development teams to work faster, deliver more consistently, and spend less time on repetitive operational tasks. By providing self-service capabilities, standardized templates, automated infrastructure provisioning, and reusable APIs, organizations can reduce bottlenecks while improving security, reliability, and developer experience. As your platform evolves, you can expand it with automated deployment pipelines, environment provisioning, secret management, governance controls, and observability, transforming it into a central hub that supports every stage of the software development lifecycle.</p><h2>Subscribe Now</h2><p>Enjoyed this article? <a href="https://www.asptoday.com/">Subscribe to ASP Today</a> for practical ASP.NET Core tutorials, architecture deep dives, and real-world engineering guides. Join our Substack Chat to connect with fellow developers, ask questions, and stay up to date with the latest ASP.NET Core best practices.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.asptoday.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.asptoday.com/subscribe?"><span>Subscribe now</span></a></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Building Audit Logging Systems in ASP.NET Core: Tracking Changes and Compliance]]></title><description><![CDATA[Learn how to build reliable audit logging in ASP.NET Core to track user actions, protect sensitive data, and support compliance. #ASPToday #aspnetcore #auditlogging #security #dotnet]]></description><link>https://www.asptoday.com/p/building-audit-logging-systems-in</link><guid isPermaLink="false">https://www.asptoday.com/p/building-audit-logging-systems-in</guid><dc:creator><![CDATA[Jenny Muralidharan]]></dc:creator><pubDate>Tue, 07 Jul 2026 15:02:45 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!fsVb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64a45198-49ad-4075-b78c-aa171b597074_2752x1536.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Protecting an application is only part of the security story. Organizations also need to know who accessed data, what changed, when it changed, and why it changed. Audit logging provides that visibility by creating a trustworthy history of important actions throughout an application.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fsVb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64a45198-49ad-4075-b78c-aa171b597074_2752x1536.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fsVb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64a45198-49ad-4075-b78c-aa171b597074_2752x1536.png 424w, https://substackcdn.com/image/fetch/$s_!fsVb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64a45198-49ad-4075-b78c-aa171b597074_2752x1536.png 848w, https://substackcdn.com/image/fetch/$s_!fsVb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64a45198-49ad-4075-b78c-aa171b597074_2752x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!fsVb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64a45198-49ad-4075-b78c-aa171b597074_2752x1536.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fsVb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64a45198-49ad-4075-b78c-aa171b597074_2752x1536.png" width="1456" height="813" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/64a45198-49ad-4075-b78c-aa171b597074_2752x1536.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:813,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:9148522,&quot;alt&quot;:&quot;A luxury hotel lobby where guests' actions are logged in large books as glowing data and code. &quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.asptoday.com/i/205141211?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64a45198-49ad-4075-b78c-aa171b597074_2752x1536.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A luxury hotel lobby where guests' actions are logged in large books as glowing data and code. " title="A luxury hotel lobby where guests' actions are logged in large books as glowing data and code. " srcset="https://substackcdn.com/image/fetch/$s_!fsVb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64a45198-49ad-4075-b78c-aa171b597074_2752x1536.png 424w, https://substackcdn.com/image/fetch/$s_!fsVb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64a45198-49ad-4075-b78c-aa171b597074_2752x1536.png 848w, https://substackcdn.com/image/fetch/$s_!fsVb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64a45198-49ad-4075-b78c-aa171b597074_2752x1536.png 1272w, https://substackcdn.com/image/fetch/$s_!fsVb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64a45198-49ad-4075-b78c-aa171b597074_2752x1536.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In this guide, you&#8217;ll learn how to build audit logging into <a href="https://www.asptoday.com/p/introduction-to-aspnet-core-whats-new-and-why-it-matters">ASP.NET Core</a> applications, automatically capture meaningful events, support regulatory compliance, and create audit trails that remain useful as your applications grow. </p><h2>Why Audit Logging Matters</h2><p>Imagine a customer calls support and says:</p><blockquote><p>&#8220;I never changed my shipping address.&#8221;</p></blockquote><p>A week later another customer reports:</p><blockquote><p>&#8220;My account was deleted.&#8221;</p></blockquote><p>An administrator notices that sensitive information has disappeared from the database.</p><p>Without an audit log, you only know that something happened.</p><p>You don&#8217;t know:</p><ul><li><p>Who made the change</p></li><li><p>When it happened</p></li><li><p>What the previous value was</p></li><li><p>What the new value became</p></li><li><p>Which application made the change</p></li></ul><p>Audit logging fills in these missing details.</p><p>It creates a permanent history of important events that helps developers troubleshoot problems, helps administrators investigate incidents, and helps organizations satisfy compliance requirements. </p><h2>Application Logs vs Audit Logs</h2><p>Many developers assume ordinary application logs are enough.</p><p>They are not.</p><p>Application logs record how the application behaves.</p><p>Examples include:</p><ul><li><p>Exceptions</p></li><li><p>Startup events</p></li><li><p>Performance metrics</p></li><li><p>Debug information</p></li></ul><p>Audit logs answer business questions instead.</p><p>Examples include:</p><ul><li><p>Who updated this customer?</p></li><li><p>Who approved this payment?</p></li><li><p>Who downloaded this report?</p></li><li><p>Who deleted this document?</p></li></ul><p>The two logging systems complement each other.</p><p>They should not replace one another. </p><h2>What Should Be Audited?</h2><p>Not every action deserves an audit record.</p><p>Focus on events that affect business operations or security.</p><p>Examples include:</p><ul><li><p>User logins</p></li><li><p>Failed login attempts</p></li><li><p>Password changes</p></li><li><p>Profile updates</p></li><li><p>Record creation</p></li><li><p>Record modification</p></li><li><p>Record deletion</p></li><li><p>Permission changes</p></li><li><p>Role assignments</p></li><li><p>Administrative actions</p></li><li><p>Data exports</p></li><li><p>Financial approvals</p></li></ul><p>Auditing every tiny event often creates unnecessary noise.</p><p>Choose events that would matter during an investigation. </p><h2>Designing an Audit Record</h2><p>Every audit entry should answer five simple questions.</p><p>Who? </p><p>What? </p><p>When? </p><p>Where? </p><p>Why? </p><p>A typical audit record might contain:</p><ul><li><p>User ID</p></li><li><p>Username</p></li><li><p>Action performed</p></li><li><p>Entity affected</p></li><li><p>Previous values</p></li><li><p>New values</p></li><li><p>Timestamp</p></li><li><p>IP address</p></li><li><p>Browser or device</p></li><li><p>Correlation ID</p></li></ul><p>The more context captured, the more useful the audit log becomes. </p><h2>A Simple Audit Model</h2><p>Example:</p><pre><code><code>public class AuditLog
{
    public int Id { get; set; }

    public string UserId { get; set; }

    public string Action { get; set; }

    public string Entity { get; set; }

    public string OldValues { get; set; }

    public string NewValues { get; set; }

    public DateTime Timestamp { get; set; }

    public string IpAddress { get; set; }
}</code></code></pre><p>This model provides a solid starting point.</p><p>Real-world systems often add more metadata. </p><h2>Capturing User Identity</h2><p>Audit logs become much more valuable when every action is linked to a specific user.</p><p>ASP.NET Core makes this easy.</p><p>Example:</p><pre><code><code>var username = User.Identity?.Name;</code></code></pre><p>For applications using JWT authentication:</p><pre><code><code>var userId =
    User.FindFirst("sub")?.Value;</code></code></pre><p>Capturing user identity should be standard practice. </p><h2>Recording Request Information</h2><p>Sometimes the request itself provides useful evidence.</p><p>Consider recording:</p><ul><li><p>IP address</p></li><li><p>HTTP method</p></li><li><p>URL</p></li><li><p>User agent</p></li><li><p>Correlation ID</p></li></ul><p>Example:</p><pre><code><code>var ip =
    HttpContext.Connection
        .RemoteIpAddress?
        .ToString();</code></code></pre><p>These details help reconstruct what happened during investigations. </p><h2>Automatically Auditing Entity Framework Core</h2><p>One of the easiest ways to implement audit logging is by intercepting Entity Framework Core changes.</p><p>Every tracked entity already knows whether it has been:</p><ul><li><p>Added</p></li><li><p>Modified</p></li><li><p>Deleted</p></li></ul><p>This makes EF Core an excellent source of audit events. </p><h2>Detecting Changes</h2><p>Example:</p><pre><code><code>foreach (var entry in
    ChangeTracker.Entries())
{
    if (entry.State ==
        EntityState.Modified)
    {
        // Create audit record
    }
}</code></code></pre><p>Instead of manually writing audit code throughout the application, you centralize it in one place.</p><p>This greatly simplifies maintenance. </p><h2>Overriding SaveChanges</h2><p>A common pattern is overriding SaveChangesAsync.</p><p>Example:</p><pre><code><code>public override async Task&lt;int&gt;
SaveChangesAsync(
CancellationToken cancellationToken)
{
    CreateAuditEntries();

    return await
        base.SaveChangesAsync(
            cancellationToken);
}</code></code></pre><p>Every database change automatically produces an audit record.</p><p>Developers no longer need to remember to create one manually. </p><h2>Capturing Old and New Values</h2><p>Suppose a customer changes their email address.</p><p>Instead of recording only:</p><blockquote><p>Customer updated</p></blockquote><p>Capture:</p><pre><code><code>Old:
john@example.com

New:
john.smith@example.com</code></code></pre><p>This information becomes invaluable during troubleshooting. </p><h2>Recording Authentication Events</h2><p>Authentication activity should also be audited.</p><p>Examples include:</p><ul><li><p>Successful logins</p></li><li><p>Failed logins</p></li><li><p>Account lockouts</p></li><li><p>Password resets</p></li><li><p>Multi-factor authentication failures</p></li></ul><p>Security teams frequently investigate these events. </p><h2>Recording Authorization Events</h2><p>Authorization changes often have significant business impact.</p><p>Examples include:</p><ul><li><p>Administrator granted</p></li><li><p>Permission revoked</p></li><li><p>Role changed</p></li><li><p>Access denied</p></li></ul><p>These records help identify accidental or unauthorized permission changes. </p><h2>Soft Deletes vs Hard Deletes</h2><p>Deleting data permanently removes valuable evidence.</p><p>Many organizations prefer soft deletes.</p><p>Instead of removing a record:</p><pre><code><code>IsDeleted = true</code></code></pre><p>The audit log records:</p><ul><li><p>Who deleted it </p></li><li><p>When </p></li><li><p>Why </p></li></ul><p>The record remains recoverable if needed. </p><h2>Tamper Resistance</h2><p>Audit logs should not be easy to modify.</p><p>Otherwise attackers can simply erase their tracks.</p><p>Common approaches include:</p><ul><li><p>Write-once storage</p></li><li><p>Append-only databases</p></li><li><p>Restricted permissions</p></li><li><p>Digital signatures</p></li></ul><p>An audit log should be treated as evidence. </p><h2>Separating Audit Logs from Application Logs</h2><p>Do not mix audit logs with application diagnostics.</p><p>Instead:</p><ul><li><p>Application logs &#8594; troubleshooting</p></li><li><p>Audit logs &#8594; accountability</p></li></ul><p>Keeping them separate improves security and simplifies compliance reporting. </p><h2>Correlation IDs</h2><p>Modern applications often span multiple services.</p><p>A single customer request might involve:</p><ul><li><p>API Gateway</p></li><li><p>Orders Service</p></li><li><p>Inventory Service</p></li><li><p>Payment Service</p></li></ul><p>Using a Correlation ID allows every audit record to be connected to the same business operation. </p><p>Example:</p><pre><code><code>var correlationId =
    HttpContext.TraceIdentifier;</code></code></pre><p>This becomes especially useful in distributed systems.</p><h2>Audit Logging in Microservices</h2><p>Microservices introduce new challenges.</p><p>Each service produces its own audit events.</p><p>Questions include:</p><ul><li><p>Should every service keep its own audit log?</p></li><li><p>Should logs be centralized?</p></li><li><p>How are timestamps synchronized?</p></li></ul><p>Many organizations collect audit events into a centralized platform for easier analysis. </p><h2>Asynchronous Audit Logging</h2><p>Writing audit records synchronously can slow applications.</p><p>Instead consider:</p><ul><li><p>Background queues</p></li><li><p>Azure Service Bus</p></li><li><p>RabbitMQ</p></li></ul><p>Applications remain responsive while audit records are written in the background.</p><p>This connects nicely with our earlier article on messaging systems. </p><h2>Protecting Audit Data</h2><p>Audit logs themselves often contain sensitive information.</p><p>Protect them using:</p><ul><li><p>Encryption</p></li><li><p>Restricted access</p></li><li><p>Backup policies</p></li><li><p>Data retention rules</p></li></ul><p>Never assume audit data is harmless. </p><h2>Compliance Requirements</h2><p>Many regulations require organizations to maintain audit trails.</p><p>Examples include:</p><h3>GDPR</h3><p><a href="https://gdpr.eu/">Requires accountability for personal data access.</a></p><h3>HIPAA</h3><p><a href="https://www.hhs.gov/hipaa">Requires auditing of healthcare information.</a></p><h3>PCI DSS</h3><p><a href="https://www.pcisecuritystandards.org/">Requires tracking access to payment information.</a></p><h3>SOC 2</h3><p><a href="https://www.aicpa.org/">Emphasizes monitoring and accountability.</a></p><p>Audit logging helps organizations demonstrate compliance.</p><h2>What Audit Logs Should Not Contain</h2><p>Avoid recording:</p><ul><li><p>Passwords</p></li><li><p>Encryption keys</p></li><li><p>Credit card numbers</p></li><li><p>Authentication tokens</p></li><li><p>Sensitive secrets</p></li></ul><p>Instead record enough information to investigate activity without exposing confidential data. </p><h2>Common Mistakes</h2><p>Several mistakes appear repeatedly.</p><h3>Logging Too Much</h3><p>Huge audit tables become difficult to search.</p><h3>Logging Too Little</h3><p>Critical investigations become impossible.</p><h3>Missing User Information</h3><p>An action without a user has little value.</p><h3>Ignoring Failed Attempts</h3><p>Unauthorized access attempts often matter as much as successful ones. </p><h2>Real-World Example</h2><p>Imagine a banking application.</p><p>A customer transfers money.</p><p>The audit log records:</p><ul><li><p>User identity</p></li><li><p>Source account</p></li><li><p>Destination account</p></li><li><p>Amount</p></li><li><p>Timestamp</p></li><li><p>IP address</p></li><li><p>Approval status</p></li><li><p>Correlation ID</p></li></ul><p>Months later an investigation occurs.</p><p>The organization can reconstruct the entire transaction history within seconds. </p><h2>How Audit Logging Supports Earlier Topics</h2><p>This article connects naturally with several recent posts.</p><p>Zero Trust verifies identities.</p><p>Authorization controls permissions.</p><p>Data Protection secures information.</p><p>Audit Logging records every important action.</p><p>Distributed Tracing follows requests across services.</p><p>Together these practices create applications that are secure, observable, and accountable. </p><h2>Best Practices Checklist</h2><p>When designing an audit logging system:</p><ul><li><p>Audit important business events rather than every operation.</p></li><li><p>Capture user identity and request context.</p></li><li><p>Record previous and new values where appropriate.</p></li><li><p>Keep audit logs separate from application logs.</p></li><li><p>Protect audit records from unauthorized modification.</p></li><li><p>Encrypt sensitive audit data.</p></li><li><p>Use asynchronous processing for high-volume systems.</p></li><li><p>Define retention policies that satisfy compliance requirements.</p></li><li><p>Regularly review audit logs for suspicious activity.</p></li></ul><p>Following these practices helps build audit systems that remain valuable as applications grow. </p><h2>Closing Thoughts</h2><p>Building secure applications involves much more than preventing unauthorized access.</p><p>Organizations also need visibility into what happens after users sign in.</p><p>Audit logging provides that visibility by recording important events in a consistent, reliable, and searchable way.</p><p>ASP.NET Core offers all the building blocks needed to create robust audit logging systems, especially when combined with Entity Framework Core, authentication, authorization, and distributed tracing.</p><p>As your applications grow in complexity, audit logging becomes one of the most valuable tools for troubleshooting, security investigations, and regulatory compliance.</p><p>Knowing what happened yesterday often determines how quickly you can solve tomorrow&#8217;s problem. </p><h2>Join The Community</h2><p>Enjoyed this article? <a href="https://www.asptoday.com/">Subscribe to </a><strong><a href="https://www.asptoday.com/">ASP Today</a></strong> for practical ASP.NET Core architecture, security, and enterprise development guides. Join the Substack Chat to discuss modern ASP.NET Core development with developers building secure, scalable applications. </p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.asptoday.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.asptoday.com/subscribe?"><span>Subscribe now</span></a></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Advanced Authorization in ASP.NET Core: Policy-Based and Attribute-Based Access Control]]></title><description><![CDATA[Go beyond simple roles with policy-based and attribute-based authorization in ASP.NET Core. Learn how to build secure, flexible access control for modern applications. #ASPToday #aspnetcore #authorization #dotnet #security]]></description><link>https://www.asptoday.com/p/advanced-authorization-in-aspnet</link><guid isPermaLink="false">https://www.asptoday.com/p/advanced-authorization-in-aspnet</guid><dc:creator><![CDATA[Jenny Muralidharan]]></dc:creator><pubDate>Tue, 30 Jun 2026 15:02:36 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!4Cr2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe92f7896-514e-4ae8-a31a-d3ce67da551f_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Knowing who a user is is only half of the security story. Modern applications must also decide what each user is allowed to do. As systems grow in size and complexity, simple role-based authorization often becomes difficult to maintain. <a href="https://www.asptoday.com/p/introduction-to-aspnet-core-whats-new-and-why-it-matters">ASP.NET Core</a> provides powerful authorization features that allow developers to build flexible, secure access control using policies, custom requirements, and authorization attributes. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4Cr2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe92f7896-514e-4ae8-a31a-d3ce67da551f_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4Cr2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe92f7896-514e-4ae8-a31a-d3ce67da551f_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!4Cr2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe92f7896-514e-4ae8-a31a-d3ce67da551f_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!4Cr2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe92f7896-514e-4ae8-a31a-d3ce67da551f_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!4Cr2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe92f7896-514e-4ae8-a31a-d3ce67da551f_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4Cr2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe92f7896-514e-4ae8-a31a-d3ce67da551f_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e92f7896-514e-4ae8-a31a-d3ce67da551f_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2553333,&quot;alt&quot;:&quot;Theme park guests use different wristbands to access rides and VIP areas. &quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.asptoday.com/i/203939378?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe92f7896-514e-4ae8-a31a-d3ce67da551f_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Theme park guests use different wristbands to access rides and VIP areas. " title="Theme park guests use different wristbands to access rides and VIP areas. " srcset="https://substackcdn.com/image/fetch/$s_!4Cr2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe92f7896-514e-4ae8-a31a-d3ce67da551f_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!4Cr2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe92f7896-514e-4ae8-a31a-d3ce67da551f_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!4Cr2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe92f7896-514e-4ae8-a31a-d3ce67da551f_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!4Cr2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe92f7896-514e-4ae8-a31a-d3ce67da551f_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In this guide, you&#8217;ll learn how advanced authorization works, when to use policy-based and attribute-based approaches, and how to build scalable authorization rules for real-world applications. </p><h2>Authentication vs Authorization</h2><p>These two concepts are closely related but solve different problems.</p><p>Authentication answers:</p><blockquote><p>Who are you?</p></blockquote><p>Authorization answers:</p><blockquote><p>What are you allowed to do?</p></blockquote><p>Imagine entering an airport.</p><p>Showing your passport proves your identity.</p><p>That is authentication.</p><p>Your boarding pass determines which gate you may enter and whether you can access the airline lounge.</p><p>That is authorization.</p><p>ASP.NET Core separates these responsibilities, making applications easier to secure and maintain.</p><h2>Why Authorization Becomes Difficult</h2><p>Small applications often begin with simple roles.</p><p>Examples include:</p><ul><li><p>Administrator</p></li><li><p>Manager</p></li><li><p>Employee</p></li><li><p>Customer</p></li></ul><p>Initially this works well.</p><p>As applications grow, new questions appear.</p><p>Can managers approve expenses over $10,000?</p><p>Can support engineers edit customer information but not delete it?</p><p>Can finance users export reports only during business hours?</p><p>Can users manage only projects belonging to their own department?</p><p>Roles alone cannot answer these questions cleanly.</p><h2>Understanding Role-Based Authorization</h2><p>ASP.NET Core supports role-based authorization out of the box.</p><p>Example:</p><pre><code><code>[Authorize(Roles = "Administrator")]
public IActionResult DeleteUser()
{
    return Ok();
}</code></code></pre><p>Only users belonging to the Administrator role can access this action.</p><p>Simple.</p><p>Easy to understand.</p><p>Perfect for straightforward scenarios.</p><p>However, real businesses usually require more flexibility.</p><h2>The Limitations of Roles</h2><p>Imagine an organization with:</p><ul><li><p>Sales Manager </p></li><li><p>Regional Sales Manager </p></li><li><p>Global Sales Manager </p></li><li><p>Finance Manager </p></li><li><p>HR Manager</p></li><li><p>Support Manager </p></li><li><p>Operations Manager<br></p></li></ul><p>Soon the number of roles begins to grow rapidly.</p><p>Eventually you encounter:</p><ul><li><p>Duplicate permissions </p></li><li><p>Conflicting responsibilities </p></li><li><p>Difficult maintenance</p></li><li><p>Authorization bugs <br></p></li></ul><p>This is often called <strong>role explosion</strong>.</p><p>Policies solve this problem.</p><h2>What Is Policy-Based Authorization?</h2><p>A policy defines rules instead of roles.</p><p>Instead of asking:</p><p>&#8220;Is this user an Administrator?&#8221;</p><p>You ask:</p><p>&#8220;Does this user satisfy these requirements?&#8221;</p><p>A policy can evaluate:</p><ul><li><p>Claims </p></li><li><p>Roles </p></li><li><p>Permissions </p></li><li><p>Custom business rules </p></li><li><p>Time of day </p></li><li><p>Department </p></li><li><p>Resource ownership<br></p></li></ul><p>Policies provide much greater flexibility.</p><h2>Creating a Policy</h2><p>Example:</p><pre><code><code>builder.Services.AddAuthorization(options =&gt;
{
    options.AddPolicy(
        "CanApproveOrders",
        policy =&gt;
        {
            policy.RequireClaim(
                "permission",
                "orders.approve");
        });
});</code></code></pre><p>The policy now checks whether the user has the required permission claim.</p><h2>Applying a Policy</h2><pre><code><code>[Authorize(Policy = "CanApproveOrders")]
public IActionResult Approve()
{
    return Ok();
}</code></code></pre><p>Notice how the controller contains no business logic.</p><p>The authorization system handles everything.</p><h2>Understanding Claims</h2><p>Claims describe information about a user.</p><p>Examples include:</p><ul><li><p>Name </p></li><li><p>Department </p></li><li><p>Country </p></li><li><p>Permission </p></li><li><p>Subscription Level </p></li><li><p>Employee Number <br></p></li></ul><p>Unlike roles, claims provide detailed information that policies can evaluate.</p><h2>Claims-Based Authorization</h2><p>Suppose users receive:</p><pre><code><code>Department = Finance

Permission = ApproveInvoices</code></code></pre><p>Your policy can require either or both.</p><p>This creates much more granular authorization.</p><h2>Combining Multiple Requirements</h2><p>Policies may require multiple conditions.</p><p>Example:</p><pre><code><code>options.AddPolicy(
    "SeniorFinance",
    policy =&gt;
    {
        policy.RequireRole("Manager");

        policy.RequireClaim(
            "Department",
            "Finance");
    });</code></code></pre><p>Only finance managers satisfy the policy.</p><h2>Custom Authorization Requirements</h2><p>Sometimes business rules become very specific.</p><p>Example:</p><p>A project manager may edit only projects belonging to their own region.</p><p>This requires custom authorization.</p><p>Create a requirement:</p><pre><code><code>public class RegionRequirement
    : IAuthorizationRequirement
{
}</code></code></pre><p>Then implement an authorization handler.</p><p>The handler contains your business logic.</p><p>This keeps controllers clean and reusable.</p><h2>Resource-Based Authorization</h2><p>Sometimes authorization depends on the resource itself.</p><p>Example:</p><p>A customer may edit only their own profile.</p><p>Not someone else&#8217;s.</p><p>Instead of checking only user claims, ASP.NET Core evaluates:</p><ul><li><p>User </p></li><li><p>Resource </p></li><li><p>Policy <br></p></li></ul><p>Together.</p><p>This is called resource-based authorization.</p><h2>Attribute-Based Access Control (ABAC)</h2><p>Attribute-Based Access Control goes even further.</p><p>Instead of checking only roles or permissions, it evaluates attributes.</p><p>Examples include:</p><p>User attributes:</p><ul><li><p>Department </p></li><li><p>Clearance level </p></li><li><p>Country<br></p></li></ul><p>Resource attributes:</p><ul><li><p>Owner </p></li><li><p>Classification </p></li><li><p>Status<br></p></li></ul><p>Environment attributes:</p><ul><li><p>Time </p></li><li><p>Device </p></li><li><p>Network location<br></p></li></ul><p>Actions:</p><ul><li><p>Read </p></li><li><p>Update </p></li><li><p>Delete<br></p></li></ul><p>All these factors influence authorization decisions.</p><h2>Example Scenario</h2><p>Suppose an employee wants to download payroll information.</p><p>Rules:</p><ul><li><p>Must belong to HR </p></li><li><p>Must use a company device </p></li><li><p>Must be inside the corporate network </p></li><li><p>Must access during working hours<br></p></li></ul><p>A single role cannot express this.</p><p>Attribute-based authorization can.</p><h2>Authorization Handlers</h2><p>Authorization handlers evaluate custom requirements.</p><p>Example:</p><pre><code><code>public class MinimumAgeHandler
    : AuthorizationHandler&lt;
        MinimumAgeRequirement&gt;
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        MinimumAgeRequirement requirement)
    {
        // Validation logic

        return Task.CompletedTask;
    }
}</code></code></pre><p>Handlers keep authorization logic centralized.</p><h2>Dynamic Policies</h2><p>Large enterprise systems often load permissions from databases.</p><p>Instead of hardcoding every policy, applications generate them dynamically.</p><p>Benefits include:</p><ul><li><p>Easier administration </p></li><li><p>Less code duplication </p></li><li><p>Business-managed permissions<br></p></li></ul><p>This approach is common in SaaS platforms.</p><h2>Authorization in Minimal APIs</h2><p>Policies also work with Minimal APIs.</p><p>Example:</p><pre><code><code>app.MapGet("/reports", () =&gt;
{
    return Results.Ok();
})
.RequireAuthorization("CanViewReports");</code></code></pre><p>Security remains consistent across application styles.</p><h2>Authorization and Microservices</h2><p>In a microservices architecture, authorization becomes distributed.</p><p>Each service should verify:</p><ul><li><p>Identity</p></li><li><p>Permissions </p></li><li><p>Claims<br></p></li></ul><p>Never assume another service has already performed sufficient authorization.</p><p>This aligns with the Zero Trust principles discussed in our previous article.</p><h2>Combining Authentication and Authorization</h2><p>Authentication produces identity.</p><p>Authorization evaluates identity.</p><p>Neither replaces the other.</p><p>Together they provide layered security.</p><h2>Common Authorization Mistakes</h2><p>Avoid:</p><ul><li><p>Hardcoding permissions </p></li><li><p>Mixing authorization into controllers </p></li><li><p>Relying only on roles </p></li><li><p>Trusting client-side validation </p></li><li><p>Forgetting API authorization <br></p></li></ul><p>Centralized authorization policies improve consistency.</p><h2>Testing Authorization</h2><p>Authorization should be tested just like business logic.</p><p>Verify:</p><ul><li><p>Authorized users succeed. </p></li><li><p>Unauthorized users receive <strong>403 Forbidden</strong>. </p></li><li><p>Anonymous users receive <strong>401 Unauthorized</strong> when appropriate.<br></p></li></ul><p>Automated tests help prevent accidental permission changes.</p><h2>Real-World Example</h2><p>Imagine a project management platform.</p><p>Users include:</p><ul><li><p>Developers </p></li><li><p>Team Leads </p></li><li><p>Project Managers </p></li><li><p>Clients<br></p></li></ul><p>Developers may edit only assigned tasks.</p><p>Project Managers approve budgets.</p><p>Clients view only their own projects.</p><p>Finance exports reports.</p><p>Instead of creating dozens of overlapping roles, policies evaluate permissions, ownership, and department.</p><p>The authorization model remains clean even as the application grows.</p><h2>Authorization and Compliance</h2><p>Strong authorization also supports compliance requirements.</p><p>Frameworks such as:</p><ul><li><p>GDPR </p></li><li><p>HIPAA </p></li><li><p>SOC 2 </p></li><li><p>PCI DSS<br></p></li></ul><p>Require organizations to restrict access to sensitive information.</p><p>Policy-based authorization helps enforce these restrictions consistently.</p><h2>How This Fits Your ASP.NET Core Journey</h2><p>Over the past several articles we&#8217;ve explored:</p><ul><li><p>Zero Trust Architecture </p></li><li><p>Data Protection </p></li><li><p>Encryption </p></li><li><p>Key Management </p></li><li><p>Secure Microservices<br></p></li></ul><p>Advanced authorization builds directly on those concepts.</p><p>Authentication proves identity.</p><p>Encryption protects data.</p><p>Authorization determines who can use that data.</p><p>Together they form the foundation of secure ASP.NET Core applications.</p><h2>Closing Thoughts</h2><p>Authorization is much more than checking whether a user is an administrator.</p><p>Modern applications require flexible, maintainable access control that adapts to changing business requirements.</p><p>ASP.NET Core&#8217;s policy-based and attribute-based authorization features provide a scalable way to secure APIs, web applications, and distributed systems without filling controllers with complex permission logic.</p><p>As your applications grow, investing in a strong authorization strategy pays dividends in security, maintainability, and compliance.</p><h2>Join The Community</h2><p>Enjoyed this article? <a href="https://www.asptoday.com/">Subscribe to </a><strong><a href="https://www.asptoday.com/">ASP Today</a></strong> for practical ASP.NET Core architecture, security, and cloud-native development guides. Join the Substack Chat to discuss modern ASP.NET Core development with developers from around the world. </p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.asptoday.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.asptoday.com/subscribe?"><span>Subscribe now</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Data Protection in ASP.NET Core: Encryption, Key Management, and Compliance]]></title><description><![CDATA[Learn how to secure sensitive data with encryption, key management, ASP.NET Core Data Protection, and compliance best practices. #ASPToday #aspnetcore #cybersecurity #dataprotection #dotnet]]></description><link>https://www.asptoday.com/p/data-protection-in-aspnet-core-encryption</link><guid isPermaLink="false">https://www.asptoday.com/p/data-protection-in-aspnet-core-encryption</guid><dc:creator><![CDATA[Jenny Muralidharan]]></dc:creator><pubDate>Tue, 23 Jun 2026 15:02:54 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!JSda!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F939cf221-63d8-4209-8af8-578af69036d2_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Data is one of the most valuable assets any application manages. Whether you&#8217;re storing customer information, authentication tokens, financial records, or business data, protecting that information is essential. Modern <a href="https://www.asptoday.com/">ASP.NET Core</a> applications must not only secure data against attackers, but also satisfy regulatory and compliance requirements.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!JSda!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F939cf221-63d8-4209-8af8-578af69036d2_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JSda!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F939cf221-63d8-4209-8af8-578af69036d2_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!JSda!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F939cf221-63d8-4209-8af8-578af69036d2_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!JSda!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F939cf221-63d8-4209-8af8-578af69036d2_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!JSda!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F939cf221-63d8-4209-8af8-578af69036d2_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!JSda!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F939cf221-63d8-4209-8af8-578af69036d2_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/939cf221-63d8-4209-8af8-578af69036d2_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2255240,&quot;alt&quot;:&quot;Child securing a piggy bank with locks, keys, and shields symbolizing data protection. &quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.asptoday.com/i/202950612?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F939cf221-63d8-4209-8af8-578af69036d2_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Child securing a piggy bank with locks, keys, and shields symbolizing data protection. " title="Child securing a piggy bank with locks, keys, and shields symbolizing data protection. " srcset="https://substackcdn.com/image/fetch/$s_!JSda!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F939cf221-63d8-4209-8af8-578af69036d2_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!JSda!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F939cf221-63d8-4209-8af8-578af69036d2_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!JSda!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F939cf221-63d8-4209-8af8-578af69036d2_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!JSda!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F939cf221-63d8-4209-8af8-578af69036d2_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In this guide, we&#8217;ll explore how ASP.NET Core supports data protection through encryption, secure key management, and practical compliance strategies that help keep sensitive information safe.</p><h2>Why Data Protection Matters</h2><p>Every application stores data.</p><p>Examples include:</p><ul><li><p>Customer profiles</p></li><li><p>Login credentials</p></li><li><p>Payment information</p></li><li><p>Business records</p></li><li><p>Application secrets</p></li><li><p>Session tokens</p></li></ul><p>If this information becomes exposed, organizations can face:</p><ul><li><p>Financial losses</p></li><li><p>Reputational damage</p></li><li><p>Regulatory penalties</p></li><li><p>Customer distrust</p></li></ul><p>Data protection is no longer optional.</p><p>It is a fundamental requirement of modern software development.</p><h2>The Expanding Risk Landscape</h2><p>Modern applications rarely exist on a single server.</p><p>Today&#8217;s ASP.NET Core applications often interact with:</p><ul><li><p><a href="https://www.asptoday.com/p/the-role-of-aspnet-core-in-cloud">Cloud databases</a></p></li><li><p><a href="https://www.asptoday.com/p/microservices-architecture-with-aspnet">Microservices</a></p></li><li><p>Message queues</p></li><li><p>Third-party APIs</p></li><li><p>Mobile applications</p></li><li><p>Browser clients</p></li></ul><p>Each connection introduces new opportunities for data exposure.</p><p>As systems become more distributed, protecting data becomes more challenging.</p><p>This builds naturally on concepts we explored in:</p><ul><li><p><a href="https://www.asptoday.com/p/securing-microservices-in-aspnet">Securing Microservices in ASP.NET Core</a></p></li><li><p><a href="https://www.asptoday.com/p/deep-dive-into-distributed-tracing">Distributed Tracing with OpenTelemetry</a></p></li><li><p><a href="https://www.asptoday.com/p/designing-fault-tolerant-systems">Fault-Tolerant Systems in ASP.NET Core</a></p></li></ul><p>Security must exist throughout the entire application lifecycle.</p><h2>Understanding Data Protection</h2><p>Data protection focuses on ensuring information remains:</p><ul><li><p>Confidential</p></li><li><p>Accurate</p></li><li><p>Available</p></li></ul><p>These goals align closely with the CIA Triad:</p><h3>Confidentiality</h3><p>Only authorized users can access information.</p><h3>Integrity</h3><p>Data cannot be altered without authorization.</p><h3>Availability</h3><p>Authorized users can access data when needed.</p><p>Strong protection strategies balance all three requirements.</p><h2>Encryption: The Foundation of Data Protection</h2><p>Encryption converts readable information into an unreadable format.</p><p>Without the correct key, encrypted data becomes useless to attackers.</p><p>For example:</p><pre><code><code>Original:
CustomerPassword123

Encrypted:
Qm1kT3dQVnNQaW5GcXhaZw==</code></code></pre><p>While this example is simplified, the principle remains the same.</p><p>Encryption protects sensitive information even if storage systems become compromised.</p><h2>Data at Rest vs Data in Transit</h2><p>When discussing encryption, two scenarios matter most.</p><h3>Data at Rest</h3><p>Data stored in:</p><ul><li><p>Databases </p></li><li><p>Files </p></li><li><p>Backups </p></li><li><p>Cloud storage<br></p></li></ul><p>Examples:</p><ul><li><p>SQL Server records </p></li><li><p>Azure Blob Storage </p></li><li><p>Backup archives<br></p></li></ul><h3>Data in Transit</h3><p>Data moving between systems.</p><p>Examples:</p><ul><li><p>Browser to API </p></li><li><p>API to database </p></li><li><p>Service to service communication </p></li><li><p>Message queue traffic<br></p></li></ul><p>Both require protection.</p><p>Securing one without the other leaves gaps.</p><h2>Encrypting Data in Transit</h2><p>HTTPS should be mandatory for every ASP.NET Core application.</p><p>HTTPS uses TLS (Transport Layer Security) to encrypt communication.</p><p>Benefits include:</p><ul><li><p>Preventing eavesdropping </p></li><li><p>Preventing data tampering </p></li><li><p>Verifying server identity<br></p></li></ul><p>ASP.NET Core makes HTTPS straightforward.</p><pre><code><code>app.UseHttpsRedirection();</code></code></pre><p>Microsoft documentation:</p><p>https://learn.microsoft.com/aspnet/core/security/enforcing-ssl</p><h2>HTTP Strict Transport Security (HSTS)</h2><p>HSTS tells browsers to always use HTTPS.</p><pre><code><code>if (!app.Environment.IsDevelopment())
{
    app.UseHsts();
}</code></code></pre><p>This prevents downgrade attacks where attackers attempt to force insecure connections.</p><h2>Encrypting Data at Rest</h2><p>Data at rest remains vulnerable if storage systems are compromised.</p><p>Common approaches include:</p><ul><li><p>Database encryption </p></li><li><p>Disk encryption </p></li><li><p>File encryption </p></li><li><p>Application-level encryption<br></p></li></ul><p>Each protects different layers of the system.</p><h2>SQL Server Encryption</h2><p>SQL Server supports:</p><ul><li><p>Transparent Data Encryption (TDE) </p></li><li><p>Column encryption </p></li><li><p>Always Encrypted<br></p></li></ul><p><a href="https://learn.microsoft.com/en-us/sql/relational-databases/security/encryption/sql-server-encryption?view=sql-server-ver17">Official documentation</a>:</p><p>TDE encrypts database files automatically.</p><p>Applications require minimal changes.</p><h2>When Application-Level Encryption Is Necessary</h2><p>Sometimes specific fields require stronger protection.</p><p>Examples include:</p><ul><li><p>National identification numbers </p></li><li><p>Payment data </p></li><li><p>Medical information </p></li><li><p>API credentials<br></p></li></ul><p>In these cases, encrypting sensitive values before storage provides additional security.</p><h4>Example: AES Encryption</h4><pre><code><code>using System.Security.Cryptography;
using System.Text;

public static string Encrypt(
    string plaintext,
    byte[] key,
    byte[] iv)
{
    using var aes = Aes.Create();

    aes.Key = key;
    aes.IV = iv;

    using var encryptor =
        aes.CreateEncryptor();

    using var ms = new MemoryStream();

    using var cs = new CryptoStream(
        ms,
        encryptor,
        CryptoStreamMode.Write);

    using var sw = new StreamWriter(cs);

    sw.Write(plaintext);

    return Convert.ToBase64String(
        ms.ToArray());
}</code></code></pre><p>This demonstrates the concept of application-level encryption.</p><p>Production implementations require additional safeguards and validation.</p><h2>Introducing ASP.NET Core Data Protection</h2><p>ASP.NET Core includes a built-in Data Protection system.</p><p>The framework uses it internally for:</p><ul><li><p>Authentication cookies </p></li><li><p>Session data </p></li><li><p>CSRF protection </p></li><li><p>Temporary tokens<br></p></li></ul><p><a href="https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/introduction?view=aspnetcore-10.0">Official documentation</a>:</p><p>This system simplifies secure encryption and key management.</p><h2>Configuring Data Protection</h2><p>Basic setup:</p><pre><code><code>builder.Services
    .AddDataProtection();</code></code></pre><p>The framework automatically generates and manages encryption keys.</p><h2>Protecting Sensitive Values</h2><p>Example:</p><pre><code><code>public class SecretService
{
    private readonly IDataProtector _protector;

    public SecretService(
        IDataProtectionProvider provider)
    {
        _protector =
            provider.CreateProtector(
                "CustomerData");
    }

    public string Protect(string value)
    {
        return _protector.Protect(value);
    }

    public string Unprotect(string value)
    {
        return _protector.Unprotect(value);
    }
}</code></code></pre><p>This approach avoids many common encryption implementation mistakes.</p><h2>Why Key Management Matters</h2><p>Encryption is only as strong as its keys.</p><p>Many breaches occur because keys are poorly managed.</p><p>Common mistakes include:</p><ul><li><p>Storing keys in source code</p></li><li><p>Sharing keys between environments </p></li><li><p>Never rotating keys </p></li><li><p>Exposing keys in configuration files<br></p></li></ul><p>Protecting encryption keys is often more important than protecting the encrypted data itself.</p><h2>Understanding Key Rotation</h2><p>Keys should not remain active forever.</p><p>Key rotation involves:</p><ul><li><p>Creating new keys </p></li><li><p>Retiring old keys </p></li><li><p>Re-encrypting data when appropriate<br></p></li></ul><p>Benefits include:</p><ul><li><p>Reduced exposure </p></li><li><p>Limited attack windows </p></li><li><p>Improved compliance <br></p></li></ul><p>ASP.NET Core Data Protection supports automatic key rotation.</p><h2>Storing Keys Securely</h2><p>Development environments often store keys locally.</p><p>Production environments require stronger solutions.</p><p>Options include:</p><ul><li><p>Azure Key Vault </p></li><li><p>AWS KMS </p></li><li><p>Hardware Security Modules (HSMs) </p></li><li><p>Dedicated secret stores<br></p></li></ul><h2>Using Azure Key Vault</h2><p>Azure Key Vault provides:</p><ul><li><p>Secure secret storage </p></li><li><p>Key management </p></li><li><p>Certificate management </p></li><li><p>Access control<br></p></li></ul><p><a href="https://learn.microsoft.com/en-us/azure/key-vault/">Official documentation</a>:</p><p>Key Vault integrates well with ASP.NET Core applications.</p><h2>Configuring Azure Key Vault</h2><p>Example:</p><pre><code><code>builder.Configuration
    .AddAzureKeyVault(
        new Uri(vaultUrl),
        new DefaultAzureCredential());</code></code></pre><p>This removes sensitive secrets from application configuration files.</p><h2>Protecting Configuration Data</h2><p>Configuration often contains:</p><ul><li><p>API keys </p></li><li><p>Database connection strings </p></li><li><p>Service credentials<br></p></li></ul><p>Never commit these values to source control.</p><p>Instead use:</p><ul><li><p>Secret managers </p></li><li><p>Environment variables </p></li><li><p>Key Vault solutions<br></p></li></ul><p><a href="https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-10.0&amp;tabs=windows%2Cpowershell">ASP.NET Core Secret Manager</a></p><h2>Data Protection in Distributed Systems</h2><p>Modern applications frequently operate across multiple services.</p><p>Examples include:</p><ul><li><p>Microservices </p></li><li><p>Background workers </p></li><li><p>Message processors<br></p></li></ul><p>These systems often need access to shared encryption keys.</p><p>Careful planning becomes essential.</p><p>Improper key distribution can create significant security risks.</p><h2>Compliance Requirements</h2><p>Many organizations must satisfy regulatory requirements.</p><p>Common examples include:</p><h3>GDPR</h3><p><a href="https://gdpr.eu/">Protects personal data for EU residents</a>.</p><h3>HIPAA</h3><p><a href="https://www.hhs.gov/hipaa/index.html">Protects healthcare information in the United States</a>.</p><h3>PCI DSS</h3><p><a href="https://www.pcisecuritystandards.org/">Protects payment card information</a>.</p><h3>SOC 2</h3><p><a href="https://www.aicpa-cima.com/">Focuses on security and operational controls</a>.</p><p>Compliance requirements often influence data protection strategies.</p><h2>Encryption Alone Does Not Guarantee Compliance</h2><p>A common misconception is:</p><blockquote><p>&#8220;We encrypted the data, so we&#8217;re compliant.&#8221;</p></blockquote><p>Compliance involves much more.</p><p>Organizations must also consider:</p><ul><li><p>Access controls </p></li><li><p>Audit logging </p></li><li><p>Data retention </p></li><li><p>Incident response </p></li><li><p>Monitoring<br></p></li></ul><p>Encryption is only one piece of the puzzle.</p><h2>Auditing and Monitoring</h2><p>Data access should be traceable.</p><p>Important events include:</p><ul><li><p>Login attempts </p></li><li><p>Data modifications </p></li><li><p>Key access </p></li><li><p>Permission changes<br></p></li></ul><p>Audit trails support:</p><ul><li><p>Security investigations </p></li><li><p>Compliance reporting </p></li><li><p>Incident response<br></p></li></ul><h2>Least Privilege Access</h2><p>Not every user should access every piece of data.</p><p>Apply least privilege principles:</p><ul><li><p>Grant only required permissions </p></li><li><p>Remove unnecessary access </p></li><li><p>Review permissions regularly<br></p></li></ul><p>This aligns closely with the Zero Trust concepts discussed in our previous article.</p><h2>Protecting Backups</h2><p>Backups often contain the same sensitive data as production systems.</p><p>Organizations sometimes secure production databases while neglecting backups.</p><p>Backups should be:</p><ul><li><p>Encrypted </p></li><li><p>Access controlled </p></li><li><p>Monitored </p></li><li><p>Retained appropriately<br></p></li></ul><p>A compromised backup can be just as damaging as a compromised database.</p><h2>Data Retention and Deletion</h2><p>Keeping data forever creates unnecessary risk.</p><p>Questions to consider:</p><ul><li><p>How long should data be retained? </p></li><li><p>When should records be deleted? </p></li><li><p>Are retention requirements documented?<br></p></li></ul><p>Compliance frameworks frequently require documented retention policies.</p><h2>Real-World Example: Customer Management Platform</h2><p>Imagine a SaaS application storing:</p><ul><li><p>Customer profiles </p></li><li><p>Billing information </p></li><li><p>Authentication data<br></p></li></ul><p>A strong protection strategy might include:</p><ul><li><p>HTTPS everywhere </p></li><li><p>Database encryption </p></li><li><p>Azure Key Vault </p></li><li><p>ASP.NET Core Data Protection </p></li><li><p>Audit logging </p></li><li><p>Automated key rotation<br></p></li></ul><p>If a database backup were stolen, encryption would help prevent attackers from accessing sensitive information.</p><h2>Common Data Protection Mistakes</h2><p>Several mistakes appear repeatedly.</p><h3>Hardcoded Secrets</h3><p>Secrets should never exist in source code.</p><h3>Weak Key Management</h3><p>Protecting data while exposing keys defeats the purpose.</p><h3>Lack of Rotation</h3><p>Long-lived keys increase risk.</p><h3>Unencrypted Backups</h3><p>Backup systems require the same protections as production.</p><h3>Excessive Access</h3><p>Too many users often have unnecessary permissions.</p><p>Avoiding these mistakes significantly improves security.</p><h2>How This Fits Your ASP.NET Core Journey</h2><p>So far, we&#8217;ve explored:</p><ul><li><p>Microservice security </p></li><li><p>Zero Trust Architecture </p></li><li><p>Distributed tracing </p></li><li><p>Chaos engineering </p></li><li><p>Fault tolerance<br></p></li></ul><p>Data protection builds on all of these topics.</p><p>Security focuses on who can access systems.</p><p>Data protection focuses on safeguarding the information those systems manage.</p><p>Together they form a critical foundation for modern cloud-native applications.</p><h2>Closing Thoughts</h2><p>Protecting data is one of the most important responsibilities developers have.</p><p>ASP.NET Core provides powerful tools for:</p><ul><li><p>Encryption </p></li><li><p>Key management </p></li><li><p>Secret storage </p></li><li><p>Data protection<br></p></li></ul><p>Combined with proper compliance practices, monitoring, and access controls, these capabilities help organizations secure sensitive information and reduce risk.</p><p>Security threats will continue to evolve.</p><p>Strong data protection practices ensure your applications remain prepared.</p><h2>Join The Community</h2><p>Enjoyed this article? Subscribe to <a href="https://www.asptoday.com/">ASP Today</a> for practical ASP.NET Core architecture guides, security best practices, and real-world development strategies. Join the Substack Chat and connect with developers building secure and reliable modern applications. </p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.asptoday.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.asptoday.com/subscribe?"><span>Subscribe now</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Securing Microservices in ASP.NET Core: Zero Trust Architecture and Best Practices]]></title><description><![CDATA[Discover Zero Trust Architecture, service-to-service authentication, JWT security, API protection, and best practices for securing ASP.NET Core microservices. #ASPToday #aspnetcore #microservices #zerotrust #cybersecurity]]></description><link>https://www.asptoday.com/p/securing-microservices-in-aspnet</link><guid isPermaLink="false">https://www.asptoday.com/p/securing-microservices-in-aspnet</guid><dc:creator><![CDATA[Jenny Muralidharan]]></dc:creator><pubDate>Tue, 16 Jun 2026 15:02:03 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!gch0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96157193-27da-46d9-878d-86d9eff9de1a_1672x941.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As applications evolve into distributed microservices architectures, security becomes significantly more complex. Instead of protecting a single application, developers must secure dozens of APIs, services, databases, message queues, and communication channels. Traditional security models often assume that systems inside a network can be trusted. Zero Trust Architecture challenges this assumption by requiring every request, user, service, and device to continuously prove its identity. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gch0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96157193-27da-46d9-878d-86d9eff9de1a_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gch0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96157193-27da-46d9-878d-86d9eff9de1a_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!gch0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96157193-27da-46d9-878d-86d9eff9de1a_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!gch0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96157193-27da-46d9-878d-86d9eff9de1a_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!gch0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96157193-27da-46d9-878d-86d9eff9de1a_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gch0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96157193-27da-46d9-878d-86d9eff9de1a_1672x941.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/96157193-27da-46d9-878d-86d9eff9de1a_1672x941.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2460739,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.asptoday.com/i/201933000?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96157193-27da-46d9-878d-86d9eff9de1a_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gch0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96157193-27da-46d9-878d-86d9eff9de1a_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!gch0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96157193-27da-46d9-878d-86d9eff9de1a_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!gch0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96157193-27da-46d9-878d-86d9eff9de1a_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!gch0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F96157193-27da-46d9-878d-86d9eff9de1a_1672x941.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In this guide, we&#8217;ll explore how to secure <a href="https://www.asptoday.com/p/introduction-to-aspnet-core-whats-new-and-why-it-matters">ASP.NET Core</a> microservices using Zero Trust principles and practical security best practices. </p><h2>Why Microservices Change the Security Landscape </h2><p>In a traditional monolithic application, security boundaries are relatively simple.</p><p>Users interact with a single application. That application communicates with a single database. Most communication happens internally.</p><p>Microservices introduce a very different environment.</p><p>A single user request may pass through:</p><ul><li><p>API Gateway</p></li><li><p>Authentication Service</p></li><li><p>Product Service</p></li><li><p>Inventory Service</p></li><li><p>Payment Service</p></li><li><p>Notification Service</p></li><li><p>Message Queue</p></li></ul><p>Each service creates a potential attack surface. Every API endpoint becomes a possible entry point. Every service-to-service call becomes a security concern.</p><p>This is why securing microservices requires a different mindset. </p><h2>The Problem with Traditional Network Trust</h2><p>Historically, many organizations relied on perimeter-based security.</p><p>The model was simple:</p><ul><li><p>Protect the network boundary</p></li><li><p>Trust everything inside</p></li></ul><p>This approach worked reasonably well when systems lived inside a single corporate network.</p><p>Modern cloud-native systems rarely operate this way.</p><p>Today we have:</p><ul><li><p>Cloud infrastructure</p></li><li><p>Hybrid environments</p></li><li><p>Remote workforces</p></li><li><p>Third-party integrations</p></li><li><p>Distributed services</p></li></ul><p>The network perimeter has effectively disappeared. </p><p>Trusting everything inside the network is no longer sufficient. </p><h2>What Is Zero Trust Architecture?</h2><p>Zero Trust follows a simple principle:</p><blockquote><p>Never trust. Always verify.</p></blockquote><p>Every request must be validated regardless of where it originates.</p><p>This applies to:</p><ul><li><p>Users</p></li><li><p>Services</p></li><li><p>Devices</p></li><li><p>Applications</p></li><li><p>APIs</p></li></ul><p>Being inside the network does not automatically grant trust.</p><p>Every interaction must continuously prove its legitimacy.</p><p><a href="https://learn.microsoft.com/security/zero-trust/">Microsoft&#8217;s Zero Trust guidance</a></p><h2>Core Principles of Zero Trust</h2><p>Zero Trust is built around three core ideas.</p><h3>Verify Explicitly</h3><p>Always authenticate and authorize every request.</p><p>Do not rely on network location.</p><p>Do not assume trust. </p><h3>Use Least Privilege Access</h3><p>Grant only the permissions required.</p><p>Nothing more.</p><p>This limits the damage caused by compromised accounts or services. </p><h3>Assume Breach</h3><p>Design systems as though attackers are already inside.</p><p>This encourages stronger monitoring, segmentation, and verification. </p><h2>Why ASP.NET Core Fits Zero Trust Well</h2><p>ASP.NET Core provides several built-in security capabilities:</p><ul><li><p>Authentication middleware</p></li><li><p>Authorization policies</p></li><li><p>Identity integration</p></li><li><p>JWT support</p></li><li><p>OAuth integration</p></li><li><p>OpenID Connect support</p></li></ul><p>These features make ASP.NET Core an excellent platform for implementing Zero Trust principles.</p><p><a href="https://learn.microsoft.com/aspnet/core/security/">Official documentation</a></p><h2>Authentication vs Authorization</h2><p>These concepts are often confused.</p><p>Authentication answers:</p><blockquote><p>Who are you?</p></blockquote><p>Authorization answers:</p><blockquote><p>What are you allowed to do?</p></blockquote><p>Both are essential.</p><p>A user may be authenticated successfully but still lack permission to perform specific actions. </p><h2>JWT Authentication for Microservices</h2><p>JSON Web Tokens (JWTs) are commonly used in microservices environments.</p><p>A user authenticates once.</p><p>The identity provider issues a token. The token accompanies subsequent requests.</p><p>Example:</p><pre><code><code>Authorization: Bearer eyJhbGciOi...</code></code></pre><p>Services validate the token before processing requests. This eliminates the need for centralized session storage. </p><h2>Configuring JWT Authentication</h2><p>ASP.NET Core provides built-in support.</p><pre><code><code>builder.Services.AddAuthentication("Bearer")
    .AddJwtBearer(options =&gt;
    {
        options.Authority =
            "https://identity.example.com";

        options.Audience = "orders-api";
    });</code></code></pre><p>This validates incoming access tokens automatically.</p><h2>Securing APIs with Authorization Policies</h2><p>Role-based authorization works well for many scenarios.</p><p>Example:</p><pre><code><code>[Authorize(Roles = "Administrator")]
public IActionResult DeleteOrder()
{
    return Ok();
}</code></code></pre><p>However, microservices often require more granular controls.</p><p>Policy-based authorization provides greater flexibility.</p><h2>Policy-Based Authorization</h2><p>Example:</p><pre><code><code>builder.Services.AddAuthorization(options =&gt;
{
    options.AddPolicy(
        "CanProcessPayments",
        policy =&gt;
        {
            policy.RequireClaim(
                "permission",
                "payments.process");
        });
});</code></code></pre><p>Controller:</p><pre><code><code>[Authorize(Policy = "CanProcessPayments")]
public IActionResult ProcessPayment()
{
    return Ok();
}</code></code></pre><p>This approach scales well across complex systems.</p><h2>Service-to-Service Authentication</h2><p>Zero Trust applies to services too.</p><p>One common mistake is assuming internal services can trust each other automatically.</p><p>Example:</p><pre><code><code>API Gateway
     &#8595;
Orders Service
     &#8595;
Inventory Service
     &#8595;
Payment Service</code></code></pre><p>Every service should verify the identity of the caller.</p><p>This prevents attackers from moving laterally through compromised systems.</p><h2>Machine-to-Machine Authentication</h2><p>Service communication often uses OAuth 2.0 client credentials flow.</p><p>Instead of users authenticating:</p><ul><li><p>Services authenticate themselves<br></p></li></ul><p>Example workflow:</p><pre><code><code>Inventory Service
     &#8595;
Identity Provider
     &#8595;
Access Token
     &#8595;
Payment Service</code></code></pre><p>This creates secure service-to-service communication.</p><p><a href="https://oauth.net/2/">OAuth documentation</a></p><h2>Mutual TLS (mTLS)</h2><p>Mutual TLS strengthens service authentication.</p><p>Normally:</p><ul><li><p>Client verifies server <br></p></li></ul><p>With mTLS:</p><ul><li><p>Client verifies server </p></li><li><p>Server verifies client </p></li></ul><p>Both parties prove their identities.</p><p>This significantly improves security for internal service communication.</p><h2>API Gateways and Security</h2><p>Most microservices architectures include an API gateway.</p><p>The gateway becomes a central security layer.</p><p>Responsibilities often include:</p><ul><li><p>Authentication </p></li><li><p>Authorization </p></li><li><p>Rate limiting </p></li><li><p>Request validation </p></li><li><p>Threat detection </p></li></ul><p>Popular gateways include:</p><ul><li><p>YARP </p></li><li><p>Kong </p></li><li><p>Azure API Management </p></li></ul><p><a href="https://microsoft.github.io/reverse-proxy">YARP documentation</a></p><h2>Rate Limiting for Protection</h2><p>Rate limiting protects APIs from abuse.</p><p>ASP.NET Core includes built-in rate limiting.</p><p>Example:</p><pre><code><code>builder.Services.AddRateLimiter(options =&gt;
{
    options.AddFixedWindowLimiter(
        "api",
        opt =&gt;
        {
            opt.PermitLimit = 100;
            opt.Window = TimeSpan.FromMinutes(1);
        });
});</code></code></pre><p>This reduces:</p><ul><li><p>Brute-force attacks </p></li><li><p>Abuse </p></li><li><p>Resource exhaustion<br></p></li></ul><h2>Protecting Secrets</h2><p>Secrets are often one of the weakest links.</p><p>Common mistakes include:</p><ul><li><p>Hardcoded passwords </p></li><li><p>Secrets in source control </p></li><li><p>Shared credentials </p></li></ul><p>Instead use:</p><ul><li><p>Azure Key Vault </p></li><li><p>AWS Secrets Manager </p></li><li><p>Environment variables<br></p></li></ul><p><a href="https://learn.microsoft.com/azure/key-vault/">Azure Key Vault</a></p><h2>Secret Rotation</h2><p>Secrets should not live forever.</p><p>Implement regular rotation policies for:</p><ul><li><p>API keys </p></li><li><p>Database credentials </p></li><li><p>Certificates </p></li><li><p>Service accounts<br></p></li></ul><p>Automated rotation reduces risk significantly.</p><h2>Securing Message-Based Systems</h2><p>Many ASP.NET Core applications use:</p><ul><li><p>Azure Service Bus </p></li><li><p>RabbitMQ </p></li><li><p>Kafka </p></li></ul><p>Security must extend to messaging systems.</p><p>Consider:</p><ul><li><p>Authentication </p></li><li><p>Authorization </p></li><li><p>Encryption </p></li><li><p>Auditing </p></li></ul><p>Message queues should never be treated as trusted by default.</p><p>This connects directly with our earlier article on Azure Service Bus.</p><h2>Encrypt Data in Transit</h2><p>All service communication should use TLS.</p><p>Without encryption:</p><ul><li><p>Credentials can be intercepted </p></li><li><p>Data can be modified </p></li><li><p>Sensitive information becomes exposed </p></li></ul><p>HTTPS should be mandatory for every service endpoint.</p><h2>Encrypt Sensitive Data at Rest</h2><p>Data stored in:</p><ul><li><p>Databases </p></li><li><p>Message queues </p></li><li><p>Object storage </p></li></ul><p>Should be encrypted.</p><p>This limits exposure if storage systems become compromised.</p><h2>Logging Security Events</h2><p>Security incidents rarely announce themselves.</p><p>Logs should capture:</p><ul><li><p>Authentication failures </p></li><li><p>Authorization failures </p></li><li><p>Suspicious activity </p></li><li><p>Policy violations<br></p></li></ul><p>These events become critical during investigations.</p><h2>Distributed Security Monitoring</h2><p>As discussed in our article on OpenTelemetry and distributed tracing:</p><p>https://www.asptoday.com/p/distributed-tracing-opentelemetry-aspnet-core</p><p>Observability plays a major role in security.</p><p>Tracing can reveal:</p><ul><li><p>Unexpected service calls </p></li><li><p>Unauthorized access attempts </p></li><li><p>Abnormal traffic patterns<br></p></li></ul><p>Security and observability increasingly overlap.</p><h2>The Principle of Least Privilege</h2><p>Least privilege remains one of the most effective security controls.</p><p>Avoid:</p><ul><li><p>Administrator everywhere </p></li><li><p>Shared service accounts </p></li><li><p>Broad permissions<br></p></li></ul><p>Instead grant only the permissions required for a specific task.</p><p>This limits the impact of compromised credentials.</p><h2>Network Segmentation</h2><p>Not every service should communicate with every other service.</p><p>Segmenting networks reduces attack paths.</p><p>Example:</p><pre><code><code>Public APIs
     &#8595;
Business Services
     &#8595;
Database Layer</code></code></pre><p>Restricted communication paths improve security.</p><h2>Common Security Mistakes</h2><p>Several mistakes appear repeatedly in microservices environments.</p><h3>Blind Trust Between Services</h3><p>Just because a request originates internally does not mean it is safe.</p><h3>Shared Credentials</h3><p>Shared service accounts make auditing and access control difficult.</p><h3>Excessive Permissions</h3><p>Services often receive more access than they need.</p><h3>Missing Input Validation</h3><p>Never trust incoming data.</p><p>Validate everything.</p><h3>Hardcoded Secrets</h3><p>Secrets should never be stored in source code.</p><h2>Real-World Example: E-Commerce Platform</h2><p>Imagine an online store.</p><p>Components include:</p><ul><li><p>API Gateway </p></li><li><p>Orders Service </p></li><li><p>Inventory Service </p></li><li><p>Payments Service </p></li><li><p>Notification Service </p></li></ul><p>Zero Trust implementation would require:</p><ul><li><p>JWT authentication </p></li><li><p>Service-to-service identity </p></li><li><p>Authorization policies </p></li><li><p>TLS encryption </p></li><li><p>Secret management </p></li><li><p>Distributed monitoring </p></li></ul><p>Each interaction must prove legitimacy.</p><p>No component receives automatic trust.</p><h2>Security and Resilience Work Together</h2><p>This article connects closely with several recent topics:</p><ul><li><p>Fault-Tolerant Systems </p></li><li><p>Advanced Retry Strategies </p></li><li><p>Chaos Engineering </p></li><li><p>Distributed Tracing </p></li></ul><p>A resilient system that is insecure remains vulnerable.</p><p>A secure system that cannot survive failures remains unreliable.</p><p>Modern architecture requires both.</p><h2>Preparing for Compliance Requirements</h2><p>Many organizations must satisfy regulations such as:</p><ul><li><p>GDPR </p></li><li><p>HIPAA </p></li><li><p>PCI DSS </p></li><li><p>SOC 2 </p></li></ul><p>Zero Trust principles help support compliance efforts by enforcing stronger access controls and auditability.</p><h2>How This Fits Your ASP.NET Core Journey</h2><p>So far, we&#8217;ve explored:</p><ul><li><p>Distributed messaging </p></li><li><p>Sagas </p></li><li><p>Retry strategies </p></li><li><p>Fault tolerance </p></li><li><p>Observability </p></li><li><p>Chaos engineering </p></li></ul><p>Security is the next critical layer.</p><p>Once systems become distributed, visibility alone is not enough.</p><p>Every service must verify identities, enforce permissions, and protect sensitive data continuously.</p><h2>Closing Thoughts</h2><p>Microservices create tremendous flexibility and scalability.</p><p>They also introduce significant security challenges.</p><p>Zero Trust Architecture provides a practical framework for addressing those challenges by eliminating assumptions of trust and requiring continuous verification.</p><p>By combining:</p><ul><li><p>Authentication </p></li><li><p>Authorization </p></li><li><p>Least privilege </p></li><li><p>Service identity </p></li><li><p>Encryption </p></li><li><p>Monitoring </p></li></ul><p>You can build ASP.NET Core microservices that remain secure even as systems grow increasingly distributed and complex.</p><p>In modern architecture, trust is not granted.</p><p>Trust is earned continuously.</p><h2>Join The Community</h2><p>Enjoyed this article? Subscribe to <a href="https://www.asptoday.com/">ASP Today</a> for practical ASP.NET Core architecture guides, security strategies, and real-world engineering practices. Join the Substack Chat and connect with developers building secure and resilient cloud-native applications. </p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.asptoday.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.asptoday.com/subscribe?"><span>Subscribe now</span></a></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Chaos Engineering for ASP.NET Core Applications: Testing Failure Scenarios]]></title><description><![CDATA[Discover how chaos engineering helps ASP.NET Core teams uncover weaknesses before production incidents happen. Learn practical failure-testing strategies for resilient systems. #ASPToday #aspnetcore #chaosengineering #resilience #softwarearchitecture]]></description><link>https://www.asptoday.com/p/chaos-engineering-for-aspnet-core</link><guid isPermaLink="false">https://www.asptoday.com/p/chaos-engineering-for-aspnet-core</guid><dc:creator><![CDATA[Jenny Muralidharan]]></dc:creator><pubDate>Tue, 09 Jun 2026 15:03:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!DECD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc0f79e17-0b2b-4e4d-8227-2687a10b9bf1_1672x941.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most teams spend their time trying to prevent failures. Chaos engineering takes a different approach. Instead of avoiding failure, it deliberately introduces controlled failures into systems to discover weaknesses before real incidents occur.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DECD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc0f79e17-0b2b-4e4d-8227-2687a10b9bf1_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DECD!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc0f79e17-0b2b-4e4d-8227-2687a10b9bf1_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!DECD!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc0f79e17-0b2b-4e4d-8227-2687a10b9bf1_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!DECD!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc0f79e17-0b2b-4e4d-8227-2687a10b9bf1_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!DECD!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc0f79e17-0b2b-4e4d-8227-2687a10b9bf1_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DECD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc0f79e17-0b2b-4e4d-8227-2687a10b9bf1_1672x941.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c0f79e17-0b2b-4e4d-8227-2687a10b9bf1_1672x941.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2688796,&quot;alt&quot;:&quot;Tiny engineers test failures in a miniature theme park while keeping attractions running safely. &quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.asptoday.com/i/200982536?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc0f79e17-0b2b-4e4d-8227-2687a10b9bf1_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Tiny engineers test failures in a miniature theme park while keeping attractions running safely. " title="Tiny engineers test failures in a miniature theme park while keeping attractions running safely. " srcset="https://substackcdn.com/image/fetch/$s_!DECD!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc0f79e17-0b2b-4e4d-8227-2687a10b9bf1_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!DECD!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc0f79e17-0b2b-4e4d-8227-2687a10b9bf1_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!DECD!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc0f79e17-0b2b-4e4d-8227-2687a10b9bf1_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!DECD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc0f79e17-0b2b-4e4d-8227-2687a10b9bf1_1672x941.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In this guide, we&#8217;ll explore chaos engineering in <a href="https://asptoday.substack.com/p/introduction-to-aspnet-core-whats-new-and-why-it-matters">ASP.NET Core</a>, how it works, when to use it, and how to safely test failure scenarios that improve reliability and resilience. </p><h2>Why Testing Success Is Not Enough</h2><p>Most application testing focuses on successful outcomes.</p><p>We verify that:</p><ul><li><p>APIs return expected responses</p></li><li><p>Database operations complete successfully</p></li><li><p>Authentication works correctly</p></li><li><p>Business workflows function as expected</p></li></ul><p>These tests are important.</p><p>But production environments rarely behave perfectly.</p><p>Real systems experience:</p><ul><li><p>Network interruptions</p></li><li><p>Database outages</p></li><li><p>Message queue failures</p></li><li><p>Cloud service disruptions</p></li><li><p>Slow dependencies</p></li><li><p>Infrastructure failures</p></li></ul><p>The challenge is that many of these situations are difficult to reproduce during normal testing.</p><p>Chaos engineering helps solve this problem.</p><h2>What Is Chaos Engineering?</h2><p>Chaos engineering is the practice of intentionally introducing failures into a system to observe how it responds.</p><p>The goal is not destruction.</p><p>The goal is learning.</p><p>By safely creating controlled failures, teams can identify weaknesses before customers experience them.</p><p>The concept was popularized by Netflix, which developed Chaos Monkey to randomly terminate production instances and verify that services remained available.</p><p><a href="https://netflix.github.io/chaosmonkey/">Official Chaos Monkey project</a>:</p><h2>Why Chaos Engineering Matters</h2><p>Imagine your application depends on:</p><ul><li><p>A SQL database</p></li><li><p>Redis cache</p></li><li><p>Payment provider</p></li><li><p>Email service</p></li><li><p>Azure Service Bus</p></li></ul><p>Everything works perfectly during development.</p><p>Then one day:</p><ul><li><p>Redis becomes unavailable</p></li><li><p>Payment API starts timing out</p></li><li><p>Network latency increases dramatically</p></li></ul><p>What happens?</p><p>Many teams discover the answer only after customers start reporting issues.</p><p>Chaos engineering helps uncover these weaknesses before they become incidents.</p><h2>Chaos Engineering Is Not Random Destruction</h2><p>A common misconception is that chaos engineering means breaking things randomly.</p><p>Effective chaos engineering is controlled and scientific.</p><p>Every experiment begins with a hypothesis.</p><p>For example:</p><blockquote><p>If Redis becomes unavailable, product pages should still load using database fallbacks.</p></blockquote><p>Then you test the hypothesis.</p><p>If reality differs from expectations, you&#8217;ve found an improvement opportunity.</p><h2>The Scientific Method for Reliability</h2><p>Chaos engineering follows a structured process:</p><ol><li><p>Define steady-state behavior</p></li><li><p>Create a hypothesis</p></li><li><p>Introduce controlled failure</p></li><li><p>Observe results</p></li><li><p>Improve the system</p></li></ol><p>This makes chaos engineering an engineering discipline rather than a guessing exercise.</p><h2>Understanding Steady State</h2><p>Before introducing failures, you need to understand normal behavior.</p><p>Examples include:</p><ul><li><p>Average response times</p></li><li><p>Error rates</p></li><li><p>Throughput</p></li><li><p>Queue depth</p></li><li><p>Resource utilization</p></li></ul><p>Without a baseline, it&#8217;s impossible to evaluate the impact of failure scenarios.</p><p>This is one reason observability is so important.</p><p>As discussed in our previous article on distributed tracing, visibility is critical when investigating system behavior.</p><h2>Chaos Engineering and Distributed Tracing</h2><p>Distributed tracing and chaos engineering work exceptionally well together.</p><p>Tracing helps answer:</p><ul><li><p>Which services were affected?</p></li><li><p>Where did failures originate?</p></li><li><p>How far did failures spread?</p></li><li><p>Which dependencies became bottlenecks?</p></li></ul><p>Using OpenTelemetry, engineers can visualize the impact of chaos experiments across an entire distributed system.</p><h2>Common Failure Scenarios</h2><p>Chaos engineering experiments often focus on realistic production failures.</p><p>Examples include:</p><ul><li><p>Service outages</p></li><li><p>Network latency</p></li><li><p>Packet loss</p></li><li><p>Dependency failures</p></li><li><p>Database connection exhaustion</p></li><li><p>High CPU usage</p></li><li><p>Memory pressure</p></li><li><p>Message queue delays</p></li></ul><p>These are failures that eventually happen in real systems.</p><p>The question is whether your application handles them gracefully.</p><h2>Simulating API Failures</h2><p>Suppose your application calls a payment provider.</p><p>Normally:</p><pre><code><code>var response = await _paymentClient.ProcessAsync(payment);</code></code></pre><p>What happens if:</p><ul><li><p>The API returns HTTP 500? </p></li><li><p>Requests timeout? </p></li><li><p>The service becomes unavailable?<br></p></li></ul><p>Chaos testing allows you to simulate these scenarios safely.</p><h2>Testing Timeouts</h2><p>Timeouts are one of the most common production issues.</p><p>A dependency may not fail completely.</p><p>Instead, it becomes extremely slow.</p><p>Example:</p><pre><code><code>await Task.Delay(TimeSpan.FromSeconds(30));</code></code></pre><p>How does your application react?</p><p>Do users receive helpful feedback?</p><p>Or does everything become stuck waiting indefinitely?</p><h2>Validating Retry Policies</h2><p>Our previous article explored:</p><ul><li><p>Exponential backoff </p></li><li><p>Jitter </p></li><li><p>Idempotency <br></p></li></ul><p>Chaos engineering helps verify those patterns actually work.</p><p>For example:</p><ul><li><p>Simulate API failures </p></li><li><p>Observe retries </p></li><li><p>Verify recovery<br></p></li></ul><p>Many teams discover retry configurations are too aggressive or too conservative.</p><p>Testing reveals these weaknesses.</p><h2>Circuit Breakers Under Stress</h2><p>Circuit breakers are designed to prevent failing dependencies from overwhelming a system.</p><p>Example using Polly:</p><pre><code><code>var circuitBreaker = Policy
    .Handle&lt;HttpRequestException&gt;()
    .CircuitBreakerAsync(
        5,
        TimeSpan.FromSeconds(30));</code></code></pre><p>Chaos testing verifies:</p><ul><li><p>Does the breaker open correctly? </p></li><li><p>Does traffic stop flowing? </p></li><li><p>Does recovery happen automatically?<br></p></li></ul><p>Without testing, assumptions remain unverified.</p><h2>Database Failure Experiments</h2><p>Databases are among the most critical dependencies.</p><p>Experiments may include:</p><ul><li><p>Connection failures </p></li><li><p>High latency </p></li><li><p>Deadlocks </p></li><li><p>Resource exhaustion<br></p></li></ul><p>Questions to ask:</p><ul><li><p>Does the application fail gracefully? </p></li><li><p>Are users informed properly? </p></li><li><p>Do background processes recover? <br></p></li></ul><p>These are valuable insights before production incidents occur.</p><h2>Testing Distributed Caching Failures</h2><p>Many ASP.NET Core systems rely on Redis.</p><p>What happens if Redis becomes unavailable?</p><p>A well-designed system should:</p><ul><li><p>Continue operating </p></li><li><p>Fall back to database queries </p></li><li><p>Maintain acceptable performance<br></p></li></ul><p>Chaos experiments validate these assumptions.</p><h2>Message Queue Failures</h2><p>Applications using Azure Service Bus or RabbitMQ should test scenarios such as:</p><ul><li><p>Delayed message delivery </p></li><li><p>Queue unavailability </p></li><li><p>Poison messages </p></li><li><p>High backlog conditions<br></p></li></ul><p>Questions include:</p><ul><li><p>Are messages retried correctly? </p></li><li><p>Are dead-letter queues used properly? </p></li><li><p>Does the system recover automatically? </p><p></p></li></ul><h2>Chaos Engineering and Saga Patterns</h2><p>Sagas coordinate distributed transactions.</p><p>Failures can occur during:</p><ul><li><p>Inventory reservation </p></li><li><p>Payment processing </p></li><li><p>Shipment creation<br></p></li></ul><p>Chaos testing helps verify:</p><ul><li><p>Compensation actions execute correctly </p></li><li><p>Eventual consistency is maintained </p></li><li><p>Workflows recover safely<br></p></li></ul><p>This is especially valuable in complex business processes.</p><h2>Infrastructure-Level Experiments</h2><p>Not all chaos experiments target application code.</p><p>Infrastructure testing can include:</p><ul><li><p>Container restarts </p></li><li><p>VM shutdowns </p></li><li><p>Kubernetes pod failures </p></li><li><p>DNS issues </p></li><li><p>Network partitions<br></p></li></ul><p>Modern cloud-native systems should tolerate these conditions.</p><h2>Latency Injection</h2><p>Sometimes dependencies do not fail.</p><p>They simply become slow.</p><p>Latency injection simulates this behavior.</p><p>Example:</p><pre><code><code>app.Use(async (context, next) =&gt;
{
    await Task.Delay(2000);
    await next();
});</code></code></pre><p>This helps reveal:</p><ul><li><p>Timeout issues </p></li><li><p>User experience problems </p></li><li><p>Resource bottlenecks </p></li></ul><h2>Fault Injection Middleware</h2><p>ASP.NET Core makes it easy to inject failures.</p><p>Example:</p><pre><code><code>app.Use(async (context, next) =&gt;
{
    if (Random.Shared.Next(100) &lt; 10)
    {
        context.Response.StatusCode = 500;
        return;
    }

    await next();
});</code></code></pre><p>This introduces controlled failures into requests.</p><p>Such experiments should only be used in non-production environments unless carefully managed.</p><h2>Monitoring During Chaos Experiments</h2><p>Observability is essential.</p><p>Monitor:</p><ul><li><p>Error rates </p></li><li><p>Response times </p></li><li><p>Queue depth </p></li><li><p>Memory consumption </p></li><li><p>CPU utilization </p></li><li><p>Retry activity<br></p></li></ul><p>Without visibility, chaos experiments provide little value.</p><h2>Defining Blast Radius</h2><p>One of the most important concepts in chaos engineering is blast radius.</p><p>Blast radius refers to the scope of impact.</p><p>Start small.</p><p>Instead of testing the entire platform:</p><ul><li><p>Test one service </p></li><li><p>Test one dependency </p></li><li><p>Test one workflow </p></li></ul><p>Expand gradually as confidence increases.</p><h2>Running Experiments Safely</h2><p>Every chaos experiment should include:</p><ul><li><p>Clear objectives </p></li><li><p>Success criteria </p></li><li><p>Monitoring </p></li><li><p>Rollback plans </p></li></ul><p>Safety must always come first.</p><p>The goal is learning, not causing outages.</p><h2>Common Mistakes</h2><p>One mistake is introducing failures without clear hypotheses.</p><p>Another is performing experiments without sufficient observability.</p><p>Also avoid:</p><ul><li><p>Testing too much at once </p></li><li><p>Running experiments without rollback procedures </p></li><li><p>Ignoring lessons learned<br></p></li></ul><p>The experiment is only valuable if it produces actionable insights.</p><h2>Real-World Example: E-Commerce Platform</h2><p>Imagine an online store.</p><p>Chaos experiments might simulate:</p><ul><li><p>Redis outage </p></li><li><p>Payment provider latency </p></li><li><p>Inventory service failure </p><p></p></li></ul><p>Expected behavior:</p><ul><li><p>Cached data falls back to database </p></li><li><p>Payments retry automatically </p></li><li><p>Inventory failures trigger compensating actions<br></p></li></ul><p>If the platform remains operational, confidence increases significantly.</p><h2>The Relationship Between Chaos and Reliability</h2><p>Chaos engineering is not about proving systems are perfect.</p><p>It is about discovering where they are fragile.</p><p>Every weakness uncovered is an opportunity to improve resilience.</p><p>Over time, systems become stronger because failures are explored proactively rather than reactively.</p><h2>When NOT to Use Chaos Engineering</h2><p>Small internal applications may not need extensive chaos testing.</p><p>Likewise, teams lacking:</p><ul><li><p>Monitoring </p></li><li><p>Alerting </p></li><li><p>Operational maturity<br></p></li></ul><p>Should establish those foundations first.</p><p>Chaos engineering works best when observability already exists.</p><h2>How This Fits Your ASP.NET Core Journey</h2><p>So far, we&#8217;ve explored:</p><ul><li><p>Distributed messaging </p></li><li><p>Saga patterns </p></li><li><p>Retry strategies </p></li><li><p>Fault-tolerant systems </p></li><li><p>OpenTelemetry and distributed tracing<br></p></li></ul><p>Chaos engineering brings these concepts together.</p><p>It validates whether resilience patterns actually work under realistic failure conditions.</p><p>This is where architecture moves from theory into real-world operational confidence.</p><h2>Closing Thoughts</h2><p>Failures are inevitable.</p><p>The most resilient systems are not those that avoid failure entirely.</p><p>They are the systems that have already practiced failure.</p><p>Chaos engineering provides a structured way to uncover weaknesses, validate assumptions, and strengthen ASP.NET Core applications before production incidents occur.</p><p>By combining:</p><ul><li><p>Observability </p><p>Distributed tracing </p></li><li><p>Retries </p></li><li><p>Circuit breakers </p></li><li><p>Sagas </p></li><li><p>Fault injection<br></p></li></ul><p>Teams can build systems that remain reliable even when the unexpected happens.</p><h2>Join The Community</h2><p>Enjoyed this article? Subscribe to ASP Today for practical <a href="https://www.asptoday.com/">ASP.NET Core</a> architecture guides, resilience strategies, and real-world engineering practices. Join the Substack Chat and connect with developers building modern cloud-native applications. </p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.asptoday.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.asptoday.com/subscribe?"><span>Subscribe now</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Deep Dive into Distributed Tracing in ASP.NET Core: OpenTelemetry in Practice]]></title><description><![CDATA[Learn how OpenTelemetry helps ASP.NET Core developers trace requests across APIs, databases, queues, and microservices. #ASPToday #aspnetcore #opentelemetry #observability #distributedsystems]]></description><link>https://www.asptoday.com/p/deep-dive-into-distributed-tracing</link><guid isPermaLink="false">https://www.asptoday.com/p/deep-dive-into-distributed-tracing</guid><dc:creator><![CDATA[Jenny Muralidharan]]></dc:creator><pubDate>Tue, 02 Jun 2026 15:04:14 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!JpCM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fb4f5e9-7428-483f-be95-d4dda983a044_1246x690.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As applications become more distributed, understanding what happens during a single user request becomes increasingly difficult. A request may travel through multiple APIs, databases, caches, message queues, and background services before completing. Distributed tracing solves this challenge by providing end-to-end visibility into request flows. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!JpCM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fb4f5e9-7428-483f-be95-d4dda983a044_1246x690.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JpCM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fb4f5e9-7428-483f-be95-d4dda983a044_1246x690.png 424w, https://substackcdn.com/image/fetch/$s_!JpCM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fb4f5e9-7428-483f-be95-d4dda983a044_1246x690.png 848w, https://substackcdn.com/image/fetch/$s_!JpCM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fb4f5e9-7428-483f-be95-d4dda983a044_1246x690.png 1272w, https://substackcdn.com/image/fetch/$s_!JpCM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fb4f5e9-7428-483f-be95-d4dda983a044_1246x690.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!JpCM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fb4f5e9-7428-483f-be95-d4dda983a044_1246x690.png" width="1246" height="690" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0fb4f5e9-7428-483f-be95-d4dda983a044_1246x690.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:690,&quot;width&quot;:1246,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1599383,&quot;alt&quot;:&quot;Miniature figures in a vast, dark library follow a winding trail of glowing books. &quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.asptoday.com/i/200228658?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fb4f5e9-7428-483f-be95-d4dda983a044_1246x690.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Miniature figures in a vast, dark library follow a winding trail of glowing books. " title="Miniature figures in a vast, dark library follow a winding trail of glowing books. " srcset="https://substackcdn.com/image/fetch/$s_!JpCM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fb4f5e9-7428-483f-be95-d4dda983a044_1246x690.png 424w, https://substackcdn.com/image/fetch/$s_!JpCM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fb4f5e9-7428-483f-be95-d4dda983a044_1246x690.png 848w, https://substackcdn.com/image/fetch/$s_!JpCM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fb4f5e9-7428-483f-be95-d4dda983a044_1246x690.png 1272w, https://substackcdn.com/image/fetch/$s_!JpCM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0fb4f5e9-7428-483f-be95-d4dda983a044_1246x690.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In this guide, you&#8217;ll learn how distributed tracing works, why OpenTelemetry has become the industry standard, and how to implement practical tracing solutions in <a href="https://asptoday.substack.com/p/introduction-to-aspnet-core-whats-new-and-why-it-matters">ASP.NET Core</a> applications. </p><h2>Why Traditional Logging Is No Longer Enough</h2><p>Years ago, many applications were relatively simple.</p><p>A user made a request.</p><p>The request hit a web server.</p><p>The application queried a database.</p><p>A response was returned.</p><p>If something went wrong, logs were usually enough.</p><p>Modern systems are very different.</p><p>A single request may now travel through:</p><ul><li><p>API Gateway</p></li><li><p>ASP.NET Core Web API</p></li><li><p>Authentication Service</p></li><li><p>Product Service</p></li><li><p>Redis Cache</p></li><li><p>SQL Database</p></li><li><p>Message Queue</p></li><li><p>Background Worker</p></li></ul><p>Each component may have its own logs.</p><p>When failures occur, finding the root cause becomes difficult.</p><p>This is where distributed tracing becomes essential. </p><h2>What Is Distributed Tracing?</h2><p>Distributed tracing allows developers to follow a request as it moves through multiple systems.</p><p>Think of it as attaching a GPS tracker to every request.</p><p>Instead of viewing isolated logs, you can see:</p><ul><li><p>Where the request started</p></li><li><p>Which services it visited</p></li><li><p>How long each step took</p></li><li><p>Where failures occurred</p></li><li><p>Which dependencies caused delays</p></li></ul><p>Distributed tracing provides a complete picture of request execution.  </p><h2>Understanding Traces, Spans, and Context</h2><p>Distributed tracing revolves around three key concepts.</p><h2>Trace</h2><p>A trace represents the complete journey of a request.</p><p>For example:</p><pre><code><code>User Request
 &#9500;&#9472; API Gateway
 &#9500;&#9472; Product Service
 &#9500;&#9472; SQL Database
 &#9500;&#9472; Redis Cache
 &#9492;&#9472; Response</code></code></pre><p>Everything belongs to a single trace.</p><h2>Span</h2><p>A span represents an individual operation.</p><p>Examples include:</p><ul><li><p>Calling a database </p></li><li><p>Querying Redis </p></li><li><p>Sending a message </p></li><li><p>Invoking another API </p></li></ul><p>Each trace contains multiple spans.</p><h2>Context</h2><p>Context links spans together.</p><p>Without context propagation, traces break apart and become useless.</p><p>OpenTelemetry automatically handles much of this process.</p><h2>Why OpenTelemetry Has Become the Standard</h2><p>Before OpenTelemetry, observability tools often used proprietary approaches.</p><p>Organizations frequently became locked into specific vendors.</p><p>OpenTelemetry was created to solve this problem.</p><p>It provides:</p><ul><li><p>Vendor-neutral instrumentation </p></li><li><p>Standardized telemetry collection </p></li><li><p>Cross-platform support </p></li><li><p>Broad ecosystem integration </p></li></ul><p>OpenTelemetry is now supported by major platforms including:</p><ul><li><p>Microsoft Azure </p></li><li><p>AWS </p></li><li><p>Google Cloud </p></li><li><p>Grafana </p></li><li><p>Datadog </p></li><li><p>New Relic </p></li></ul><p>Official project:</p><p>https://opentelemetry.io</p><h2>Observability vs Monitoring</h2><p>These terms are often confused.</p><p>Monitoring focuses on answering known questions.</p><p>Examples:</p><ul><li><p>Is the service running? </p></li><li><p>How many requests failed? </p></li><li><p>Is CPU usage high? </p></li></ul><p>Observability helps answer unknown questions.</p><p>Examples:</p><ul><li><p>Why is checkout suddenly slow? </p></li><li><p>Which dependency caused latency spikes? </p></li><li><p>Which service introduced failures? </p></li></ul><p>Distributed tracing is a core pillar of observability.</p><h2>The Three Pillars of Observability</h2><p>Modern observability typically includes:</p><ul><li><p>Logs </p></li><li><p>Metrics </p></li><li><p>Traces<br></p></li></ul><p>Logs explain what happened.</p><p>Metrics show how often something happens.</p><p>Traces reveal where it happened.</p><p>Together they provide a complete operational picture.</p><h2>Why Tracing Matters in ASP.NET Core</h2><p>Consider an e-commerce application.</p><p>A customer clicks &#8220;Place Order.&#8221;</p><p>The request triggers:</p><ol><li><p>Authentication </p></li><li><p>Inventory validation </p></li><li><p>Payment processing </p></li><li><p>Shipping creation </p></li><li><p>Notification service <br></p></li></ol><p>A failure occurs.</p><p>Which step caused the issue?</p><p>Without tracing:</p><ul><li><p>Search logs manually </p></li><li><p>Correlate timestamps </p></li><li><p>Guess relationships<br></p></li></ul><p>With tracing:</p><ul><li><p>View the entire workflow instantly<br></p></li></ul><p>This becomes especially valuable in architectures we&#8217;ve discussed previously:</p><ul><li><p>ASP.NET Core and Azure Service Bus </p></li><li><p>Saga Patterns </p></li><li><p>Fault-Tolerant Systems </p></li><li><p>Advanced Retry Strategies </p><p></p></li></ul><p>Distributed tracing ties these systems together.</p><h2>Installing OpenTelemetry</h2><p>Start by adding required packages.</p><pre><code><code>dotnet add package OpenTelemetry.Extensions.Hosting

dotnet add package OpenTelemetry.Instrumentation.AspNetCore

dotnet add package OpenTelemetry.Instrumentation.Http

dotnet add package OpenTelemetry.Instrumentation.SqlClient</code></code></pre><p>These packages provide automatic instrumentation.</p><h2>Configuring OpenTelemetry</h2><p>In Program.cs:</p><pre><code><code>builder.Services.AddOpenTelemetry()
    .WithTracing(tracing =&gt;
    {
        tracing
            .AddAspNetCoreInstrumentation()
            .AddHttpClientInstrumentation()
            .AddSqlClientInstrumentation();
    });</code></code></pre><p>This immediately starts collecting trace information.</p><h2>Automatic Instrumentation</h2><p>One of OpenTelemetry&#8217;s greatest strengths is automatic instrumentation.</p><p>Without changing business logic, you can collect traces from:</p><ul><li><p>Incoming HTTP requests </p></li><li><p>Outgoing HTTP calls </p></li><li><p>SQL queries </p></li><li><p>Entity Framework operations </p></li><li><p>gRPC services<br></p></li></ul><p>This dramatically reduces implementation effort.</p><h2>Viewing Traces Locally</h2><p>During development, traces can be exported to the console.</p><pre><code><code>builder.Services.AddOpenTelemetry()
    .WithTracing(builder =&gt;
    {
        builder
            .AddAspNetCoreInstrumentation()
            .AddConsoleExporter();
    });</code></code></pre><p>This helps developers understand how traces are structured.</p><h2>Understanding Trace Hierarchies</h2><p>Imagine this workflow:</p><pre><code><code>Order Request
 &#9500;&#9472; Validate User
 &#9500;&#9472; Check Inventory
 &#9474;   &#9500;&#9472; Redis Cache
 &#9474;   &#9492;&#9472; SQL Query
 &#9500;&#9472; Process Payment
 &#9492;&#9472; Send Confirmation</code></code></pre><p>Each operation becomes a span.</p><p>The complete tree becomes a trace.</p><p>This hierarchy makes bottlenecks easy to identify.</p><div><hr></div><h2>Adding Custom Spans</h2><p>Automatic instrumentation is powerful.</p><p>But custom spans provide additional business insight.</p><p>Example:</p><pre><code><code>private static readonly ActivitySource ActivitySource =
    new("OrderService");

public async Task ProcessOrderAsync()
{
    using var activity =
        ActivitySource.StartActivity("ProcessOrder");

    await Task.Delay(100);
}</code></code></pre><p>This creates a custom span visible in trace visualizations.</p><h2>Capturing Business Operations</h2><p>Not everything revolves around technical dependencies.</p><p>Sometimes you want visibility into business workflows.</p><p>Examples:</p><ul><li><p>Calculate Discount </p><p>Validate Coupon </p></li><li><p>Generate Invoice </p></li><li><p>Process Refund <br></p></li></ul><p>Custom spans allow these operations to appear in traces.</p><h2>Context Propagation</h2><p>Tracing only works if context moves between services.</p><p>Consider:</p><pre><code><code>API A
 &#8595;
API B
 &#8595;
API C</code></code></pre><p>If trace context is lost between services:</p><ul><li><p>Visibility breaks </p></li><li><p>Relationships disappear<br></p></li></ul><p>OpenTelemetry automatically propagates context through standard HTTP headers.</p><p>This allows traces to remain connected.</p><h2>Tracing HTTP Requests</h2><p>Outgoing HTTP requests are automatically captured.</p><p>Example:</p><pre><code><code>var response =
    await _httpClient.GetAsync("/products");</code></code></pre><p>OpenTelemetry records:</p><ul><li><p>URL </p></li><li><p>Duration </p></li><li><p>Status code </p></li><li><p>Parent trace </p></li></ul><p>This creates a complete dependency map.</p><h2>Tracing Database Calls</h2><p>Database performance often becomes a bottleneck.</p><p>OpenTelemetry automatically traces:</p><pre><code><code>await _dbContext.Products
    .Where(p =&gt; p.IsActive)
    .ToListAsync();</code></code></pre><p>Useful information includes:</p><ul><li><p>Query duration </p></li><li><p>Database dependency </p></li><li><p>Success or failure </p></li></ul><p>This greatly simplifies performance investigations.</p><h2>Tracing Message-Based Architectures</h2><p>Many modern ASP.NET Core systems rely on messaging.</p><p>Examples include:</p><ul><li><p>Azure Service Bus </p></li><li><p>RabbitMQ </p></li><li><p>Kafka </p></li></ul><p>Distributed tracing becomes even more important because workflows become asynchronous.</p><p>Tracing helps developers understand:</p><ul><li><p>Message publication </p></li><li><p>Message consumption </p></li><li><p>Processing delays </p></li><li><p>Queue bottlenecks<br></p></li></ul><h2>OpenTelemetry and ASP.NET Core APIs</h2><p>Minimal APIs work seamlessly.</p><pre><code><code>app.MapGet("/products", async () =&gt;
{
    return Results.Ok();
});</code></code></pre><p>Each request automatically generates trace information.</p><p>No additional code required.</p><h2>Sampling Strategies</h2><p>Not every trace needs to be collected.</p><p>High-volume systems can generate millions of traces daily.</p><p>Sampling helps control costs.</p><p>Common approaches:</p><ul><li><p>Always sample </p></li><li><p>Never sample </p></li><li><p>Percentage-based sampling </p></li><li><p>Adaptive sampling<br></p></li></ul><p>Example:</p><pre><code><code>.SetSampler(new TraceIdRatioBasedSampler(0.1))</code></code></pre><p>This collects approximately 10% of traces.</p><h2>Exporting Trace Data</h2><p>OpenTelemetry separates collection from storage.</p><p>Popular destinations include:</p><ul><li><p>Azure Monitor </p></li><li><p>Jaeger </p></li><li><p>Zipkin </p></li><li><p>Grafana Tempo </p></li><li><p>Datadog<br></p></li></ul><p>This flexibility prevents vendor lock-in.</p><h2>Visualizing Traces with Jaeger</h2><p>Jaeger is one of the most popular tracing platforms.</p><p><a href="https://www.jaegertracing.io/">Official website</a></p><p>It provides:</p><ul><li><p>Trace timelines </p></li><li><p>Dependency graphs </p></li><li><p>Span details </p></li><li><p>Performance analysis<br></p></li></ul><p>Developers can visually inspect entire request journeys.</p><h2>Diagnosing Performance Problems</h2><p>Suppose a page suddenly becomes slow.</p><p>Tracing reveals:</p><pre><code><code>Request Duration: 3.8s

Authentication: 40ms
Product Service: 120ms
Redis Cache: 5ms
SQL Query: 3.4s</code></code></pre><p>The bottleneck becomes obvious immediately.</p><p>Without tracing, finding this issue could take hours.</p><div><hr></div><h2>Tracing and Distributed Caching</h2><p>Distributed caching often improves performance dramatically.</p><p>But cache misses can still cause problems.</p><p>Tracing helps identify:</p><ul><li><p>Cache hit rates </p></li><li><p>Cache miss paths </p></li><li><p>Database fallbacks<br></p></li></ul><p>This pairs naturally with distributed caching architectures.</p><h2>Tracing Fault-Tolerant Systems</h2><p>In our previous article on fault tolerance, we discussed:</p><ul><li><p>Retries </p></li><li><p>Circuit breakers </p></li><li><p>Bulkheads </p></li><li><p>Graceful degradation </p></li></ul><p>Tracing makes these patterns visible.</p><p>You can observe:</p><ul><li><p>Retry attempts </p></li><li><p>Circuit breaker activations </p></li><li><p>Fallback executions <br></p></li></ul><p>This turns resilience from theory into measurable behavior.</p><h2>Tracing Saga Workflows</h2><p>Saga patterns coordinate long-running distributed transactions.</p><p>Tracing allows developers to follow:</p><ul><li><p>Order creation </p></li><li><p>Inventory reservation </p></li><li><p>Payment processing </p></li><li><p>Shipment generation<br></p></li></ul><p>Across multiple services. </p><p>This provides enormous operational value.</p><h2>Security Considerations</h2><p>Be careful with trace data.</p><p>Avoid recording:</p><ul><li><p>Passwords </p></li><li><p>Credit card numbers </p></li><li><p>Personal information </p></li><li><p>Authentication tokens <br></p></li></ul><p>Telemetry should support troubleshooting without exposing sensitive data.</p><p><a href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-10.0">Microsoft guidance</a>:</p><p></p><h2>Common Mistakes</h2><p>A frequent mistake is collecting traces without using them.</p><p>Another is tracing everything indiscriminately.</p><p>This creates:</p><ul><li><p>Storage costs </p></li><li><p>Noise </p></li><li><p>Analysis difficulties </p></li></ul><p>Focus on meaningful visibility.</p><h2>When Distributed Tracing Is Most Valuable</h2><p>Distributed tracing provides the greatest value when systems include:</p><ul><li><p>Multiple services </p></li><li><p>External APIs </p></li><li><p>Message queues </p></li><li><p>Background processing </p></li><li><p>Cloud infrastructure<br></p></li></ul><p>Simple applications may not require extensive tracing.</p><p>Complex systems almost always benefit.</p><h2>Real-World Example</h2><p>Imagine a food delivery platform.</p><p>A customer places an order.</p><p>The workflow touches:</p><ul><li><p>User Service </p></li><li><p>Restaurant Service </p></li><li><p>Payment Service </p></li><li><p>Delivery Service </p></li><li><p>Notification Service<br></p></li></ul><p>A trace shows every step.</p><p>If delays occur, engineers can pinpoint the exact dependency causing problems.</p><p>This is the power of distributed tracing.</p><h2>How This Fits Your ASP.NET Core Journey</h2><p>So far, we&#8217;ve explored:</p><ul><li><p>Azure Service Bus </p></li><li><p>Saga Patterns </p></li><li><p>Distributed Caching </p></li><li><p>Retry Strategies </p></li><li><p>Fault-Tolerant Systems<br></p></li></ul><p>Distributed tracing now provides the visibility layer that ties all these architectural patterns together.</p><p>Building distributed systems is only half the challenge.</p><p>Understanding them in production is the other half.</p><h2>Closing Thoughts</h2><p>Modern ASP.NET Core systems are increasingly distributed.</p><p>As complexity grows, traditional logging alone becomes insufficient.</p><p>OpenTelemetry provides a standardized, vendor-neutral approach to distributed tracing that helps developers understand exactly how requests move through their systems.</p><p>By implementing tracing early, teams gain:</p><ul><li><p>Faster troubleshooting </p></li><li><p>Better performance visibility </p></li><li><p>Improved reliability </p></li><li><p>Stronger operational insight<br></p></li></ul><p>As your applications scale, distributed tracing becomes less of a luxury and more of a necessity.</p><h2>Join The Community</h2><p>Enjoyed this article? <a href="https://www.asptoday.com/">Subscribe to ASP Today</a> for practical ASP.NET Core architecture guides, observability strategies, and real-world engineering patterns. Join the Substack Chat and connect with developers building modern cloud-native applications. </p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.asptoday.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.asptoday.com/subscribe?"><span>Subscribe now</span></a></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Designing Fault-Tolerant Systems in ASP.NET Core: Patterns and Trade-Offs]]></title><description><![CDATA[Learn how modern ASP.NET Core systems survive failures using resilience patterns, graceful degradation, retries, circuit breakers, and recovery strategies. #ASPToday #aspnetcore #dotnet #distributedsystems #softwarearchitecture]]></description><link>https://www.asptoday.com/p/designing-fault-tolerant-systems</link><guid isPermaLink="false">https://www.asptoday.com/p/designing-fault-tolerant-systems</guid><dc:creator><![CDATA[Jenny Muralidharan]]></dc:creator><pubDate>Tue, 26 May 2026 15:02:41 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!3uhp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2e6700e-bfac-43a6-80b2-fe3cad08ab2a_1376x768.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Modern applications cannot assume everything will always work correctly. Networks fail, APIs timeout, databases become overloaded, and cloud infrastructure occasionally goes down. The goal of fault-tolerant design is not preventing every failure, but ensuring your <a href="https://www.asptoday.com/">ASP.NET Core</a> systems continue operating gracefully when failures happen. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!3uhp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2e6700e-bfac-43a6-80b2-fe3cad08ab2a_1376x768.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!3uhp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2e6700e-bfac-43a6-80b2-fe3cad08ab2a_1376x768.png 424w, https://substackcdn.com/image/fetch/$s_!3uhp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2e6700e-bfac-43a6-80b2-fe3cad08ab2a_1376x768.png 848w, https://substackcdn.com/image/fetch/$s_!3uhp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2e6700e-bfac-43a6-80b2-fe3cad08ab2a_1376x768.png 1272w, https://substackcdn.com/image/fetch/$s_!3uhp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2e6700e-bfac-43a6-80b2-fe3cad08ab2a_1376x768.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!3uhp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2e6700e-bfac-43a6-80b2-fe3cad08ab2a_1376x768.png" width="1376" height="768" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b2e6700e-bfac-43a6-80b2-fe3cad08ab2a_1376x768.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:768,&quot;width&quot;:1376,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2452547,&quot;alt&quot;:&quot;A detailed miniature model of a chaotic night-time highway repair scene, where tiny construction figures with sparks, crane, and police cars work on a majorly collapsed overpass.&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.asptoday.com/i/199281294?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2e6700e-bfac-43a6-80b2-fe3cad08ab2a_1376x768.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A detailed miniature model of a chaotic night-time highway repair scene, where tiny construction figures with sparks, crane, and police cars work on a majorly collapsed overpass." title="A detailed miniature model of a chaotic night-time highway repair scene, where tiny construction figures with sparks, crane, and police cars work on a majorly collapsed overpass." srcset="https://substackcdn.com/image/fetch/$s_!3uhp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2e6700e-bfac-43a6-80b2-fe3cad08ab2a_1376x768.png 424w, https://substackcdn.com/image/fetch/$s_!3uhp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2e6700e-bfac-43a6-80b2-fe3cad08ab2a_1376x768.png 848w, https://substackcdn.com/image/fetch/$s_!3uhp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2e6700e-bfac-43a6-80b2-fe3cad08ab2a_1376x768.png 1272w, https://substackcdn.com/image/fetch/$s_!3uhp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb2e6700e-bfac-43a6-80b2-fe3cad08ab2a_1376x768.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In this guide, we&#8217;ll explore the key patterns, architectural decisions, and trade-offs involved in building resilient fault-tolerant systems with ASP.NET Core. </p><h2>Why Fault Tolerance Matters</h2><p>In small applications, failures are often manageable.</p><p>A user refreshes the page.<br>An administrator restarts the service.<br>The problem disappears.</p><p>But modern systems are different.</p><p>Applications now operate across:</p><ul><li><p>Multiple APIs</p></li><li><p>Distributed databases</p></li><li><p>Cloud services</p></li><li><p>Message brokers</p></li><li><p>External providers</p></li><li><p>Containerized infrastructure</p></li></ul><p>This means failures become inevitable.</p><p>At scale, the question changes from:</p><blockquote><p>&#8220;Will failures happen?&#8221;</p></blockquote><p>To:</p><blockquote><p>&#8220;How will the system behave when they do?&#8221;</p></blockquote><p>That shift is the foundation of fault-tolerant design.</p><h2>What Is a Fault-Tolerant System?</h2><p>A fault-tolerant system continues functioning even when parts of the system fail.</p><p>This does not mean:</p><ul><li><p>zero downtime</p></li><li><p>perfect operation</p></li><li><p>infinite reliability</p></li></ul><p>Instead, fault-tolerant systems:</p><ul><li><p>degrade gracefully</p></li><li><p>recover automatically</p></li><li><p>isolate failures</p></li><li><p>minimize user impact</p></li></ul><p>The user experience remains stable even during internal problems.</p><h2>Understanding Failure in Distributed Systems</h2><p>Failures in distributed systems are normal.</p><p>Examples include:</p><ul><li><p>network interruptions</p></li><li><p>slow APIs</p></li><li><p>overloaded databases</p></li><li><p>dropped messages</p></li><li><p>DNS failures</p></li><li><p>container crashes</p></li><li><p>cloud outages</p></li></ul><p>This connects directly with concepts from:</p><ul><li><p><a href="https://www.asptoday.com/p/advanced-retry-strategies-in-aspnet">Advanced Retry Strategies in ASP.NET Core</a></p></li><li><p><a href="https://www.asptoday.com/p/implementing-saga-patterns-in-aspnet">Implementing Saga Patterns in ASP.NET Core</a></p></li><li><p><a href="https://www.asptoday.com/p/aspnet-core-and-azure-service-bus">ASP.NET Core and Azure Service Bus</a></p></li></ul><p>Distributed systems must expect these conditions continuously.</p><h2>Designing for Failure from the Beginning</h2><p>One of the biggest mistakes developers make is treating resilience as something added later.</p><p>Fault tolerance must influence:</p><ul><li><p>architecture</p></li><li><p>communication patterns</p></li><li><p>deployment strategy</p></li><li><p>state management</p></li><li><p>monitoring</p></li></ul><p>Systems designed for failure behave very differently from systems that merely react to failure.</p><h2>The Fallacies of Distributed Computing</h2><p>Many reliability problems come from false assumptions.</p><p>Developers often assume:</p><ul><li><p>networks are reliable</p></li><li><p>latency is zero</p></li><li><p>bandwidth is infinite</p></li><li><p>services are always available</p></li></ul><p>These assumptions break quickly in production environments.</p><p>Recognizing this reality is the first step toward resilient architecture.</p><h2>Fault Tolerance vs High Availability</h2><p>These terms are related but different.</p><h3>High Availability</h3><p>Focuses on:</p><ul><li><p>minimizing downtime</p></li><li><p>ensuring services remain accessible</p></li></ul><h3>Fault Tolerance</h3><p>Focuses on:</p><ul><li><p>surviving failures gracefully</p></li><li><p>maintaining functionality during problems</p></li></ul><p>You can have high availability without strong fault tolerance.</p><p>And vice versa.</p><h2>Core Principles of Fault-Tolerant Design</h2><p>Fault-tolerant systems typically rely on several key principles:</p><ul><li><p>redundancy</p></li><li><p>isolation</p></li><li><p>retries</p></li><li><p>graceful degradation</p></li><li><p>observability</p></li><li><p>recovery automation</p></li></ul><p>Each introduces trade-offs.</p><h2>Retries as a Foundation</h2><p>Retries are one of the simplest resilience patterns.</p><p>Transient failures often resolve automatically.</p><p>Retrying can recover from:</p><ul><li><p>temporary network issues</p></li><li><p>overloaded services</p></li><li><p>cloud throttling</p></li></ul><p>This was covered deeply in <a href="https://www.asptoday.com/p/advanced-retry-strategies-in-aspnet">Advanced Retry Strategies in ASP.NET Core</a></p><p>But retries alone are not enough.</p><h2>The Danger of Aggressive Retries</h2><p>Poorly designed retries can amplify failures.</p><p>If thousands of services retry simultaneously:</p><ul><li><p>traffic spikes</p></li><li><p>failing systems collapse further</p></li><li><p>cascading outages occur</p></li></ul><p>This is why:</p><ul><li><p>exponential backoff</p></li><li><p>jitter</p></li><li><p>retry limits</p></li></ul><p>Are essential in production systems.</p><h2>Circuit Breaker Pattern</h2><p>Circuit breakers prevent repeated calls to failing dependencies.</p><p>Without circuit breakers:</p><ul><li><p>services continuously hammer failing systems</p></li></ul><p>With circuit breakers:</p><ul><li><p>failing dependencies are temporarily isolated</p></li></ul><p>This allows recovery.</p><h2>Implementing Circuit Breakers with Polly</h2><pre><code><code>var circuitBreakerPolicy = Policy
    .Handle&lt;HttpRequestException&gt;()
    .CircuitBreakerAsync(
        5,
        TimeSpan.FromSeconds(30));</code></code></pre><p>After repeated failures:</p><ul><li><p>the circuit opens </p></li><li><p>requests stop temporarily<br></p></li></ul><p><a href="https://www.pollydocs.org/">Official Polly documentation</a>. <br></p><h2>Bulkhead Isolation Pattern</h2><p>Bulkheads isolate failures between components.</p><p>The term comes from ships:</p><ul><li><p>compartments prevent flooding from sinking the entire vessel<br></p></li></ul><p>In software:</p><ul><li><p>resource pools become isolated<br></p></li></ul><p>Example:</p><ul><li><p>one failing service cannot exhaust all threads or database connections<br></p></li></ul><h2>Example Bulkhead Policy</h2><pre><code><code>var bulkheadPolicy = Policy.BulkheadAsync(
    maxParallelization: 10,
    maxQueuingActions: 20);</code></code></pre><p>This limits concurrent resource usage.</p><h2>Graceful Degradation</h2><p>Fault-tolerant systems do not always fail completely.</p><p>Sometimes reduced functionality is acceptable.</p><p>Example:</p><ul><li><p>recommendation engine unavailable </p></li><li><p>checkout still works<br></p></li></ul><p>Users may lose non-critical features while core operations remain operational.</p><p>This dramatically improves perceived reliability.</p><h2>Real-World Example: Streaming Platforms</h2><p>Streaming services often degrade gracefully.</p><p>If recommendation systems fail:</p><ul><li><p>videos still stream<br></p></li></ul><p>If subtitles fail:</p><ul><li><p>playback continues<br></p></li></ul><p>This separation prevents small failures from becoming catastrophic outages.</p><h2>Timeouts Prevent Resource Exhaustion</h2><p>One dangerous issue in distributed systems is waiting forever.</p><p>Without timeouts:</p><ul><li><p>threads become blocked </p></li><li><p>connections accumulate </p></li><li><p>cascading failures spread<br></p></li></ul><p>Timeouts protect system resources.</p><h2>Example Timeout Policy</h2><pre><code><code>var timeoutPolicy = Policy.TimeoutAsync(
    TimeSpan.FromSeconds(5));</code></code></pre><p>After 5 seconds:</p><ul><li><p>operation cancels automatically<br></p></li></ul><h2>Combining Resilience Policies</h2><p>Modern systems usually combine:</p><ul><li><p>retries </p></li><li><p>timeouts </p></li><li><p>circuit breakers </p></li><li><p>bulkheads<br></p></li></ul><p>Example:</p><pre><code><code>var policyWrap = Policy.WrapAsync(
    retryPolicy,
    circuitBreakerPolicy,
    timeoutPolicy);</code></code></pre><p>This creates layered resilience behavior.</p><h2>Idempotency and Safe Recovery</h2><p>Retries introduce duplication risks.</p><p>A request may succeed internally while the client never receives the response.</p><p>If retried blindly:</p><ul><li><p>duplicate payments </p></li><li><p>duplicate orders </p></li><li><p>repeated operations<br></p></li></ul><p>May occur.</p><p>This is why idempotency matters.</p><h2>Designing Idempotent APIs</h2><p>Clients send unique request identifiers:</p><pre><code><code>Idempotency-Key: xyz789</code></code></pre><p>Server stores processed keys.</p><p>Repeated requests:</p><ul><li><p>return existing results </p></li><li><p>avoid duplicate processing<br></p></li></ul><p>This pattern becomes essential in fault-tolerant workflows.</p><h2>Event-Driven Resilience</h2><p>Message queues improve fault tolerance significantly.</p><p>Instead of synchronous dependencies:</p><ul><li><p>messages become buffered </p></li><li><p>services process asynchronously <br></p></li></ul><p>This reduces direct coupling between systems.</p><p></p><h2>Queue-Based Recovery</h2><p>If a service becomes unavailable:</p><ul><li><p>messages remain queued </p></li><li><p>processing resumes later<br></p></li></ul><p>This prevents immediate system-wide failures.</p><h2>Fault Tolerance and Saga Patterns</h2><p>Distributed transactions create additional reliability challenges.</p><p>Saga patterns solve this through:</p><ul><li><p>compensating actions </p></li><li><p>eventual consistency </p></li><li><p>recovery workflows<br></p></li></ul><p>This was explored in <a href="https://www.asptoday.com/p/implementing-saga-patterns-in-aspnet">Implementing Saga Patterns in ASP.NET Core</a>.</p><p>Fault-tolerant systems often rely heavily on Saga coordination.</p><h2>Redundancy Improves Reliability</h2><p>Redundancy means having backup resources.</p><p>Examples:</p><ul><li><p>multiple application instances </p></li><li><p>replicated databases </p></li><li><p>failover regions </p></li><li><p>backup message brokers<br></p></li></ul><p>Redundancy improves availability but increases:</p><ul><li><p>cost </p></li><li><p>synchronization complexity </p></li><li><p>operational overhead<br></p></li></ul><h2>Horizontal Scaling and Resilience</h2><p>Fault-tolerant systems avoid single points of failure.</p><p>Horizontal scaling helps distribute load across:</p><ul><li><p>containers </p></li><li><p>servers </p></li><li><p>cloud instances<br></p></li></ul><p>If one instance fails:</p><ul><li><p>others continue operating<br></p></li></ul><p>This improves survivability dramatically.</p><h2>Health Checks in ASP.NET Core</h2><p>ASP.NET Core includes built-in health checks.</p><pre><code><code>builder.Services.AddHealthChecks();</code></code></pre><p>Expose endpoint:</p><pre><code><code>app.MapHealthChecks("/health");</code></code></pre><p>These help orchestrators:</p><ul><li><p>detect unhealthy services </p></li><li><p>restart failed instances </p></li><li><p>reroute traffic automatically<br></p></li></ul><p><a href="https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks?view=aspnetcore-10.0">Official documentation</a> <br></p><h2>Observability Is Critical</h2><p>Fault tolerance without observability is dangerous.</p><p>You must monitor:</p><ul><li><p>failures </p></li><li><p>latency </p></li><li><p>retries </p></li><li><p>queue depth </p></li><li><p>circuit breaker activity </p></li><li><p>resource exhaustion<br></p></li></ul><p>Otherwise:</p><ul><li><p>problems remain invisible<br></p></li></ul><h2>Logging and Distributed Tracing</h2><p>Modern systems require:</p><ul><li><p>centralized logging </p></li><li><p>correlation IDs </p></li><li><p>distributed tracing <br></p></li></ul><p>These tools help trace failures across services.</p><p>Platforms commonly used:</p><ul><li><p>OpenTelemetry </p></li><li><p>Application Insights </p></li><li><p>Seq </p></li><li><p>Grafana </p></li><li><p>Prometheus<br></p></li></ul><h2>Cascading Failures</h2><p>One failing dependency can trigger widespread outages.</p><p>Example:</p><ul><li><p>database slows down </p></li><li><p>API threads become blocked </p></li><li><p>retries increase traffic </p></li><li><p>queue processing stalls </p></li><li><p>system collapses<br></p></li></ul><p>Fault-tolerant systems isolate failures early.</p><h2>Backpressure and Load Shedding</h2><p>Sometimes systems must reject traffic intentionally.</p><p>This sounds counterintuitive, but controlled rejection is often safer than overload collapse.</p><p>Techniques include:</p><ul><li><p>rate limiting </p></li><li><p>queue limits </p></li><li><p>request throttling </p></li></ul><p><a href="https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit?view=aspnetcore-10.0">ASP.NET Core supports built-in rate limiting</a> <br></p><h2>CAP Theorem Trade-Offs</h2><p>Distributed systems cannot simultaneously guarantee:</p><ul><li><p>consistency </p></li><li><p>availability </p></li><li><p>partition tolerance<br></p></li></ul><p>Trade-offs become necessary.</p><p>Fault-tolerant systems often prioritize:</p><ul><li><p>availability </p></li><li><p>partition tolerance<br></p></li></ul><p>While accepting eventual consistency.</p><h2>Trade-Offs of Fault-Tolerant Design</h2><p>Resilience introduces complexity.</p><p>More resilience means:</p><ul><li><p>more infrastructure </p></li><li><p>more monitoring </p></li><li><p>more operational overhead </p></li><li><p>more architecture decisions<br></p></li></ul><p>Simple systems are easier to understand.</p><p>Highly fault-tolerant systems are harder to build and maintain.</p><h2>When Simplicity Is Better</h2><p>Not every application requires advanced fault tolerance.</p><p>Internal tools with low traffic may not need:</p><ul><li><p>distributed queues </p></li><li><p>regional failover </p></li><li><p>complex resilience policies <br></p></li></ul><p>Overengineering creates unnecessary operational burden.</p><p>Architecture should match business requirements.</p><h2>Real-World Example: E-Commerce Checkout</h2><p>Consider checkout processing.</p><p>A fault-tolerant flow might:</p><ul><li><p>queue orders asynchronously </p></li><li><p>retry payment calls </p></li><li><p>use idempotency keys </p></li><li><p>coordinate inventory via Sagas </p></li><li><p>isolate recommendation systems<br></p></li></ul><p>Even during failures:</p><ul><li><p>customers still complete purchases<br></p></li></ul><p>This is resilience in practice.</p><h2>Fault Injection and Chaos Testing</h2><p>Resilient systems should be tested under failure conditions.</p><p>Chaos engineering introduces:</p><ul><li><p>random outages </p></li><li><p>latency spikes </p></li><li><p>dependency failures<br></p></li></ul><p>This validates recovery behavior before production incidents occur.</p><p>Netflix popularized this approach through <a href="https://netflix.github.io/chaosmonkey/">Chaos Monkey</a>. </p><h2>The Human Side of Reliability</h2><p>Fault tolerance is not only technical.</p><p>Teams also need:</p><ul><li><p>incident response plans </p></li><li><p>operational runbooks </p></li><li><p>monitoring alerts </p></li><li><p>rollback procedures <br></p></li></ul><p>Human processes are part of resilience engineering too.</p><h2>Reliability Is an Ongoing Process</h2><p>Fault tolerance is never &#8220;finished.&#8221;</p><p>As systems evolve:</p><ul><li><p>traffic grows </p></li><li><p>dependencies change </p></li><li><p>new failure modes emerge<br></p></li></ul><p>Resilience requires continuous refinement.</p><h2>How This Fits Your ASP.NET Core Journey</h2><p>So far, you&#8217;ve explored:</p><ul><li><p>distributed messaging </p></li><li><p>retries </p></li><li><p>Sagas </p></li><li><p>modular architecture </p></li><li><p>distributed caching </p></li><li><p>API evolution<br></p></li></ul><p>This blog now ties those concepts together into a broader resilience strategy.</p><p>This is where architecture becomes production-grade engineering.</p><h2>Final Thoughts</h2><p>Failures are unavoidable in distributed systems.</p><p>The goal is not creating systems that never fail.</p><p>The goal is creating systems that:</p><ul><li><p>recover gracefully </p></li><li><p>isolate failures </p></li><li><p>maintain critical functionality </p></li><li><p>minimize user impact<br></p></li></ul><p>By combining:</p><ul><li><p>retries </p></li><li><p>circuit breakers </p></li><li><p>timeouts </p></li><li><p>bulkheads </p></li><li><p>graceful degradation </p></li><li><p>observability<br></p></li></ul><p>You can build ASP.NET Core systems that remain stable even under difficult real-world conditions.</p><p>True reliability comes from designing with failure in mind from the very beginning.</p><h2>Join The Community</h2><p>Enjoyed this article? <a href="https://www.asptoday.com/">Subscribe to ASP Today</a> for practical ASP.NET Core architecture guides, distributed systems strategies, and real-world resilience engineering insights. Join the Substack Chat and connect with developers building reliable modern systems. </p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.asptoday.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.asptoday.com/subscribe?"><span>Subscribe now</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Advanced Retry Strategies in ASP.NET Core: Exponential Backoff, Jitter, and Idempotency]]></title><description><![CDATA[Learn how ASP.NET Core applications handle failures using retry strategies, exponential backoff, jitter, and idempotent design patterns. #ASPToday #aspnetcore #dotnet #resilience #softwarearchitecture]]></description><link>https://www.asptoday.com/p/advanced-retry-strategies-in-aspnet</link><guid isPermaLink="false">https://www.asptoday.com/p/advanced-retry-strategies-in-aspnet</guid><dc:creator><![CDATA[Jenny Muralidharan]]></dc:creator><pubDate>Tue, 19 May 2026 15:01:45 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Fhpi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1276921d-0fd2-463e-8034-aca6e66e4a42_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Failures are normal in distributed systems. APIs timeout, databases temporarily disconnect, networks become unstable, and cloud services occasionally fail. The difference between fragile systems and resilient systems is not avoiding failure, but handling failure intelligently.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Fhpi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1276921d-0fd2-463e-8034-aca6e66e4a42_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Fhpi!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1276921d-0fd2-463e-8034-aca6e66e4a42_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!Fhpi!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1276921d-0fd2-463e-8034-aca6e66e4a42_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!Fhpi!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1276921d-0fd2-463e-8034-aca6e66e4a42_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!Fhpi!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1276921d-0fd2-463e-8034-aca6e66e4a42_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Fhpi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1276921d-0fd2-463e-8034-aca6e66e4a42_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1276921d-0fd2-463e-8034-aca6e66e4a42_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2676932,&quot;alt&quot;:&quot;Engineers adjust exponential backoff and jitter controls over a model city representing system resilience. &quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.asptoday.com/i/198366561?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1276921d-0fd2-463e-8034-aca6e66e4a42_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Engineers adjust exponential backoff and jitter controls over a model city representing system resilience. " title="Engineers adjust exponential backoff and jitter controls over a model city representing system resilience. " srcset="https://substackcdn.com/image/fetch/$s_!Fhpi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1276921d-0fd2-463e-8034-aca6e66e4a42_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!Fhpi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1276921d-0fd2-463e-8034-aca6e66e4a42_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!Fhpi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1276921d-0fd2-463e-8034-aca6e66e4a42_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!Fhpi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1276921d-0fd2-463e-8034-aca6e66e4a42_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In this guide, we&#8217;ll explore advanced retry strategies in <a href="https://www.asptoday.com/p/introduction-to-aspnet-core-whats-new-and-why-it-matters">ASP.NET Core</a> using exponential backoff, jitter, and idempotent design to build systems that remain reliable under real-world conditions. </p><h2>Why Retries Matter in Modern Applications</h2><p>In simple applications running on one machine, failures are often straightforward.</p><p>A request succeeds or fails.</p><p>But modern ASP.NET Core applications are different.</p><p>They communicate with:</p><ul><li><p>External APIs</p></li><li><p>Payment providers</p></li><li><p>Message brokers</p></li><li><p>Distributed databases</p></li><li><p>Cloud services</p></li><li><p>Microservices</p></li></ul><p>And every network call introduces uncertainty.</p><p>Sometimes failures are temporary:</p><ul><li><p>A service is overloaded</p></li><li><p>A network packet is lost</p></li><li><p>A database connection pool is exhausted</p></li><li><p>A container restarts</p></li></ul><p>Retrying the operation a few moments later may succeed perfectly.</p><p>This is why retry strategies are critical in resilient architecture.</p><h2>Understanding Transient Failures</h2><p>Not all failures are permanent.</p><p>A transient failure is temporary and often resolves itself automatically.</p><p>Examples include:</p><ul><li><p>HTTP 503 Service Unavailable</p></li><li><p>Network timeout</p></li><li><p>Temporary DNS resolution issues</p></li><li><p>Cloud throttling</p></li><li><p>Deadlocks in databases</p></li></ul><p>These failures are ideal candidates for retries.</p><h2>Permanent Failures Should Not Be Retried</h2><p>Retries are not always appropriate.</p><p>Some failures are permanent:</p><ul><li><p>Invalid credentials</p></li><li><p>Bad requests</p></li><li><p>Validation failures</p></li><li><p>Missing resources</p></li></ul><p>Retrying these repeatedly only wastes resources.</p><p>Good retry systems distinguish between:</p><ul><li><p>Transient failures</p></li><li><p>Permanent failures</p></li></ul><p>This is one reason resilience engineering requires careful design.</p><h2>The Simplest Retry Strategy</h2><p>The most basic retry logic looks like this:</p><pre><code><code>for (int i = 0; i &lt; 3; i++)
{
    try
    {
        await CallApiAsync();
        break;
    }
    catch
    {
        await Task.Delay(1000);
    }
}</code></code></pre><p>This works, but it has serious problems.</p><h2>Why Simple Retries Become Dangerous</h2><p>Imagine thousands of clients retrying simultaneously.</p><p>If a service becomes overloaded:</p><ul><li><p>Every client retries immediately </p></li><li><p>Traffic spikes even more </p></li><li><p>The failing service collapses further<br></p></li></ul><p>This creates a retry storm.</p><p>Instead of helping recovery, retries make things worse.</p><h2>Introducing Exponential Backoff</h2><p>Exponential backoff solves this problem.</p><p>Instead of retrying at a fixed interval, delays increase gradually.</p><p>Example:</p><ul><li><p>Retry 1 &#8594; wait 1 second<br></p></li><li><p>Retry 2 &#8594; wait 2 seconds<br></p></li><li><p>Retry 3 &#8594; wait 4 seconds<br></p></li><li><p>Retry 4 &#8594; wait 8 seconds<br></p></li></ul><p>This gives struggling systems time to recover.</p><h2>Why Exponential Backoff Works</h2><p>Exponential backoff:</p><ul><li><p>Reduces pressure on failing systems </p></li><li><p>Spreads retry attempts over time </p></li><li><p>Prevents traffic spikes </p></li><li><p>Improves overall stability<br></p></li></ul><p>Cloud providers strongly recommend this strategy.</p><p><a href="https://learn.microsoft.com/azure/architecture/patterns/retry">Microsoft guidance</a> <br></p><h2>Implementing Exponential Backoff in ASP.NET Core</h2><p>The most common solution is Polly.</p><p><a href="https://www.pollydocs.org/">Official site</a> </p><p>Install:</p><pre><code><code>dotnet add package Polly</code></code></pre><h2>Basic Polly Retry Policy</h2><pre><code><code>var retryPolicy = Policy
    .Handle&lt;HttpRequestException&gt;()
    .WaitAndRetryAsync(3, retryAttempt =&gt;
        TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));</code></code></pre><p>This creates:</p><ul><li><p>2s delay </p></li><li><p>4s delay </p></li><li><p>8s delay<br></p></li></ul><h2>Using Polly with HttpClientFactory</h2><p>ASP.NET Core integrates beautifully with Polly.</p><pre><code><code>builder.Services.AddHttpClient("ApiClient")
    .AddPolicyHandler(retryPolicy);</code></code></pre><p>This automatically applies retry behavior to outgoing HTTP requests.</p><h2>The Problem with Predictable Retries</h2><p>Even exponential backoff has a hidden issue.</p><p>If thousands of clients fail at the same moment:</p><ul><li><p>They all retry at identical intervals<br></p></li></ul><p>Example:</p><ul><li><p>Everyone retries at 2 seconds </p></li><li><p>Then everyone retries at 4 seconds<br></p></li></ul><p>This creates synchronized retry waves.</p><h2>Enter Jitter</h2><p>Jitter introduces randomness into retry delays.</p><p>Instead of:</p><ul><li><p>Exactly 2 seconds<br></p></li></ul><p>Clients wait:</p><ul><li><p>1.7 seconds </p></li><li><p>2.4 seconds </p></li><li><p>2.1 seconds<br></p></li></ul><p>This spreads traffic naturally.</p><h2>Why Jitter Is Important</h2><p>Jitter:</p><ul><li><p>Prevents synchronized retry spikes </p></li><li><p>Smooths traffic patterns </p></li><li><p>Reduces cascading failures </p></li><li><p>Improves system recovery<br></p></li></ul><p>In large distributed systems, jitter becomes extremely important.</p><h2>Implementing Jitter with Polly</h2><pre><code><code>var random = new Random();

var retryPolicy = Policy
    .Handle&lt;HttpRequestException&gt;()
    .WaitAndRetryAsync(5, retryAttempt =&gt;
    {
        var exponentialDelay = Math.Pow(2, retryAttempt);

        var jitter = random.NextDouble();

        return TimeSpan.FromSeconds(exponentialDelay + jitter);
    });</code></code></pre><p>Now retries become staggered naturally.</p><h2>Retry Strategies and Distributed Systems</h2><p>Retries are foundational in distributed systems.</p><p>They connect directly to:</p><ul><li><p>Service communication </p></li><li><p>Messaging reliability </p></li><li><p>Distributed transactions<br></p></li></ul><p>This builds naturally on concepts from:</p><ul><li><p><a href="https://www.asptoday.com/p/implementing-saga-patterns-in-aspnet">Implementing Saga Patterns in ASP.NET Core</a> </p></li><li><p><a href="https://www.asptoday.com/p/aspnet-core-and-azure-service-bus">ASP.NET Core and Azure Service Bus</a>  </p></li><li><p><a href="https://www.asptoday.com/p/building-modular-monoliths-in-aspnet">Building Modular Monoliths in ASP.NET Core</a><br></p></li></ul><p>Distributed systems assume failure will happen.</p><p>Retries help systems recover gracefully.</p><h2>Understanding Idempotency</h2><p>Retries introduce another major challenge.</p><p>What happens if the first request actually succeeded, but the response was lost?</p><p>The client retries.<br><br>Now the operation executes twice.</p><p>This can cause serious issues.</p><p>Example:</p><ul><li><p>Double payments </p></li><li><p>Duplicate orders </p></li><li><p>Repeated emails<br></p></li></ul><p>This is why idempotency matters.</p><h2>What Is Idempotency?</h2><p>An operation is idempotent if:</p><ul><li><p>Repeating it produces the same result<br></p></li></ul><p>Examples:</p><ul><li><p>Updating a profile </p></li><li><p>Setting a status value<br></p></li></ul><p>Not naturally idempotent:</p><ul><li><p>Charging a credit card </p></li><li><p>Creating a new order<br></p></li></ul><h2>Designing Idempotent APIs</h2><p>One common approach uses idempotency keys.</p><p>Client sends:</p><pre><code><code>Idempotency-Key: abc123</code></code></pre><p>Server stores processed keys.</p><p>If the same request arrives again:</p><ul><li><p>Return previous result </p></li><li><p>Do not process twice<br></p></li></ul><h2>Example Idempotency Middleware</h2><pre><code><code>public class IdempotencyMiddleware
{
    private readonly RequestDelegate _next;

    public IdempotencyMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        var key = context.Request.Headers["Idempotency-Key"];

        if (string.IsNullOrEmpty(key))
        {
            await _next(context);
            return;
        }

        // Check if request already processed

        await _next(context);
    }
}</code></code></pre><p>This pattern becomes critical in payment systems.</p><h2>Retry Policies for Different Scenarios</h2><p>Different systems require different retry behavior. </p><p></p><h2>API Calls</h2><p>Usually:</p><ul><li><p>Short retries </p></li><li><p>Exponential backoff </p></li><li><p>Jitter  </p><p></p></li></ul><h2>Database Operations</h2><p>Often:</p><ul><li><p>Fewer retries </p></li><li><p>Shorter delays </p></li><li><p>Deadlock handling  </p><p></p></li></ul><h2>Messaging Systems</h2><p>Can tolerate:</p><ul><li><p>Longer retries </p></li><li><p>Queue persistence </p></li><li><p>Delayed processing </p><p></p></li></ul><h2>Retrying Database Transactions</h2><p>Entity Framework Core supports retry behavior.</p><pre><code><code>builder.Services.AddDbContext&lt;AppDbContext&gt;(options =&gt;
    options.UseSqlServer(connectionString,
        sqlOptions =&gt;
        {
            sqlOptions.EnableRetryOnFailure();
        }));</code></code></pre><p>This helps recover from transient SQL failures automatically.</p><h2>Avoid Infinite Retries</h2><p>Retries must always have limits.</p><p>Otherwise:</p><ul><li><p>Systems become stuck </p></li><li><p>Resources get exhausted </p></li><li><p>Failure cascades worsen<br></p></li></ul><p>Always define:</p><ul><li><p>Maximum attempts </p></li><li><p>Maximum delay </p></li><li><p>Timeout limits </p></li></ul><h2>Combining Retries with Circuit Breakers</h2><p>Retries and circuit breakers often work together.</p><p>Retries:</p><ul><li><p>Handle temporary failures<br></p></li></ul><p>Circuit breakers:</p><ul><li><p>Stop repeated calls to failing services<br></p></li></ul><p>This prevents overloaded systems from collapsing further.</p><h2>Example Circuit Breaker</h2><pre><code><code>var circuitBreakerPolicy = Policy
    .Handle&lt;HttpRequestException&gt;()
    .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));</code></code></pre><p>After repeated failures:</p><ul><li><p>Requests stop temporarily </p></li><li><p>System gets time to recover<br></p></li></ul><h2>Retry Storms in Cloud Systems</h2><p>Large cloud systems are especially vulnerable to retry storms.</p><p>Imagine:</p><ul><li><p>One dependency fails </p></li><li><p>Thousands of services retry simultaneously<br></p></li></ul><p>Traffic multiplies instantly.</p><p>This is why:</p><ul><li><p>Backoff </p></li><li><p>Jitter </p></li><li><p>Circuit breakers </p></li></ul><p>Are essential together.</p><h2>Real-World Example: Payment Processing</h2><p>A payment API times out.</p><p>Did payment fail?<br><br>Or did payment succeed while the response was lost?</p><p>Without idempotency:</p><ul><li><p>Retry may double-charge customers<br></p></li></ul><p>A resilient payment system:</p><ul><li><p>Uses retries </p></li><li><p>Uses idempotency keys </p></li><li><p>Stores transaction states carefully </p></li></ul><p>This combination prevents catastrophic duplication.</p><h2>Observability and Retry Monitoring</h2><p>Retries should never become invisible.</p><p>You must monitor:</p><ul><li><p>Retry counts </p></li><li><p>Failure frequency </p></li><li><p>Circuit breaker activity </p></li><li><p>Recovery times </p></li></ul><p>Good observability helps identify unhealthy dependencies early.</p><h2>Logging Retry Attempts</h2><pre><code><code>.WaitAndRetryAsync(3,
    retryAttempt =&gt; TimeSpan.FromSeconds(2),
    onRetry: (exception, timeSpan, retryCount, context) =&gt;
    {
        logger.LogWarning(
            "Retry {RetryCount} after {Delay}",
            retryCount,
            timeSpan);
    });</code></code></pre><p>This provides operational visibility.</p><h2>Common Mistakes to Avoid</h2><p>One major mistake is retrying everything blindly.</p><p>Another is retrying too aggressively.</p><p>Also avoid:</p><ul><li><p>Ignoring idempotency </p></li><li><p>Infinite retries </p></li><li><p>Missing timeout policies </p></li><li><p>Combining retries with long-running synchronous operations<br></p></li></ul><p>Retries must be intentional.</p><h2>When Retries Should NOT Be Used</h2><p>Retries are inappropriate when:</p><ul><li><p>Validation fails </p></li><li><p>Authentication fails </p></li><li><p>Business rules fail </p></li><li><p>Data is invalid </p></li></ul><p>These are not transient problems.</p><p>Retrying them only wastes resources.</p><h2>Resilience Is About Accepting Failure</h2><p>One of the biggest mindset shifts in distributed architecture is realizing:<br></p><p><strong>Failures are normal.</strong></p><p>Resilient systems:</p><ul><li><p>Expect failures </p></li><li><p>Prepare for failures </p></li><li><p>Recover gracefully from failures<br></p></li></ul><p>This is the foundation of modern cloud-native architecture.</p><h2>How This Fits Your Architecture Journey</h2><p>So far, you&#8217;ve explored:</p><ul><li><p>Messaging systems </p></li><li><p>Distributed transactions </p></li><li><p>Saga coordination </p></li><li><p>API evolution </p></li><li><p>Modular architectures </p></li></ul><p>Retry strategies now add:<br><br><strong>Reliability and fault tolerance</strong></p><p>This is where systems stop being merely functional and start becoming production-ready.</p><h2>Closing Thoughts</h2><p>Retries seem simple at first.</p><p>But in distributed systems, retry behavior directly impacts:</p><ul><li><p>Stability </p></li><li><p>Scalability </p></li><li><p>Reliability </p></li><li><p>User experience<br></p></li></ul><p>By combining:</p><ul><li><p>Exponential backoff </p></li><li><p>Jitter </p></li><li><p>Idempotency </p></li><li><p>Circuit breakers<br></p></li></ul><p>You can build ASP.NET Core applications that remain resilient even under failure conditions.</p><p>Failures will always happen.</p><p>The goal is not eliminating them.</p><p>The goal is surviving them gracefully.</p><h2>Join The Community</h2><p>Enjoyed this article? Subscribe to <a href="https://www.asptoday.com/">ASP Today</a> for practical ASP.NET Core insights, distributed systems patterns, and real-world architecture guidance. Join the Substack Chat and connect with developers building resilient modern systems. </p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.asptoday.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.asptoday.com/subscribe?"><span>Subscribe now</span></a></p>]]></content:encoded></item><item><title><![CDATA[Implementing Saga Patterns in ASP.NET Core: Managing Distributed Transactions]]></title><description><![CDATA[Manage distributed transactions safely using Saga patterns in ASP.NET Core applications. #ASPToday #aspnetcore #dotnet #microservices #softwarearchitecture]]></description><link>https://www.asptoday.com/p/implementing-saga-patterns-in-aspnet</link><guid isPermaLink="false">https://www.asptoday.com/p/implementing-saga-patterns-in-aspnet</guid><dc:creator><![CDATA[Jenny Muralidharan]]></dc:creator><pubDate>Tue, 12 May 2026 15:01:08 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!2n78!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5181bee3-770f-4250-aa4b-2d09cafd9e01_1672x941.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As applications grow into distributed systems, handling transactions becomes much harder. A single workflow may span multiple services, databases, and queues, and if one step fails, your system can end up in an inconsistent state. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2n78!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5181bee3-770f-4250-aa4b-2d09cafd9e01_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2n78!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5181bee3-770f-4250-aa4b-2d09cafd9e01_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!2n78!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5181bee3-770f-4250-aa4b-2d09cafd9e01_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!2n78!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5181bee3-770f-4250-aa4b-2d09cafd9e01_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!2n78!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5181bee3-770f-4250-aa4b-2d09cafd9e01_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2n78!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5181bee3-770f-4250-aa4b-2d09cafd9e01_1672x941.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5181bee3-770f-4250-aa4b-2d09cafd9e01_1672x941.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1837025,&quot;alt&quot;:&quot;A diagram shows a Saga Orchestrator coordinating transactions and failures across microservices. &quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.asptoday.com/i/197294154?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5181bee3-770f-4250-aa4b-2d09cafd9e01_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A diagram shows a Saga Orchestrator coordinating transactions and failures across microservices. " title="A diagram shows a Saga Orchestrator coordinating transactions and failures across microservices. " srcset="https://substackcdn.com/image/fetch/$s_!2n78!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5181bee3-770f-4250-aa4b-2d09cafd9e01_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!2n78!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5181bee3-770f-4250-aa4b-2d09cafd9e01_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!2n78!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5181bee3-770f-4250-aa4b-2d09cafd9e01_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!2n78!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5181bee3-770f-4250-aa4b-2d09cafd9e01_1672x941.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In this guide, we&#8217;ll explore how Saga patterns help ASP.NET Core applications manage distributed transactions safely, reliably, and at scale. </p><h2>Why Distributed Transactions Become Difficult</h2><p>In a traditional monolithic application, transactions are usually straightforward.</p><p>You wrap operations in a database transaction:</p><pre><code><code>using var transaction = await _dbContext.Database.BeginTransactionAsync();

try
{
    await _dbContext.SaveChangesAsync();

    await transaction.CommitAsync();
}
catch
{
    await transaction.RollbackAsync();
}</code></code></pre><p>Everything succeeds or everything rolls back.</p><p>Simple.</p><p>But modern systems rarely live inside a single database anymore.</p><p>Today&#8217;s applications often involve:</p><ul><li><p>Multiple services </p></li><li><p>Independent databases </p></li><li><p>Message queues </p></li><li><p>External APIs<br></p></li></ul><p>Now imagine this workflow:</p><ol><li><p>Create an order </p></li><li><p>Reserve inventory </p></li><li><p>Process payment </p></li><li><p>Send confirmation </p></li></ol><p>What happens if payment fails after inventory was reserved?</p><p>You can&#8217;t simply roll back everything with one database transaction anymore.</p><p>This is where Saga patterns become essential.</p><h2>What Is a Saga Pattern?</h2><p>A Saga is a way of managing distributed transactions across multiple services or modules.</p><p>Instead of one large transaction:</p><ul><li><p>Each step completes independently </p></li><li><p>If something fails, compensating actions undo previous work<br></p></li></ul><p>Think of it like a chain of small transactions working together.</p><h2>Understanding the Core Idea</h2><p>Imagine booking a vacation:</p><ul><li><p>Reserve flights </p></li><li><p>Book hotel </p></li><li><p>Rent a car<br></p></li></ul><p>If hotel booking fails:</p><ul><li><p>Cancel the flight </p></li><li><p>Cancel the car reservation </p></li></ul><p>You don&#8217;t rewind time. You perform compensating actions.</p><p>That&#8217;s exactly how a Saga works.</p><h2>Why Traditional Distributed Transactions Don&#8217;t Scale Well</h2><p>Older distributed transaction systems relied on protocols like Two-Phase Commit (2PC).</p><p>While powerful, they introduce:</p><ul><li><p>Tight coupling </p></li><li><p>Performance overhead </p></li><li><p>Availability problems </p></li></ul><p>Modern cloud-native systems prefer eventual consistency instead.</p><p>This aligns with many of the distributed architecture concepts we&#8217;ve explored earlier:</p><ul><li><p><a href="https://www.asptoday.com/p/aspnet-core-and-azure-service-bus">ASP.NET Core Azure Service Bus</a> </p></li><li><p><a href="https://www.asptoday.com/p/designing-data-pipelines-in-aspnet">Designing Data Pipelines in ASP.NET Core</a> </p></li><li><p><a href="https://www.asptoday.com/p/building-modular-monoliths-in-aspnet">Building Modular Monoliths in ASP.NET Core</a> </p></li></ul><p>Saga patterns fit naturally into these systems.</p><h2>Two Types of Saga Patterns</h2><p>There are two common approaches.</p><h3>Choreography-Based Sagas</h3><p>In choreography:</p><ul><li><p>Services react to events </p></li><li><p>No central coordinator exists </p></li></ul><p>Each service listens for events and performs actions independently.</p><h3>Example Flow</h3><ol><li><p>Order service publishes <code>OrderCreated </code></p></li><li><p>Inventory service reserves stock </p></li><li><p>Payment service processes payment </p></li><li><p>Shipping service prepares shipment </p></li></ol><p>Each service reacts automatically.</p><h3>Benefits</h3><ul><li><p>Loosely coupled </p></li><li><p>Easy to extend </p></li><li><p>Natural event-driven design<br></p></li></ul><h3>Challenges</h3><ul><li><p>Harder to debug </p></li><li><p>Workflow becomes distributed </p></li><li><p>Complex flows become difficult to track<br></p></li></ul><h3>Orchestration-Based Sagas</h3><p>In orchestration:</p><ul><li><p>A central coordinator controls the workflow<br></p></li></ul><p>The orchestrator decides:</p><ul><li><p>What step runs next </p></li><li><p>What happens on failure </p></li><li><p>Which compensation actions execute<br></p></li></ul><h3>Example Flow</h3><pre><code><code>Saga Orchestrator
    &#8595;
Create Order
    &#8595;
Reserve Inventory
    &#8595;
Process Payment
    &#8595;
Ship Product</code></code></pre><p>If payment fails:</p><pre><code><code>Cancel Inventory Reservation
Cancel Order</code></code></pre><h2>Which Approach Should You Choose?</h2><p>Smaller systems often start with choreography.</p><p>As workflows grow more complex, orchestration usually becomes easier to manage.</p><p>Most enterprise systems eventually lean toward orchestration because visibility and control become important.</p><h2>Implementing Saga Patterns in ASP.NET Core</h2><p>Let&#8217;s build a simplified orchestration example.</p><h3>Step 1: Create Saga State</h3><pre><code><code>public class OrderSagaState
{
    public Guid OrderId { get; set; }

    public bool InventoryReserved { get; set; }

    public bool PaymentProcessed { get; set; }

    public bool ShipmentCreated { get; set; }
}</code></code></pre><p>The Saga tracks progress across the workflow.</p><h3>Step 2: Create the Orchestrator</h3><pre><code><code>public class OrderSaga
{
    private readonly InventoryService _inventoryService;
    private readonly PaymentService _paymentService;

    public async Task ExecuteAsync(OrderSagaState state)
    {
        try
        {
            await _inventoryService.ReserveAsync(state.OrderId);
            state.InventoryReserved = true;

            await _paymentService.ProcessAsync(state.OrderId);
            state.PaymentProcessed = true;
        }
        catch
        {
            await CompensateAsync(state);
        }
    }
}</code></code></pre><h3>Step 3: Compensation Logic</h3><pre><code><code>private async Task CompensateAsync(OrderSagaState state)
{
    if (state.PaymentProcessed)
    {
        await _paymentService.RefundAsync(state.OrderId);
    }

    if (state.InventoryReserved)
    {
        await _inventoryService.ReleaseAsync(state.OrderId);
    }
}</code></code></pre><p>Compensation restores consistency.</p><h2>Using Messaging Systems with Sagas</h2><p>Most Saga implementations rely heavily on messaging systems.</p><p>This is where tools like:</p><ul><li><p>Azure Service Bus </p></li><li><p>RabbitMQ </p></li><li><p><a href="https://www.asptoday.com/p/implementing-event-driven-architecture">MassTransit</a> </p></li></ul><p>become important.</p><p>Messaging enables:</p><ul><li><p>Reliable communication </p></li><li><p>Asynchronous workflows </p></li><li><p>Loose coupling </p></li></ul><h2>Saga Patterns with MassTransit</h2><p>MassTransit provides strong Saga support for ASP.NET Core.</p><p><a href="https://masstransit.massient.com/concepts/saga-state-machines">Official documentation</a><br><br>Install:</p><pre><code><code>dotnet add package MassTransit</code></code></pre><h2>Configure MassTransit</h2><pre><code><code>builder.Services.AddMassTransit(x =&gt;
{
    x.AddSagaStateMachine&lt;OrderStateMachine, OrderState&gt;();

    x.UsingInMemory((context, cfg) =&gt;
    {
        cfg.ConfigureEndpoints(context);
    });
});</code></code></pre><p>This simplifies orchestration significantly.</p><h2>Long-Running Transactions</h2><p>One major advantage of Sagas is handling long-running workflows.</p><p>Traditional transactions:</p><ul><li><p>Hold database locks </p></li><li><p>Must complete quickly <br></p></li></ul><p>Sagas:</p><ul><li><p>Run asynchronously </p></li><li><p>Can span minutes or hours </p></li></ul><p>This is critical in real-world distributed systems.</p><h2>Eventual Consistency</h2><p>Saga patterns embrace eventual consistency.</p><p>Instead of requiring every service to update instantly:</p><ul><li><p>Systems become consistent over time<br></p></li></ul><p>This is one of the biggest mindset shifts when building distributed systems.</p><h2>Failure Handling in Sagas</h2><p>Failures are normal in distributed systems.</p><p>Networks fail.<br><br>Services go down.<br><br>Messages arrive late.</p><p>Saga patterns are designed with this reality in mind.</p><h2>Retry Strategies</h2><p>Retries are commonly used before compensation.</p><pre><code><code>Policy
    .Handle&lt;Exception&gt;()
    .RetryAsync(3);</code></code></pre><p>Libraries like <a href="https://www.pollydocs.org/">Polly</a> help implement resilience. </p><p>This connects naturally to upcoming reliability topics in the series.</p><h2>Idempotency Matters</h2><p>Messages may be delivered more than once.</p><p>Your handlers must safely handle duplicate processing.</p><p>Example:</p><pre><code><code>if (order.Status == "Processed")
{
    return;
}</code></code></pre><p>Without idempotency, duplicate events can create serious issues.</p><h2>Real-World Example: E-Commerce Checkout</h2><p>A checkout workflow may involve:</p><ul><li><p>Orders service </p></li><li><p>Inventory service </p></li><li><p>Payments service </p></li><li><p>Shipping service<br></p></li></ul><p>If shipping fails after payment succeeds:</p><ul><li><p>Refund payment </p></li><li><p>Release inventory </p></li><li><p>Cancel order </p></li></ul><p>The Saga coordinates recovery automatically.</p><h2>Sagas and Modular Monoliths</h2><p>Saga patterns are not only for microservices.</p><p>They also work well inside <a href="https://www.asptoday.com/p/building-modular-monoliths-in-aspnet">modular monoliths</a>. <br><br>Modules can:</p><ul><li><p>Publish events internally</p></li><li><p> Coordinate workflows </p></li><li><p>Remain loosely coupled </p></li></ul><p>This creates clean internal architecture.</p><h2>Monitoring and Observability</h2><p>Distributed workflows are harder to debug.</p><p>Tracking:</p><ul><li><p>Which step failed </p></li><li><p>Which compensation executed </p></li><li><p>Current workflow state </p></li></ul><p>Becomes essential.</p><p>Good logging and tracing are critical.</p><p>This prepares us perfectly for the upcoming observability topics later in the series.</p><h2>Common Mistakes to Avoid</h2><p>One common mistake is trying to make distributed systems behave like a single database transaction.</p><p>Another is forgetting compensation logic entirely.</p><p>Also avoid:</p><ul><li><p>Tight coupling between services </p></li><li><p>Shared databases across services </p></li><li><p>Ignoring idempotency </p></li></ul><h2>When NOT to Use Sagas</h2><p>Not every workflow needs a Saga.</p><p>Simple applications with:</p><ul><li><p>One database </p></li><li><p>One service </p></li><li><p>Simple transactions </p></li></ul><p>May not benefit from the added complexity.</p><p>Use Sagas when workflows become distributed and failure recovery matters.</p><h2>How Saga Patterns Fit Your Architecture Journey</h2><p>So far, you&#8217;ve learned:</p><ul><li><p>How systems communicate </p></li><li><p>How APIs evolve </p></li><li><p>How modular architectures work </p></li><li><p>How distributed messaging operates<br></p></li></ul><p>Saga patterns now connect all those pieces into coordinated workflows.</p><p>This is a major step toward designing resilient distributed systems.</p><h2>Closing Thoughts</h2><p>Distributed systems are powerful, but they introduce new challenges around consistency and failure handling.</p><p>Saga patterns provide a practical way to manage these challenges without relying on heavy distributed transaction protocols.</p><p>By combining:</p><ul><li><p>Messaging </p></li><li><p>Compensation logic </p></li><li><p>Event-driven workflows </p></li><li><p>Eventual consistency<br></p></li></ul><p>You can build scalable ASP.NET Core systems that remain reliable even when failures occur. Start simple, model workflows carefully, and design for failure from the beginning.</p><h2>Join The Community</h2><p>Enjoyed this article? <a href="https://www.asptoday.com/">Subscribe to ASP Today</a> for practical ASP.NET Core insights and real-world architecture patterns. Join the Substack Chat and connect with developers building modern systems. </p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.asptoday.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.asptoday.com/subscribe?"><span>Subscribe now</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Building Modular Monoliths in ASP.NET Core: Structure, Boundaries, and Scalability]]></title><description><![CDATA[Learn how to structure scalable ASP.NET Core apps using modular monolith architecture with clean boundaries and real-world patterns. #ASPToday #aspnetcore #dotnet #softwarearchitecture #cleanarchitecture]]></description><link>https://www.asptoday.com/p/building-modular-monoliths-in-aspnet</link><guid isPermaLink="false">https://www.asptoday.com/p/building-modular-monoliths-in-aspnet</guid><dc:creator><![CDATA[Jenny Muralidharan]]></dc:creator><pubDate>Tue, 05 May 2026 15:02:28 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!YwBq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5563816e-ff28-462f-b155-5e759dc128be_1672x941.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Not every application needs <a href="https://asptoday.substack.com/p/microservices-architecture-with-aspnet">microservices</a>. In fact, many systems become unnecessarily complex when broken apart too early. A modular monolith gives you the best of both worlds. The simplicity of a single deployment with the structure and scalability of well-defined modules.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YwBq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5563816e-ff28-462f-b155-5e759dc128be_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YwBq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5563816e-ff28-462f-b155-5e759dc128be_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!YwBq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5563816e-ff28-462f-b155-5e759dc128be_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!YwBq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5563816e-ff28-462f-b155-5e759dc128be_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!YwBq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5563816e-ff28-462f-b155-5e759dc128be_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YwBq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5563816e-ff28-462f-b155-5e759dc128be_1672x941.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5563816e-ff28-462f-b155-5e759dc128be_1672x941.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2219680,&quot;alt&quot;:&quot;Photorealistic cutaway building showing modular systems with interconnected sections and neon data flows. &quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.asptoday.com/i/196419714?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5563816e-ff28-462f-b155-5e759dc128be_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Photorealistic cutaway building showing modular systems with interconnected sections and neon data flows. " title="Photorealistic cutaway building showing modular systems with interconnected sections and neon data flows. " srcset="https://substackcdn.com/image/fetch/$s_!YwBq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5563816e-ff28-462f-b155-5e759dc128be_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!YwBq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5563816e-ff28-462f-b155-5e759dc128be_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!YwBq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5563816e-ff28-462f-b155-5e759dc128be_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!YwBq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5563816e-ff28-462f-b155-5e759dc128be_1672x941.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In this guide, we&#8217;ll explore how to design modular monoliths in <a href="https://asptoday.substack.com/p/introduction-to-aspnet-core-whats-new-and-why-it-matters">ASP.NET Core</a> with clean boundaries, maintainable structure, and long-term scalability. </p><h2>Why Modular Monoliths Matter</h2><p>When building modern applications, many teams jump straight into microservices.</p><p>It sounds like the right move: </p><ul><li><p>Independent services</p></li><li><p>Scalability </p></li><li><p>Flexibility </p></li></ul><p>But in reality, microservices introduce: </p><ul><li><p>Network complexity </p></li><li><p>Deployment overhead </p></li><li><p>Debugging challenges </p></li><li><p>Distributed data problems </p></li></ul><p>For many applications, this is too much, too soon.</p><p>A modular monolith provides a better starting point.</p><p>You still: </p><ul><li><p>Keep everything in one application </p></li><li><p>Avoid network overhead </p></li><li><p>Simplify deployment </p></li></ul><p>But you also:</p><ul><li><p>Organize your system into modules </p></li><li><p>Define clear boundaries </p></li><li><p>Prepare for future scaling </p></li></ul><p>This approach fits perfectly with everything we&#8217;ve built so far in the series, from data pipelines to API design:  </p><p><a href="https://asptoday.substack.com/p/designing-data-pipelines-in-aspnet">Designing Data Pipelines</a> </p><p><a href="https://www.asptoday.com/p/advanced-api-design-in-aspnet-core">Advanced API Design</a> </p><h2>What Is a Modular Monolith?</h2><p>A modular monolith is a single application that is internally divided into independent modules.</p><p>Each module:</p><ul><li><p>Has its own responsibility</p></li><li><p>Owns its own logic</p></li><li><p>Avoids direct coupling with others</p></li></ul><p>Think of it like a house.</p><p>It&#8217;s one building, but:</p><ul><li><p>The kitchen has its own purpose</p></li><li><p>The bedroom has its own purpose</p></li><li><p>The bathroom has its own purpose</p></li></ul><p>You don&#8217;t mix everything into one room. </p><h2>The Problem with Traditional Monoliths</h2><p>A traditional monolith often becomes messy over time.</p><p>You see:</p><ul><li><p>Shared models everywhere</p></li><li><p>Tight coupling between features</p></li><li><p>Hard-to-maintain code</p></li></ul><p>Everything depends on everything else.</p><p>This makes:</p><ul><li><p>Changes risky</p></li><li><p>Debugging difficult</p></li><li><p>Scaling painful</p></li></ul><p>A modular monolith solves this by enforcing structure. </p><h2>Core Principles of a Modular Monolith</h2><p>A good modular monolith follows a few simple rules.</p><p>Each module should:</p><ul><li><p>Own its own data and logic</p></li><li><p>Expose only what&#8217;s necessary</p></li><li><p>Avoid direct access to other modules&#8217; internals</p></li></ul><p>Communication happens through clear interfaces.</p><p>This idea connects with distributed data ownership concepts we explored earlier &#8212; even inside a single application. </p><h2>Structuring a Modular Monolith in ASP.NET Core</h2><p>Let&#8217;s look at a practical structure.</p><pre><code><code>/Modules
    /Orders
        Application
        Domain
        Infrastructure
    /Customers
        Application
        Domain
        Infrastructure
    /Products
        Application
        Domain
        Infrastructure

/API
    Controllers

/Shared
    Common utilities</code></code></pre><p>Each module is isolated. </p><h3>Inside a Module</h3><p>Each module typically has:</p><ul><li><p><strong>Domain</strong> &#8594; business logic</p></li><li><p><strong>Application</strong> &#8594; use cases</p></li><li><p><strong>Infrastructure</strong> &#8594; database, external services</p></li></ul><p>This structure keeps responsibilities clear. </p><h3>Enforcing Boundaries</h3><p>Structure alone is not enough.</p><p>You must enforce boundaries. </p><h3>Avoid This</h3><pre><code><code>var customer = _dbContext.Customers.First();</code></code></pre><p>Accessing another module&#8217;s data directly breaks boundaries. </p><h3>Do This Instead</h3><pre><code><code>var customer = await _customerService.GetCustomer(id);</code></code></pre><p>Always go through interfaces. </p><h2>Communication Between Modules</h2><p>Modules should not depend directly on each other&#8217;s internals.</p><p>Instead, use:</p><ul><li><p>Interfaces</p></li><li><p>Events</p></li><li><p>Application services</p></li></ul><p>This is similar to how services communicate in distributed systems, but without network overhead. </p><h2>Using MediatR for Internal Communication</h2><p>MediatR is commonly used for decoupled communication.</p><pre><code><code>dotnet add package MediatR</code></code></pre><h3>Example</h3><pre><code><code>public class CreateOrderCommand : IRequest
{
    public int CustomerId { get; set; }
} </code></code></pre><pre><code><code>public class CreateOrderHandler : IRequestHandler&lt;CreateOrderCommand&gt;
{
    public async Task&lt;Unit&gt; Handle(CreateOrderCommand request, CancellationToken cancellationToken)
    {
        // Handle logic here
        return Unit.Value;
    }
}</code></code></pre><p>This keeps modules loosely coupled.</p><h2>Data Ownership Inside a Monolith</h2><p>Each module should manage its own data.</p><p>Even if using the same database, logically separate it.</p><h3>Example</h3><ul><li><p>Orders module &#8594; Orders table </p></li><li><p>Customers module &#8594; Customers table </p></li></ul><p>Avoid sharing tables across modules.</p><p>This aligns with distributed data ownership concepts:<br><br>https://www.asptoday.com/p/designing-distributed-data-ownership-with-sqlite-databases</p><h2>Scaling a Modular Monolith</h2><p>One of the biggest advantages is scalability.</p><p>You can scale:</p><ul><li><p>Code complexity </p></li><li><p>Team collaboration </p></li><li><p>Features </p></li></ul><p>Without immediately needing microservices.</p><h2>When to Split into Microservices</h2><p>You should consider splitting when:</p><ul><li><p>A module becomes too large </p></li><li><p>It requires independent scaling </p></li><li><p>Teams need full autonomy </p></li></ul><p>Because your system is already modular, extraction becomes easier.</p><h2>Testing in Modular Monoliths</h2><p>Testing becomes simpler with clear boundaries.</p><p>You can:</p><ul><li><p>Test modules independently  </p></li><li><p>Mock interfaces </p></li><li><p>Avoid system-wide dependencies </p></li></ul><h2>Performance Benefits</h2><p>Because everything runs in the same process:</p><ul><li><p>No network latency</p></li><li><p>Faster execution </p></li><li><p>Easier debugging<br></p></li></ul><p>This is a major advantage over microservices.</p><h2>Real-World Example</h2><p>Imagine an e-commerce system.</p><p>Modules:</p><ul><li><p>Orders </p></li><li><p>Customers </p></li><li><p>Products </p></li><li><p>Payments<br></p></li></ul><p>Each module:</p><ul><li><p>Has its own logic </p></li><li><p>Communicates through interfaces </p></li><li><p>Remains independent </p></li></ul><p>You can later extract Payments into a microservice without rewriting everything.</p><h2>Common Mistakes to Avoid</h2><p>One common mistake is calling it modular but not enforcing boundaries.</p><p>Another is sharing everything through a common layer.</p><p>This defeats the purpose.</p><p>Also avoid over-engineering. Keep modules simple.</p><h2>How This Fits Your Architecture Journey</h2><p>So far, you&#8217;ve learned:</p><ul><li><p>How data flows (pipelines) </p></li><li><p>How APIs evolve </p></li><li><p>How systems communicate </p></li></ul><p>Now, modular monoliths give you:<br><br>&#128073; A clean internal structure</p><p>Before jumping into:<br><br>&#128073; Distributed systems <br>&#128073; Microservices</p><h2>Final Thoughts</h2><p>A modular monolith is not a compromise.</p><p>It&#8217;s a strategic choice.</p><p>It allows you to:</p><ul><li><p>Build clean systems </p></li><li><p>Scale gradually </p></li><li><p>Avoid unnecessary complexity </p></li></ul><p>Start modular, stay structured, and evolve when needed. </p><h2>Join The Community</h2><p>Enjoyed this article? Subscribe to <a href="https://www.asptoday.com/">ASP Today</a> for practical ASP.NET Core insights and real-world architecture patterns. Join the Substack Chat and connect with developers building modern systems. </p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.asptoday.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.asptoday.com/subscribe?"><span>Subscribe now</span></a></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p> </p>]]></content:encoded></item><item><title><![CDATA[Advanced API Design in ASP.NET Core Guide]]></title><description><![CDATA[Learn how to design scalable ASP.NET Core APIs using versioning, HATEOAS, and evolution strategies that keep clients working. #ASPToday #aspnetcore #dotnet #apidesign #backend]]></description><link>https://www.asptoday.com/p/advanced-api-design-in-aspnet-core</link><guid isPermaLink="false">https://www.asptoday.com/p/advanced-api-design-in-aspnet-core</guid><dc:creator><![CDATA[Jenny Muralidharan]]></dc:creator><pubDate>Tue, 28 Apr 2026 15:03:17 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!ZFaM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a3ffbb8-cce3-4515-b1d1-741b157798aa_1133x672.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>APIs are easy to build but hard to maintain. What works today can quickly break tomorrow when requirements change or clients depend on older behavior. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ZFaM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a3ffbb8-cce3-4515-b1d1-741b157798aa_1133x672.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ZFaM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a3ffbb8-cce3-4515-b1d1-741b157798aa_1133x672.png 424w, https://substackcdn.com/image/fetch/$s_!ZFaM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a3ffbb8-cce3-4515-b1d1-741b157798aa_1133x672.png 848w, https://substackcdn.com/image/fetch/$s_!ZFaM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a3ffbb8-cce3-4515-b1d1-741b157798aa_1133x672.png 1272w, https://substackcdn.com/image/fetch/$s_!ZFaM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a3ffbb8-cce3-4515-b1d1-741b157798aa_1133x672.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ZFaM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a3ffbb8-cce3-4515-b1d1-741b157798aa_1133x672.png" width="1133" height="672" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2a3ffbb8-cce3-4515-b1d1-741b157798aa_1133x672.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:672,&quot;width&quot;:1133,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1468362,&quot;alt&quot;:&quot;Airport control tower guiding planes with glowing routes, symbolizing API design and evolution.&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.asptoday.com/i/195708850?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a3ffbb8-cce3-4515-b1d1-741b157798aa_1133x672.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Airport control tower guiding planes with glowing routes, symbolizing API design and evolution." title="Airport control tower guiding planes with glowing routes, symbolizing API design and evolution." srcset="https://substackcdn.com/image/fetch/$s_!ZFaM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a3ffbb8-cce3-4515-b1d1-741b157798aa_1133x672.png 424w, https://substackcdn.com/image/fetch/$s_!ZFaM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a3ffbb8-cce3-4515-b1d1-741b157798aa_1133x672.png 848w, https://substackcdn.com/image/fetch/$s_!ZFaM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a3ffbb8-cce3-4515-b1d1-741b157798aa_1133x672.png 1272w, https://substackcdn.com/image/fetch/$s_!ZFaM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a3ffbb8-cce3-4515-b1d1-741b157798aa_1133x672.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In this guide, we&#8217;ll explore how to design APIs in ASP.NET Core that evolve safely using versioning, HATEOAS, and practical long-term design strategies. </p><h2>Why API Design Matters More Than You Think</h2><p>At the start, APIs feel simple. You expose endpoints, return data, and everything works.</p><p>But once your API is used by:</p><ul><li><p>Mobile apps</p></li><li><p>Frontend applications</p></li><li><p>Third-party integrations</p></li></ul><p>You lose the freedom to change things freely.</p><p>Even small changes can break clients.</p><p>This is where API design becomes critical. You are no longer just building endpoints. You are designing a <strong>contract</strong>.</p><p>And once that contract is used, it becomes very difficult to change. </p><h2>The Problem with &#8220;Simple&#8221; APIs</h2><p>Most APIs start like this:</p><pre><code>[HttpGet(&#8221;{id}&#8221;)]
public async Task&lt;IActionResult&gt; Get(int id)
{
    var product = await _service.GetProduct(id);
    return Ok(product);
}</code></pre><p>This works fine initially.</p><p>But over time:</p><ul><li><p>New fields are added </p></li><li><p>Old fields need to be removed </p></li><li><p>Data shapes change </p></li><li><p>New rules apply </p></li></ul><p>If your API isn&#8217;t designed to evolve, you&#8217;ll either:</p><ul><li><p>Break existing clients </p></li><li><p>Or accumulate messy workarounds </p></li></ul><h2>API Versioning: The Foundation of Change</h2><p>Versioning is the first step in building APIs that can evolve.</p><p>Instead of changing an API directly, you introduce versions.</p><p>Microsoft provides official guidance here:<br><a href="https://learn.microsoft.com/en-us/aspnet/web-api/overview/advanced/">https://learn.microsoft.com/aspnet/core/web-api/advanced/advanced-versioning</a></p><h2>Types of Versioning</h2><p>There are a few common approaches.</p><div><hr></div><h3>URL Versioning</h3><pre><code>/api/v1/products  
/api/v2/products  </code></pre><p>Simple and easy to understand. </p><h3>Header Versioning</h3><pre><code>api-version: 1.0</code></pre><p>Cleaner URLs, but harder to test manually. </p><h3>Query String Versioning</h3><pre><code>/api/products?version=1</code></pre><p>Simple, but less commonly used in modern APIs. </p><h2>Implementing Versioning in ASP.NET Core</h2><p>Install:</p><pre><code>dotnet add package Microsoft.AspNetCore.Mvc.Versioning</code></pre><h3>Configure Versioning</h3><pre><code>builder.Services.AddApiVersioning(options =&gt;
{
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.ReportApiVersions = true;
});</code></pre><h3>Use Versioned Controllers</h3><pre><code>[ApiVersion(&#8221;1.0&#8221;)]
[Route(&#8221;api/v{version:apiVersion}/products&#8221;)]
public class ProductsController : ControllerBase
{
}</code></pre><h2>When Should You Create a New Version?</h2><p>Not every change requires versioning.</p><p>Safe changes:</p><ul><li><p>Adding new fields</p></li><li><p>Adding optional parameters</p></li></ul><p>Breaking changes:</p><ul><li><p>Removing fields</p></li><li><p>Changing response structure</p></li><li><p>Changing behavior</p></li></ul><p>When in doubt, version. </p><h2>Designing APIs for Evolution</h2><p>Versioning is only part of the story.</p><p>Good API design reduces the need for versioning.</p><h3>Principles for Long-Term APIs</h3><p>Avoid exposing internal models directly.</p><p>Use DTOs instead.</p><pre><code>public class ProductResponse
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}</code></pre><p>This gives you flexibility to evolve your domain without breaking APIs. </p><h3>Keep Responses Stable</h3><p>Clients depend on consistency.</p><p>Even small changes can cause issues.</p><p>Instead of removing fields, consider deprecating them. </p><h2>Introducing HATEOAS</h2><p>HATEOAS stands for:</p><p>&#128073; Hypermedia As The Engine Of Application State</p><p>It sounds complex, but the idea is simple.</p><p>Instead of just returning data, your API also tells clients what they can do next.</p><h3>Basic Example</h3><p>Without HATEOAS:</p><pre><code>{
  &#8220;id&#8221;: 1,
  &#8220;name&#8221;: &#8220;Laptop&#8221;
}</code></pre><p>With HATEOAS:</p><pre><code>{
  &#8220;id&#8221;: 1,
  &#8220;name&#8221;: &#8220;Laptop&#8221;,
  &#8220;links&#8221;: [
    { &#8220;rel&#8221;: &#8220;self&#8221;, &#8220;href&#8221;: &#8220;/api/products/1&#8221; },
    { &#8220;rel&#8221;: &#8220;update&#8221;, &#8220;href&#8221;: &#8220;/api/products/1&#8221; },
    { &#8220;rel&#8221;: &#8220;delete&#8221;, &#8220;href&#8221;: &#8220;/api/products/1&#8221; }
  ]
}</code></pre><p>Now the API is guiding the client.</p><h2>Why HATEOAS Matters</h2><p>HATEOAS reduces tight coupling.</p><p>Clients don&#8217;t need to hardcode routes.</p><p>They discover actions dynamically.</p><p>This becomes useful in systems where APIs evolve frequently.</p><h2>Implementing HATEOAS in ASP.NET Core</h2><p>You can build link objects manually.</p><pre><code>public class Link
{
    public string Rel { get; set; }
    public string Href { get; set; }
}</code></pre><h3>Add Links to Responses</h3><pre><code>var response = new ProductResponse
{
    Name = product.Name,
    Price = product.Price,
    Links = new List&lt;Link&gt;
    {
        new Link { Rel = &#8220;self&#8221;, Href = $&#8221;/api/products/{product.Id}&#8221; }
    }
};</code></pre><h2>API Evolution Strategies</h2><p>As your API grows, managing change becomes critical.</p><h3>Backward Compatibility</h3><p>Always aim to keep older clients working.</p><p>Avoid breaking changes unless absolutely necessary.</p><h3>Deprecation</h3><p>Mark old endpoints as deprecated before removing them.</p><pre><code>[Obsolete(&#8221;This endpoint will be removed in v2&#8221;)]</code></pre><h3>Version Lifecycle</h3><p>Plan your API lifecycle:</p><ul><li><p>Introduce </p></li><li><p>Maintain </p></li><li><p>Deprecate </p></li><li><p>Remove </p></li></ul><h2>API Design and Distributed Systems</h2><p>In modern architectures, APIs are everywhere.</p><p>They connect:</p><ul><li><p>Microservices </p></li><li><p>External systems </p></li><li><p>Frontend clients </p></li></ul><p>This connects with previous topics like <a href="https://www.asptoday.com/p/aspnet-core-and-azure-service-bus">messaging</a> and <a href="https://www.asptoday.com/p/designing-data-pipelines-in-aspnet">pipelines</a>.  </p><p>APIs are often the entry point into these systems.</p><h2>Performance Considerations</h2><p>API design impacts performance.</p><p>Avoid:</p><ul><li><p>Over-fetching data </p></li><li><p>Returning large payloads </p></li><li><p>Unnecessary nested objects <br></p></li></ul><p>Consider pagination:</p><pre><code>/api/products?page=1&amp;pageSize=10</code></pre><h2>Real-World Example: Evolving a Product API</h2><p>Version 1:</p><pre><code>{
  &#8220;name&#8221;: &#8220;Laptop&#8221;,
  &#8220;price&#8221;: 1200
}</code></pre><p>Version 2 adds:</p><pre><code>{
  &#8220;name&#8221;: &#8220;Laptop&#8221;,
  &#8220;price&#8221;: 1200,
  &#8220;currency&#8221;: &#8220;USD&#8221;
}</code></pre><p>Instead of breaking v1, both versions coexist.</p><h2>Common Mistakes to Avoid</h2><p>One common mistake is skipping versioning entirely.</p><p>Another is over-versioning for minor changes.</p><p>Also, avoid tightly coupling APIs to database structures.</p><h2>Final Thoughts</h2><p>APIs are long-term contracts.</p><p>Designing them properly from the beginning saves time, reduces bugs, and avoids breaking clients.</p><p>By combining:</p><ul><li><p>Versioning </p></li><li><p>HATEOAS </p></li><li><p>Thoughtful evolution strategies </p></li></ul><p>You can build APIs that grow with your application instead of holding it back.</p><h2>Join The Community</h2><p>Enjoyed this article? Subscribe to <a href="https://www.asptoday.com/">ASP Today</a> for practical ASP.NET Core insights and real-world architecture patterns. Join the Substack Chat and connect with developers building modern systems. </p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.asptoday.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.asptoday.com/subscribe?"><span>Subscribe now</span></a></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p> </p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Implementing Distributed Caching in ASP.NET Core: Redis, Cache Invalidation, and Strategies]]></title><description><![CDATA[Learn how to use Redis and distributed caching in ASP.NET Core to build faster, scalable apps with smart cache strategies. #ASPToday #aspnetcore #dotnet #redis #caching]]></description><link>https://www.asptoday.com/p/implementing-distributed-caching</link><guid isPermaLink="false">https://www.asptoday.com/p/implementing-distributed-caching</guid><dc:creator><![CDATA[Jenny Muralidharan]]></dc:creator><pubDate>Tue, 21 Apr 2026 15:02:53 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!NeVV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad37d7c5-ca01-42de-971f-c3f010f453e0_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As applications grow, performance becomes just as important as functionality. One of the most effective ways to improve performance in <a href="https://www.asptoday.com/p/introduction-to-aspnet-core-whats-new-and-why-it-matters">ASP.NET Core</a> is by using distributed caching. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NeVV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad37d7c5-ca01-42de-971f-c3f010f453e0_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NeVV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad37d7c5-ca01-42de-971f-c3f010f453e0_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!NeVV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad37d7c5-ca01-42de-971f-c3f010f453e0_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!NeVV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad37d7c5-ca01-42de-971f-c3f010f453e0_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!NeVV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad37d7c5-ca01-42de-971f-c3f010f453e0_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NeVV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad37d7c5-ca01-42de-971f-c3f010f453e0_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ad37d7c5-ca01-42de-971f-c3f010f453e0_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3231687,&quot;alt&quot;:&quot;Operators monitor high-speed and freight trains from a control station at a busy railway junction. &quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.asptoday.com/i/194882581?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad37d7c5-ca01-42de-971f-c3f010f453e0_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Operators monitor high-speed and freight trains from a control station at a busy railway junction. " title="Operators monitor high-speed and freight trains from a control station at a busy railway junction. " srcset="https://substackcdn.com/image/fetch/$s_!NeVV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad37d7c5-ca01-42de-971f-c3f010f453e0_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!NeVV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad37d7c5-ca01-42de-971f-c3f010f453e0_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!NeVV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad37d7c5-ca01-42de-971f-c3f010f453e0_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!NeVV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad37d7c5-ca01-42de-971f-c3f010f453e0_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In this guide, we&#8217;ll explore how to implement caching with Redis, understand cache invalidation, and design strategies that keep your applications fast and scalable.</p><h2>Why Caching Becomes Essential</h2><p>In the early stages of building an application, performance often feels &#8220;good enough.&#8221; A request comes in, the application queries the database, and a response is returned.</p><p>But as usage increases, cracks start to appear.</p><p>You begin to notice:</p><ul><li><p>Slow response times</p></li><li><p>Increased database load</p></li><li><p>Repeated queries for the same data</p></li></ul><p>The problem is simple. Your system is doing the same work over and over again.</p><p>Caching solves this by remembering results so they don&#8217;t have to be recomputed.</p><p>Instead of asking the database every time, your application can return a cached result instantly.</p><p>This becomes especially important in systems that already process large volumes of data, such <a href="https://www.asptoday.com/p/designing-data-pipelines-in-aspnet">as the pipelines we explored previously</a>.  </p><h2>What Is Distributed Caching?</h2><p>Caching can be done in two ways.</p><p>In-memory caching stores data inside a single application instance. It&#8217;s fast but limited. Once the app restarts, the cache is gone, and it doesn&#8217;t work well across multiple servers.</p><p>Distributed caching, on the other hand, stores cached data in a shared external system.</p><p>This means:</p><ul><li><p>Multiple application instances share the same cache</p></li><li><p>Data persists beyond a single process</p></li><li><p>The system scales more easily</p></li></ul><p>Microsoft provides official guidance on distributed caching in ASP.NET Core:<br>https://learn.microsoft.com/aspnet/core/performance/caching/distributed</p><h2>Why Redis Is the Go-To Choice</h2><p>Redis is one of the most popular distributed caching systems.</p><p>Official documentation:<br><a href="https://redis.io/docs/">https://redis.io/docs/</a></p><p>It&#8217;s:</p><ul><li><p>Extremely fast (in-memory)</p></li><li><p>Simple to use</p></li><li><p>Designed for high throughput</p></li><li><p>Widely supported</p></li></ul><p>Redis stores data as key-value pairs, making it perfect for caching.</p><h2>Setting Up Redis in ASP.NET Core</h2><p>To use Redis, install the package:</p><pre><code>dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis</code></pre><div><hr></div><h2>Configure Redis</h2><p>In <code>Program.cs</code>:</p><pre><code>builder.Services.AddStackExchangeRedisCache(options =&gt;
{
    options.Configuration = &#8220;localhost:6379&#8221;;
    options.InstanceName = &#8220;MyAppCache&#8221;;
});</code></pre><div><hr></div><h2>Using the Cache</h2><pre><code>public class ProductService
{
    private readonly IDistributedCache _cache;

    public ProductService(IDistributedCache cache)
    {
        _cache = cache;
    }

    public async Task&lt;string&gt; GetProductAsync(string id)
    {
        var cacheKey = $&#8221;product:{id}&#8221;;

        var cached = await _cache.GetStringAsync(cacheKey);

        if (cached != null)
        {
            return cached;
        }

        var product = await FetchFromDatabase(id);

        await _cache.SetStringAsync(cacheKey, product,
            new DistributedCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10)
            });

        return product;
    }
}</code></pre><p>This pattern is simple and effective.</p><h2>Understanding Cache Invalidation</h2><p>Caching is powerful, but it introduces a new problem.</p><p>What happens when data changes?</p><p>If your cache still holds old data, users will see outdated information.</p><p>This is called <strong>cache invalidation</strong>, and it&#8217;s one of the hardest problems in software.</p><h2>Common Invalidation Strategies</h2><p>There are a few common approaches.</p><h3>Time-Based Expiration</h3><pre><code>AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10)</code></pre><p>The cache expires after a fixed time.</p><p>Simple, but not always accurate.</p><h3>Sliding Expiration</h3><p>The cache resets its lifetime every time it&#8217;s accessed.</p><p>Good for frequently used data.</p><h3>Manual Invalidation</h3><pre><code>await _cache.RemoveAsync(cacheKey);</code></pre><p>You explicitly remove cache when data changes.</p><p>This is more accurate but requires discipline.</p><h2>Cache Strategies That Actually Work</h2><p>Caching is not just about storing data. It&#8217;s about how and when you use it.</p><h3>Cache-Aside (Lazy Loading)</h3><p>This is the most common pattern.</p><ul><li><p>Check cache first </p></li><li><p>If not found, fetch from database </p></li><li><p>Store in cache </p></li></ul><p>This is what we used earlier.</p><h3>Write-Through</h3><p>Data is written to both the database and cache at the same time.</p><p>Ensures consistency but adds complexity.</p><h3>Write-Behind</h3><p>Data is written to cache first, then persisted later.</p><p>Improves performance but introduces risk.</p><h2>Choosing the Right Strategy</h2><p>Most ASP.NET Core applications start with <strong>cache-aside</strong> because it&#8217;s simple and reliable.</p><p>As systems grow, more advanced strategies can be introduced.</p><h2>Distributed Caching in Real Systems</h2><p>In real-world applications, caching is used everywhere.</p><h4>Example: Product Catalog</h4><p>Instead of querying the database for every request:</p><ul><li><p>Cache product details </p></li><li><p>Cache category lists </p></li><li><p>Cache frequently viewed items </p></li></ul><p>This reduces database load significantly.</p><h4>Example: API Responses</h4><p>Entire API responses can be cached.</p><p>This is especially useful for read-heavy endpoints.</p><h2>Caching and Data Pipelines</h2><p>Caching plays a critical role in data pipelines.</p><p>Instead of recalculating results repeatedly, intermediate results can be cached.</p><p>This improves performance and reduces system load.</p><p>If you recall the pipeline concepts from the previous blog, caching acts as a <strong>shortcut</strong> in the flow.</p><h2>Caching and NoSQL Systems</h2><p>Caching works well alongside NoSQL databases.</p><p>Since NoSQL systems often handle large volumes of data, caching frequently accessed data reduces pressure on the database.</p><p>This ties into our earlier NoSQL discussion:<br><br>https://www.asptoday.com/p/working-with-nosql-databases-in-aspnet-core</p><h2>Performance Considerations</h2><p>Caching improves performance, but misuse can cause issues.</p><p>Be careful of:</p><ul><li><p>Over-caching large objects </p></li><li><p>Storing unnecessary data </p></li><li><p>Using long expiration times </p></li></ul><p>Also, serialization matters. Storing large JSON objects can impact performance.</p><p><a href="https://www.asptoday.com/p/performance-tuning-aspnet-core-applications">Performance tuning</a> remains important: </p><h2>Handling Cache Failures</h2><p>Your system should not depend entirely on the cache.</p><p>If Redis goes down: </p><ul><li><p>The application should still work </p></li><li><p>It should fall back to the database </p></li></ul><p>Caching is an optimization, not a requirement.</p><h2>Real-World Example: Order Dashboard</h2><p>Imagine a dashboard showing recent orders. </p><p>Without caching:</p><ul><li><p>Every request hits the database  </p><p></p></li></ul><p>With caching:</p><ul><li><p>Results are stored for a short time </p></li><li><p>Multiple users see fast responses<br></p></li></ul><p>This significantly improves scalability.</p><h2>Common Mistakes to Avoid</h2><p>One common mistake is caching everything.</p><p>Not all data benefits from caching.</p><p>Another mistake is ignoring invalidation.</p><p>Stale data can cause serious issues.</p><p>Finally, avoid tightly coupling cache logic to business logic. Keep caching as a separate concern. </p><h2>Final Thoughts</h2><p>Distributed caching is one of the simplest and most effective ways to improve application performance.</p><p>By using Redis and applying the right strategies, you can reduce load, improve response times, and scale your application more efficiently.</p><p>Start with simple patterns like cache-aside, understand invalidation, and evolve your approach as your system grows.</p><h2>Join The Community</h2><p>Enjoyed this article? <a href="https://www.asptoday.com/">Subscribe to ASP Today</a> for practical ASP.NET Core insights and real-world architecture patterns. Join the Substack Chat and connect with developers building modern systems. </p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.asptoday.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.asptoday.com/subscribe?"><span>Subscribe now</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Designing Data Pipelines in ASP.NET Core: ETL, Streaming, and Real-Time Processing]]></title><description><![CDATA[Learn how to design ETL, streaming, and real-time data pipelines in ASP.NET Core with practical patterns and real-world examples. #ASPToday #aspnetcore #dotnet #datapipelines #softwarearchitecture]]></description><link>https://www.asptoday.com/p/designing-data-pipelines-in-aspnet</link><guid isPermaLink="false">https://www.asptoday.com/p/designing-data-pipelines-in-aspnet</guid><dc:creator><![CDATA[Jenny Muralidharan]]></dc:creator><pubDate>Tue, 14 Apr 2026 15:03:08 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!ghPc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa35fd61b-f8b4-4a89-9c01-7e2c0c99c117_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Modern applications don&#8217;t just store and return data, they continuously move, transform, and process it. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ghPc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa35fd61b-f8b4-4a89-9c01-7e2c0c99c117_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ghPc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa35fd61b-f8b4-4a89-9c01-7e2c0c99c117_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!ghPc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa35fd61b-f8b4-4a89-9c01-7e2c0c99c117_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!ghPc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa35fd61b-f8b4-4a89-9c01-7e2c0c99c117_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!ghPc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa35fd61b-f8b4-4a89-9c01-7e2c0c99c117_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ghPc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa35fd61b-f8b4-4a89-9c01-7e2c0c99c117_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a35fd61b-f8b4-4a89-9c01-7e2c0c99c117_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3185672,&quot;alt&quot;:&quot;Engineers manage a complex water irrigation and filtration system integrated with lush farmland. &quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.asptoday.com/i/194151298?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa35fd61b-f8b4-4a89-9c01-7e2c0c99c117_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Engineers manage a complex water irrigation and filtration system integrated with lush farmland. " title="Engineers manage a complex water irrigation and filtration system integrated with lush farmland. " srcset="https://substackcdn.com/image/fetch/$s_!ghPc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa35fd61b-f8b4-4a89-9c01-7e2c0c99c117_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!ghPc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa35fd61b-f8b4-4a89-9c01-7e2c0c99c117_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!ghPc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa35fd61b-f8b4-4a89-9c01-7e2c0c99c117_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!ghPc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa35fd61b-f8b4-4a89-9c01-7e2c0c99c117_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In this guide, we&#8217;ll explore how to design data pipelines in <a href="https://www.asptoday.com/p/introduction-to-aspnet-core-whats-new-and-why-it-matters">ASP.NET Core</a> using ETL, streaming, and real-time processing so your systems can handle data efficiently at scale. </p><h2>Why Data Pipelines Matter More Than Ever</h2><p>In earlier stages of building applications, data feels simple. A user submits a form, you save it to a database, and maybe return a response.</p><p>But as systems grow, that model breaks down.</p><p>Now your application needs to:</p><ul><li><p>Process large batches of data</p></li><li><p>React to events in real time</p></li><li><p>Move data between services</p></li><li><p>Transform raw input into useful output</p></li></ul><p>Instead of simple requests and responses, your system becomes a <strong>flow of data moving continuously</strong>.</p><p>This is where data pipelines come in.</p><p>A data pipeline is simply a structured way of moving data from one place to another while processing it along the way.</p><p>If you&#8217;ve worked through earlier topics in this series, you&#8217;ve already built pieces of this idea:</p><ul><li><p><a href="https://www.asptoday.com/p/file-upload-and-processing-in-aspnet">File uploads bring data into your system</a> </p></li><li><p><a href="https://www.asptoday.com/p/bulk-operations-and-batch-processing">Bulk processing handles large workloads</a> </p></li><li><p><a href="https://www.asptoday.com/p/aspnet-core-and-azure-service-bus">Messaging systems move data between services</a> </p></li><li><p><a href="https://www.asptoday.com/p/go-with-specific-libraries-epplus">Data export sends processed data out</a> </p></li></ul><p>A data pipeline connects all of these into one continuous system. </p><h2>Understanding the Three Core Pipeline Types</h2><p>Data pipelines generally fall into three categories.</p><p>ETL pipelines handle structured, scheduled data processing. Streaming pipelines process data as it arrives. Real-time pipelines react instantly to events.</p><p>Each serves a different purpose, and in most applications, you&#8217;ll use a combination of all three.</p><h2>ETL Pipelines in ASP.NET Core</h2><p>ETL stands for Extract, Transform, Load.</p><p>It&#8217;s one of the oldest and most widely used data processing patterns.</p><p>You:</p><ol><li><p>Extract data from a source</p></li><li><p>Transform it into the required format</p></li><li><p>Load it into a destination</p></li></ol><h2>A Simple ETL Example</h2><p>Imagine you receive a CSV file with sales data.</p><p>You:</p><ul><li><p>Read the file</p></li><li><p>Clean and transform the data</p></li><li><p>Store it in your database</p></li></ul><pre><code>public async Task ProcessCsvAsync(Stream fileStream)
{
    using var reader = new StreamReader(fileStream);

    while (!reader.EndOfStream)
    {
        var line = await reader.ReadLineAsync();
        var values = line.Split(&#8217;,&#8217;);

        var record = new SalesRecord
        {
            Product = values[0],
            Amount = decimal.Parse(values[1])
        };

        await _repository.SaveAsync(record);
    }
}</code></pre><p>This is a basic ETL pipeline.</p><h2>When to Use ETL</h2><p>ETL works best when:</p><ul><li><p>Data is processed in batches </p></li><li><p>Timing is not critical </p></li><li><p>You&#8217;re transforming structured datasets </p></li></ul><p>In real systems, ETL often runs in the background using scheduled jobs or workers.</p><h2>Streaming Pipelines</h2><p>Streaming pipelines process data continuously as it flows through the system.</p><p>Instead of waiting for a batch, data is handled as soon as it arrives.</p><p>This is common in:</p><ul><li><p>Event-driven systems </p></li><li><p>Messaging platforms </p></li><li><p>Real-time dashboards </p></li></ul><h2>Streaming with Messaging Systems</h2><p>In ASP.NET Core, streaming pipelines often rely on message brokers like Azure Service Bus.</p><pre><code>public async Task HandleMessageAsync(ServiceBusReceivedMessage message)
{
    var data = JsonSerializer.Deserialize&lt;OrderEvent&gt;(message.Body);

    await _processor.ProcessAsync(data);
}</code></pre><p>Each message becomes part of a continuous data stream.</p><p>Microsoft provides <a href="https://learn.microsoft.com/en-us/azure/service-bus-messaging/service-bus-messaging-overview">detailed guidance on messaging and event processing</a>:<br></p><h2>Why Streaming Matters</h2><p>Streaming pipelines:</p><ul><li><p>Reduce latency </p></li><li><p>Enable real-time processing </p></li><li><p>Scale better with distributed systems </p></li></ul><p>They are especially useful when systems must react immediately.</p><h2>Real-Time Processing Pipelines</h2><p>Real-time pipelines are about immediate reactions.</p><p>As soon as an event occurs, something happens.</p><p>Examples:</p><ul><li><p>Fraud detection </p></li><li><p>Notifications </p></li><li><p>Live analytics </p></li></ul><h2>Real-Time Example</h2><pre><code>public async Task ProcessOrderAsync(Order order)
{
    if (order.Amount &gt; 10000)
    {
        await _alertService.TriggerHighValueAlert(order);
    }
}</code></pre><p>This logic runs instantly as data enters the system.</p><h2>Designing a Pipeline in ASP.NET Core</h2><p>A well-designed pipeline has clear stages.</p><p>Think of it like a production line.</p><ol><li><p>Input (data enters) </p></li><li><p>Processing (transform, validate) </p></li><li><p>Output (store, send, or act)</p></li></ol><h2>Clean Pipeline Structure</h2><pre><code>public async Task HandleAsync(InputData input)
{
    var validated = _validator.Validate(input);

    var transformed = _transformer.Transform(validated);

    await _repository.SaveAsync(transformed);

    await _eventPublisher.PublishAsync(transformed);
}</code></pre><p>Each step has a clear responsibility.</p><p>This keeps the system maintainable.</p><h2>Handling Large Data Efficiently</h2><p>Pipelines often deal with large datasets.</p><p>Loading everything into memory is a common mistake.</p><p>Instead:</p><ul><li><p>Process data in chunks </p></li><li><p>Use streaming where possible </p></li><li><p>Avoid blocking operations </p></li></ul><p>This ties closely to patterns discussed in <a href="https://www.asptoday.com/p/bulk-operations-and-batch-processing">bulk processing</a>. </p><h2>Pipeline + NoSQL Integration</h2><p>Document databases work well with pipelines because they handle flexible data structures. </p><p>For example, raw events can be stored directly without rigid schemas.</p><p>This connects with <a href="https://www.asptoday.com/p/working-with-nosql-databases-in-aspnet">our NoSQL discussion</a>.  </p><h2>Error Handling in Pipelines</h2><p>Pipelines must handle failure gracefully.</p><p>A failed step should not break the entire system.</p><p>Common strategies include:</p><ul><li><p>Retry logic </p></li><li><p>Dead-letter queues </p></li><li><p>Logging and monitoring </p></li></ul><p>This is where reliability patterns become important, which we&#8217;ll explore deeper in upcoming blogs.</p><h2>Performance Considerations</h2><p>Data pipelines can become bottlenecks if not optimized.</p><p>Focus on:</p><ul><li><p>Asynchronous processing </p></li><li><p>Parallel execution </p></li><li><p>Efficient I/O operations </p></li></ul><p><a href="https://www.asptoday.com/p/performance-tuning-aspnet-core-applications">Performance tuning techniques</a> are critical here.  </p><h2>Real-World Example: Order Processing Pipeline</h2><p>Let&#8217;s bring everything together.</p><p>An order pipeline might look like this:</p><ul><li><p>User places an order </p></li><li><p>API validates input </p></li><li><p>Event is published </p></li><li><p>Worker processes payment </p></li><li><p>Data is stored </p></li><li><p>Notification is sent </p></li></ul><p>Each step is part of a pipeline.</p><p>Instead of one large process, the system flows step by step.</p><h2>How Pipelines Connect Everything</h2><p>At this point in your journey, pipelines tie everything together.</p><p>They connect:</p><ul><li><p>Input systems </p></li><li><p>Processing systems </p></li><li><p>Storage systems </p></li><li><p>Output systems </p></li></ul><p>Without pipelines, systems become disconnected.</p><p>With pipelines, everything flows smoothly.</p><h2>Final Thoughts</h2><p>Data pipelines are not just a feature. They are the backbone of modern applications.</p><p>As your systems grow, handling data as a continuous flow becomes essential.</p><p>ASP.NET Core provides the flexibility to build ETL, streaming, and real-time pipelines that scale with your needs.</p><p>Start simple, structure your pipeline clearly, and evolve it as your application grows.</p><h2>Join The Community</h2><p>Enjoyed this article? Subscribe to ASP Today for practical <a href="https://www.asptoday.com/">ASP.NET Core</a> insights and real-world architecture patterns. Join the Substack Chat and connect with developers building modern systems. </p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.asptoday.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.asptoday.com/subscribe?"><span>Subscribe now</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Advanced Data Validation and Business Rules in ASP.NET Core]]></title><description><![CDATA[Stop messy validation logic. Learn how to use FluentValidation and the Specification Pattern to build clean, scalable ASP.NET Core apps. #ASPToday #aspnetcore #dotnet #cleanarchitecture #softwaredesign]]></description><link>https://www.asptoday.com/p/advanced-data-validation-and-business</link><guid isPermaLink="false">https://www.asptoday.com/p/advanced-data-validation-and-business</guid><dc:creator><![CDATA[Jenny Muralidharan]]></dc:creator><pubDate>Tue, 07 Apr 2026 15:02:41 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Gx76!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff66958b0-20d0-4da8-8aee-ec11dcd07c0b_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Validation is where many applications quietly break down. What starts as a few simple checks quickly turns into scattered logic, duplicated rules, and hard-to-maintain code.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Gx76!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff66958b0-20d0-4da8-8aee-ec11dcd07c0b_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Gx76!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff66958b0-20d0-4da8-8aee-ec11dcd07c0b_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!Gx76!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff66958b0-20d0-4da8-8aee-ec11dcd07c0b_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!Gx76!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff66958b0-20d0-4da8-8aee-ec11dcd07c0b_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!Gx76!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff66958b0-20d0-4da8-8aee-ec11dcd07c0b_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Gx76!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff66958b0-20d0-4da8-8aee-ec11dcd07c0b_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f66958b0-20d0-4da8-8aee-ec11dcd07c0b_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2353873,&quot;alt&quot;:&quot;A man uses a tablet to monitor digital security alerts on various devices in a modern showroom. &quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.asptoday.com/i/193435795?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff66958b0-20d0-4da8-8aee-ec11dcd07c0b_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A man uses a tablet to monitor digital security alerts on various devices in a modern showroom. " title="A man uses a tablet to monitor digital security alerts on various devices in a modern showroom. " srcset="https://substackcdn.com/image/fetch/$s_!Gx76!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff66958b0-20d0-4da8-8aee-ec11dcd07c0b_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!Gx76!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff66958b0-20d0-4da8-8aee-ec11dcd07c0b_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!Gx76!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff66958b0-20d0-4da8-8aee-ec11dcd07c0b_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!Gx76!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff66958b0-20d0-4da8-8aee-ec11dcd07c0b_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In this guide, we&#8217;ll walk through how to structure validation and business rules in <a href="https://www.asptoday.com/p/introduction-to-aspnet-core-whats-new-and-why-it-matters">ASP.NET Core</a> using <a href="https://docs.fluentvalidation.net/en/latest/">FluentValidation</a> and the Specification Pattern so your code stays clean as your application grows.</p><h2>Why Validation Gets Messy So Quickly</h2><p>At the beginning of any project, validation feels simple.</p><p>You check if fields are required. You validate formats. Maybe you enforce a range or two. Everything lives neatly inside your models or controllers.</p><p>Then the application grows.</p><p><strong>Now validation depends on:</strong></p><ul><li><p>Other fields</p></li><li><p>Database state</p></li><li><p>User roles</p></li><li><p>Time or external conditions</p></li></ul><p>Suddenly, your controller is doing too much. Your services start duplicating logic. And your validation rules are scattered across multiple layers.</p><p>This is especially common in systems that already handle complex workflows like:</p><ul><li><p>Batch processing</p></li><li><p>Messaging systems</p></li><li><p>Data exports</p></li></ul><p>If you&#8217;ve worked through earlier topics like <a href="https://www.asptoday.com/p/bulk-operations-and-batch-processing">bulk processing</a> or <a href="https://www.asptoday.com/p/aspnet-core-and-azure-service-bus">Service Bus integration</a>, you&#8217;ve already seen how quickly complexity can build. </p><p>Validation needs structure just as much as data flow and processing do.</p><h2>The Problem with Data Annotations</h2><p>ASP.NET Core gives you built-in validation using Data Annotations.</p><pre><code>[Required]
[EmailAddress]
public string Email { get; set; }</code></pre><p>This works well for basic scenarios, but it starts to fall apart when things get more complex.</p><p><strong>Data Annotations:</strong></p><ul><li><p>Are tightly coupled to models </p></li><li><p>Don&#8217;t scale well for complex rules </p></li><li><p>Are hard to reuse across contexts </p></li><li><p>Don&#8217;t handle conditional logic cleanly </p></li></ul><p>Microsoft&#8217;s guidance also suggests using custom validation approaches when complexity increases: <br><a href="https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-10.0">https://learn.microsoft.com/aspnet/core/mvc/models/validation</a></p><p>This is where FluentValidation comes in.</p><h2>Introducing FluentValidation</h2><p>FluentValidation is a widely used library that allows you to define validation rules in a clean, readable, and testable way.</p><p><strong>Official docs:</strong><br><a href="https://docs.fluentvalidation.net/en/latest/">https://docs.fluentvalidation.net</a></p><p><strong>Install it:</strong></p><pre><code>dotnet add package FluentValidation.AspNetCore</code></pre><h2>A Better Way to Write Validation</h2><p>Instead of cluttering your models, you define validators separately.</p><pre><code>public class CreateOrderValidator : AbstractValidator&lt;CreateOrderRequest&gt;
{
    public CreateOrderValidator()
    {
        RuleFor(x =&gt; x.CustomerId)
            .NotEmpty();

        RuleFor(x =&gt; x.Amount)
            .GreaterThan(0);
    }
}</code></pre><p><strong>This approach:</strong></p><ul><li><p>Keeps models clean </p></li><li><p>Makes validation reusable </p></li><li><p>Improves readability </p></li><li><p>Makes testing easier </p></li></ul><h2>Plugging FluentValidation into ASP.NET Core</h2><p><strong>Register validators:</strong></p><pre><code>builder.Services.AddValidatorsFromAssemblyContaining&lt;CreateOrderValidator&gt;();</code></pre><p>Once registered, FluentValidation integrates directly into the request pipeline.</p><p>No extra controller logic needed.</p><h2>Real-World Validation Scenarios</h2><p>Basic validation is only the beginning.</p><p>In real systems, validation becomes dynamic.</p><h3>Conditional Validation</h3><pre><code>RuleFor(x =&gt; x.Discount)
    .GreaterThan(0)
    .When(x =&gt; x.HasDiscount);</code></pre><h3>Cross-Field Validation</h3><pre><code>RuleFor(x =&gt; x.EndDate)
    .GreaterThan(x =&gt; x.StartDate);</code></pre><h3>Custom Rules</h3><pre><code>RuleFor(x =&gt; x.Email)
    .Must(email =&gt; email.EndsWith(&#8221;@company.com&#8221;))
    .WithMessage(&#8221;Only company emails are allowed.&#8221;);</code></pre><h2>Validation vs Business Rules (Critical Distinction)</h2><p>This is where many systems go wrong.</p><p><strong>Validation answers:<br></strong><br>&#128073; &#8220;Is the data valid?&#8221;</p><p><strong>Business rules answer:<br></strong><br>&#128073; &#8220;Is this action allowed?&#8221;</p><p><strong>Example:</strong></p><ul><li><p>&#8220;Amount must be greater than 0&#8221; &#8212;&#8212;&#8212;&#8594; validation </p></li><li><p>&#8220;Customer must have sufficient balance&#8221; &#8212;&#8212;&#8212;&#8594; business rule<br></p></li></ul><p>Mixing these leads to confusion and tightly coupled code.</p><h2>Introducing the Specification Pattern</h2><p>The Specification Pattern is used to define business rules in a reusable and composable way.</p><p>Instead of writing logic inline, you encapsulate it.</p><h2>Basic Specification</h2><pre><code>public interface ISpecification&lt;T&gt;
{
    bool IsSatisfiedBy(T entity);
}</code></pre><p><strong>Example: Active Customer Rule</strong></p><pre><code>public class ActiveCustomerSpecification : ISpecification&lt;Customer&gt;
{
    public bool IsSatisfiedBy(Customer customer)
    {
        return customer.IsActive;
    }
}</code></pre><p><strong>Using It</strong></p><p>if (!activeCustomerSpec.IsSatisfiedBy(customer))<br>{<br>    throw new Exception(&#8221;Customer is not active.&#8221;);<br>}</p><p><strong>Now your business rules are:</strong></p><ul><li><p>Reusable </p></li><li><p>Testable </p></li><li><p>Decoupled </p></li></ul><h2>Combining FluentValidation and Specification</h2><p>This is where things get powerful.</p><p>FluentValidation:</p><ul><li><p>Handles input validation<br></p></li></ul><p>Specification Pattern:</p><ul><li><p>Handles business rules<br></p></li></ul><p>Together, they create a clean separation of concerns.</p><p><strong>Example Flow</strong></p><pre><code>public async Task Handle(CreateOrderRequest request)
{
    var customer = await _repository.GetCustomer(request.CustomerId);

    if (!_customerSpecification.IsSatisfiedBy(customer))
    {
        throw new Exception(&#8221;Customer does not meet requirements.&#8221;);
    }

    // Continue processing
}</code></pre><h2>Scaling Validation in Real Systems</h2><p>In modern applications, validation doesn&#8217;t just happen at the API level.</p><p>It happens across:</p><ul><li><p>Background jobs </p></li><li><p>Message handlers </p></li><li><p>Batch processors<br></p></li></ul><p>For example, if you&#8217;re exporting data or processing large batches, validation ensures data integrity before processing. </p><p><a href="https://www.asptoday.com/p/go-with-specific-libraries-epplus">https://www.asptoday.com/p/building-data-export-and-reporting-systems-in-aspnet-core</a></p><h2>Avoiding Common Pitfalls</h2><p>One of the most common mistakes is putting business logic inside validators.</p><p>Validators should remain lightweight.</p><p>Another issue is duplicating rules across layers.</p><p>Instead, centralize business rules using specifications.</p><p>Also, avoid overcomplicating things too early. Start simple and evolve your architecture as your application grows.</p><h2>Performance Considerations</h2><p>Validation can become a bottleneck if done incorrectly.</p><p><strong>Avoid:</strong></p><ul><li><p>Heavy database calls inside validators </p></li><li><p>Blocking operations </p></li><li><p>Repeated validations <br></p></li></ul><p>Keep validation fast and delegate heavier logic to specifications or services.</p><p>If your system handles high throughput, <a href="https://www.asptoday.com/p/performance-tuning-aspnet-core-applications">performance tuning</a> becomes important. </p><h2>Real-World Example: Order Processing System</h2><p>Let&#8217;s bring this together.</p><p><strong>Validation:</strong></p><ul><li><p>CustomerId must exist </p></li><li><p>Amount must be greater than zero<br></p></li></ul><p><strong>Business rules:</strong></p><ul><li><p>Customer must be active </p></li><li><p>Customer must have sufficient balance<br></p></li></ul><p>FluentValidation ensures the request is valid.</p><p>Specification ensures the operation is allowed.</p><p>This separation keeps your system clean and scalable.</p><h2>Why This Approach Matters Long-Term</h2><p><strong>As your system evolves:</strong></p><ul><li><p>Rules change </p></li><li><p>New conditions are added </p></li><li><p>Complexity increases<br></p></li></ul><p>Without structure, validation becomes fragile.</p><p>With FluentValidation and Specification:</p><ul><li><p>Rules stay organized </p></li><li><p>Code stays readable </p></li><li><p>Changes are easier to manage<br></p></li></ul><p>This becomes critical in systems that already involve:</p><ul><li><p>Distributed messaging </p></li><li><p>Data processing pipelines </p></li><li><p>Cloud-native architectures </p></li></ul><h2>Final Thoughts</h2><p>Validation is often underestimated, but it plays a critical role in system stability.</p><p>FluentValidation gives you a clean way to manage input validation, while the Specification Pattern helps structure business rules properly.</p><p>Together, they allow you to build systems that scale without turning into a maintenance nightmare.</p><p>If you invest in this structure early, your future self will thank you.</p><h2>Join The Community</h2><p>Enjoyed this post?<a href="https://www.asptoday.com/"> Subscribe to ASP Today</a> for practical ASP.NET Core insights and real-world architecture patterns. Join the Substack Chat and connect with developers building modern systems every day. </p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.asptoday.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.asptoday.com/subscribe?"><span>Subscribe now</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Working with NoSQL Databases in ASP.NET Core: MongoDB, CosmosDB, and Document Stores]]></title><description><![CDATA[Learn how to use MongoDB and Cosmos DB in ASP.NET Core with real-world patterns for scalable, flexible data storage. #ASPToday #aspnetcore #dotnet #mongodb #azure]]></description><link>https://www.asptoday.com/p/working-with-nosql-databases-in-aspnet</link><guid isPermaLink="false">https://www.asptoday.com/p/working-with-nosql-databases-in-aspnet</guid><dc:creator><![CDATA[Jenny Muralidharan]]></dc:creator><pubDate>Tue, 31 Mar 2026 15:03:10 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!QA9A!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc72749a3-06f2-4707-a886-a9c8aa21100c_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Modern applications increasingly rely on flexible, scalable data storage. In this guide, we&#8217;ll explore how to use MongoDB and Azure Cosmos DB with <a href="https://www.asptoday.com/p/introduction-to-aspnet-core-whats-new-and-why-it-matters">ASP.NET Core</a>, and how document databases can simplify development while supporting high-performance, cloud-native systems.</p><p>As applications grow, traditional relational databases don&#8217;t always fit every use case. They work well for structured data and strict relationships, but modern systems often deal with evolving schemas, distributed workloads, and large volumes of semi-structured data. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QA9A!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc72749a3-06f2-4707-a886-a9c8aa21100c_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QA9A!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc72749a3-06f2-4707-a886-a9c8aa21100c_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!QA9A!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc72749a3-06f2-4707-a886-a9c8aa21100c_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!QA9A!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc72749a3-06f2-4707-a886-a9c8aa21100c_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!QA9A!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc72749a3-06f2-4707-a886-a9c8aa21100c_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QA9A!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc72749a3-06f2-4707-a886-a9c8aa21100c_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c72749a3-06f2-4707-a886-a9c8aa21100c_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3346034,&quot;alt&quot;:&quot;Researcher in a lush jungle using a tablet to manage glowing cloud data and digital interfaces. &quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.asptoday.com/i/192686254?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc72749a3-06f2-4707-a886-a9c8aa21100c_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Researcher in a lush jungle using a tablet to manage glowing cloud data and digital interfaces. " title="Researcher in a lush jungle using a tablet to manage glowing cloud data and digital interfaces. " srcset="https://substackcdn.com/image/fetch/$s_!QA9A!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc72749a3-06f2-4707-a886-a9c8aa21100c_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!QA9A!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc72749a3-06f2-4707-a886-a9c8aa21100c_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!QA9A!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc72749a3-06f2-4707-a886-a9c8aa21100c_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!QA9A!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc72749a3-06f2-4707-a886-a9c8aa21100c_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>That&#8217;s where NoSQL databases come in.</p><p>NoSQL databases, especially document stores, allow you to store data in flexible formats like JSON. This makes it easier to evolve your application over time without constantly redesigning your schema.</p><p>In <a href="https://www.asptoday.com/p/performance-tuning-aspnet-core-applications">ASP.NET Core applications</a>, NoSQL databases are often used alongside relational databases, not as replacements, but as complementary tools.</p><p>In this article, we&#8217;ll explore how document databases work, how to integrate MongoDB and Azure Cosmos DB into ASP.NET Core, and how to design systems that take advantage of their strengths.</p><h2>What Are NoSQL and Document Databases?</h2><p>NoSQL databases are designed to handle data that doesn&#8217;t fit neatly into tables.</p><p>Instead of rows and columns, document databases store data as documents, typically in JSON format.</p><p>A simple example:</p><pre><code>{
  &#8220;id&#8221;: &#8220;1&#8221;,
  &#8220;name&#8221;: &#8220;Laptop&#8221;,
  &#8220;price&#8221;: 1200,
  &#8220;tags&#8221;: [&#8221;electronics&#8221;, &#8220;computers&#8221;]
}</code></pre><p>This structure is flexible. You can add fields without changing a schema.</p><p>Microsoft explains NoSQL databases as systems optimized for scalability, performance, and flexible data models: </p><p><a href="https://learn.microsoft.com/en-us/azure/cosmos-db/overview">https://learn.microsoft.com/en-us/azure/cosmos-db/overview</a></p><p>This flexibility is especially useful in applications where data changes frequently or varies between records.</p><h2>Why Use NoSQL in ASP.NET Core Applications</h2><p>NoSQL databases solve several common problems in modern applications.</p><p>They allow developers to move faster because there&#8217;s no rigid schema to manage. They scale horizontally, making them ideal for cloud environments. And they map naturally to object-oriented code since documents resemble C# objects.</p><p>For example, if you&#8217;re working with event-driven systems or messaging, storing JSON payloads directly can simplify your design. This aligns closely with patterns we discussed in our <a href="https://www.asptoday.com/p/aspnet-core-and-azure-service-bus">Azure Service Bus guide</a>. </p><p>Similarly, when handling large datasets or batch workflows, document databases can reduce complexity compared to relational joins. This ties into <a href="https://www.asptoday.com/p/bulk-operations-and-batch-processing">bulk processing strategies</a> we covered earlier. </p><h2>Getting Started with MongoDB in ASP.NET Core</h2><p>MongoDB is one of the most popular document databases. It stores data as BSON (binary JSON) and provides powerful querying capabilities.</p><p>Official documentation can be found here - <a href="https://www.mongodb.com/docs/">https://www.mongodb.com/docs/</a></p><p>Install the MongoDB driver: </p><pre><code>dotnet add package MongoDB.Driver</code></pre><h2>Configuring MongoDB</h2><p>In <code>appsettings.json</code>:</p><pre><code>{
  &#8220;MongoDbSettings&#8221;: {
    &#8220;ConnectionString&#8221;: &#8220;mongodb://localhost:27017&#8221;,
    &#8220;DatabaseName&#8221;: &#8220;MyAppDb&#8221;
  }
}</code></pre><p>Create a configuration class:</p><pre><code>public class MongoDbSettings
{
    public string ConnectionString { get; set; }
    public string DatabaseName { get; set; }
}</code></pre><p>Register it:</p><pre><code>builder.Services.Configure&lt;MongoDbSettings&gt;(
    builder.Configuration.GetSection(&#8221;MongoDbSettings&#8221;));</code></pre><h2>Creating a MongoDB Service</h2><pre><code>public class ProductService
{
    private readonly IMongoCollection&lt;Product&gt; _collection;

    public ProductService(IOptions&lt;MongoDbSettings&gt; settings)
    {
        var client = new MongoClient(settings.Value.ConnectionString);
        var database = client.GetDatabase(settings.Value.DatabaseName);

        _collection = database.GetCollection&lt;Product&gt;(&#8221;Products&#8221;);
    }

    public async Task&lt;List&lt;Product&gt;&gt; GetAsync() =&gt;
        await _collection.Find(_ =&gt; true).ToListAsync();

    public async Task CreateAsync(Product product) =&gt;
        await _collection.InsertOneAsync(product);
}</code></pre><p>This structure integrates cleanly with ASP.NET Core dependency injection.</p><h2>Querying Data in MongoDB</h2><p>MongoDB uses expressive filters for querying.</p><pre><code>var products = await _collection
    .Find(p =&gt; p.Price &gt; 1000)
    .ToListAsync();</code></pre><p>You can also build more complex queries using builders.</p><p>Document databases shine here because queries map naturally to object structures.</p><h2>Using Azure Cosmos DB with ASP.NET Core</h2><p>Azure Cosmos DB is Microsoft&#8217;s globally distributed NoSQL database.</p><p>It supports multiple APIs, including a MongoDB-compatible API and a native NoSQL API.</p><p>Official documentation:<br><br>https://learn.microsoft.com/azure/cosmos-db/</p><p>Install the SDK:</p><pre><code>dotnet add package Microsoft.Azure.Cosmos</code></pre><h2>Connecting to Cosmos DB</h2><pre><code>var client = new CosmosClient(connectionString);
var container = client.GetContainer(&#8221;MyDatabase&#8221;, &#8220;Products&#8221;);</code></pre><h2>Inserting Data</h2><pre><code>await container.CreateItemAsync(product, new PartitionKey(product.Id));</code></pre><h2>Querying Data</h2><pre><code>var query = container.GetItemQueryIterator&lt;Product&gt;(
    &#8220;SELECT * FROM c WHERE c.price &gt; 1000&#8221;);

var results = new List&lt;Product&gt;();

while (query.HasMoreResults)
{
    var response = await query.ReadNextAsync();
    results.AddRange(response);
}</code></pre><p>Cosmos DB is designed for global scale and low latency, making it ideal for distributed systems.</p><h2>Designing with Document Databases</h2><p>When using document databases, data modeling is different.</p><p>Instead of normalizing data across tables, you often embed related data in a single document.</p><p>For example:</p><pre><code>{
  &#8220;orderId&#8221;: &#8220;123&#8221;,
  &#8220;customer&#8221;: {
    &#8220;name&#8221;: &#8220;John&#8221;,
    &#8220;email&#8221;: &#8220;john@example.com&#8221;
  },
  &#8220;items&#8221;: [
    { &#8220;product&#8221;: &#8220;Laptop&#8221;, &#8220;price&#8221;: 1200 }
  ]
}</code></pre><p>This reduces the need for joins and improves read performance.</p><h2>When to Use MongoDB vs Cosmos DB</h2><p>MongoDB is a great choice when:</p><ul><li><p>You want full control over your database </p></li><li><p>You&#8217;re running on your own infrastructure </p></li><li><p>You need flexible querying </p></li></ul><p>Cosmos DB is ideal when:</p><ul><li><p>You want a fully managed cloud service</p></li><li><p>You need global distribution</p></li><li><p>You require automatic scaling </p></li></ul><p>In many ASP.NET Core applications, Cosmos DB fits naturally with Azure-based architectures.</p><h2>Performance Considerations</h2><p>NoSQL databases are fast, but they still require proper design.</p><p>Indexing is critical. Without indexes, queries can become slow.</p><p>MongoDB example:</p><pre><code>await _collection.Indexes.CreateOneAsync(
    new CreateIndexModel&lt;Product&gt;(
        Builders&lt;Product&gt;.IndexKeys.Ascending(p =&gt; p.Price)));</code></pre><p>Cosmos DB automatically indexes data, but you can customize indexing policies for better performance.</p><p>If you&#8217;re working with high-throughput systems, performance tuning becomes essential. For deeper insights, see <a href="https://www.asptoday.com/p/go-with-specific-libraries-epplus">our performance tuning guide</a>. </p><h2>Handling Large Data Workloads</h2><p>Document databases work well with large datasets, but you still need to manage memory and processing carefully.</p><p>Use pagination instead of loading all records at once.</p><p>Process data in batches when needed. This aligns with <a href="https://www.asptoday.com/p/go-with-specific-libraries-epplus">export and reporting workflows discussed here</a>. </p><h2>Real-World Example: Event Storage</h2><p>Imagine an application that processes user events.</p><p>Each event is stored as a document:</p><pre><code>{
  &#8220;eventType&#8221;: &#8220;OrderPlaced&#8221;,
  &#8220;timestamp&#8221;: &#8220;2026-01-01T10:00:00Z&#8221;,
  &#8220;data&#8221;: {
    &#8220;orderId&#8221;: &#8220;123&#8221;,
    &#8220;amount&#8221;: 500
  }
}</code></pre><p>This structure is flexible and works well with messaging systems.</p><p>It also integrates naturally with <a href="https://www.asptoday.com/p/file-upload-and-processing-in-aspnet">file-based workflows like uploads and processing</a>. </p><h2>Common Mistakes to Avoid</h2><p>One common mistake is treating NoSQL databases like relational databases.</p><p>Trying to normalize everything defeats the purpose of document storage.</p><p>Another mistake is ignoring indexing. Without indexes, performance will degrade quickly.</p><p>Finally, avoid storing excessively large documents. While document databases allow flexibility, extremely large records can impact performance.</p><h2>Closing Thoughts</h2><p>NoSQL databases are not a replacement for relational databases, but they are an essential tool for modern applications.</p><p>MongoDB and Azure Cosmos DB provide flexible, scalable options for storing and querying data in ASP.NET Core applications.</p><p>By understanding how document databases work and designing your data models appropriately, you can build systems that are easier to maintain and scale.</p><p>As your application grows, combining NoSQL with messaging, batch processing, and export systems creates a strong foundation for handling real-world workloads.</p><h2>Join The Community</h2><p>Enjoyed this article? Subscribe to <a href="https://www.asptoday.com/">ASP Today</a> for practical ASP.NET Core insights and real-world development patterns. Join our Substack Chat to connect with developers building modern applications. </p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.asptoday.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.asptoday.com/subscribe?"><span>Subscribe now</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Building Data Export and Reporting Systems in ASP.NET Core: Excel, PDF, and CSV Generation]]></title><description><![CDATA[Learn how to generate Excel, PDF, and CSV reports in ASP.NET Core using real-world patterns and libraries. #ASPToday #aspnetcore #dotnet #webdevelopment #backend]]></description><link>https://www.asptoday.com/p/go-with-specific-libraries-epplus</link><guid isPermaLink="false">https://www.asptoday.com/p/go-with-specific-libraries-epplus</guid><dc:creator><![CDATA[Jenny Muralidharan]]></dc:creator><pubDate>Tue, 24 Mar 2026 15:02:31 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!gtfe!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7360e16-bba6-43fc-876a-c5d93f6330f5_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Exporting data is a core requirement in many applications, whether it&#8217;s generating reports for business users, downloading datasets, or sharing insights externally. In this guide, we&#8217;ll walk through how to build reliable data export systems in <a href="https://www.asptoday.com/p/introduction-to-aspnet-core-whats-new-and-why-it-matters">ASP.NET Core</a> using Excel, PDF, and CSV generation techniques that work in real-world applications.</p><p>Most applications eventually need reporting. Users want to download data, finance teams want structured reports, and operations teams need exports they can analyze offline. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gtfe!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7360e16-bba6-43fc-876a-c5d93f6330f5_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gtfe!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7360e16-bba6-43fc-876a-c5d93f6330f5_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!gtfe!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7360e16-bba6-43fc-876a-c5d93f6330f5_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!gtfe!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7360e16-bba6-43fc-876a-c5d93f6330f5_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!gtfe!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7360e16-bba6-43fc-876a-c5d93f6330f5_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gtfe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7360e16-bba6-43fc-876a-c5d93f6330f5_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a7360e16-bba6-43fc-876a-c5d93f6330f5_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2724466,&quot;alt&quot;:&quot;Busy shipping port at sunset with workers using tablets to manage glowing digital cargo data. &quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.asptoday.com/i/191878447?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7360e16-bba6-43fc-876a-c5d93f6330f5_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Busy shipping port at sunset with workers using tablets to manage glowing digital cargo data. " title="Busy shipping port at sunset with workers using tablets to manage glowing digital cargo data. " srcset="https://substackcdn.com/image/fetch/$s_!gtfe!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7360e16-bba6-43fc-876a-c5d93f6330f5_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!gtfe!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7360e16-bba6-43fc-876a-c5d93f6330f5_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!gtfe!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7360e16-bba6-43fc-876a-c5d93f6330f5_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!gtfe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7360e16-bba6-43fc-876a-c5d93f6330f5_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>At first, it seems simple. Just take some data and convert it into a file. But in production systems, exporting data introduces real challenges. Large datasets can impact performance. Formatting matters. File generation can block requests. And different formats require different handling.</p><p>ASP.NET Core gives you the flexibility to build clean, scalable export systems. When combined with the right libraries, you can generate professional reports without adding unnecessary complexity.</p><p>In this article, we&#8217;ll focus on three common formats: Excel, PDF, and CSV. We&#8217;ll look at how to generate each, when to use them, and how to structure your code so it scales as your application grows. </p><h2>Why Data Export Matters</h2><p>Data export is more than just a feature. It&#8217;s part of how users interact with your system.</p><p>Business users often rely on exports to:</p><ul><li><p>Analyze data in Excel</p></li><li><p>Share reports with stakeholders</p></li><li><p>Archive records</p></li><li><p>Integrate with other systems</p></li></ul><p>In many cases, exporting data becomes just as important as displaying it in the UI.</p><p>If you&#8217;re already working with large datasets or background processing, this ties closely to how bulk data workflows are handled. We explored similar patterns in <a href="https://www.asptoday.com/p/bulk-operations-and-batch-processing">our guide on batch processing in ASP.NET Core</a>. </p><h2>Designing a Simple Export Architecture</h2><p>Before jumping into libraries, it helps to structure your export logic properly.</p><p>A clean approach is:</p><ul><li><p>Controller handles the request</p></li><li><p>Service prepares the data</p></li><li><p>Export service generates the file</p></li></ul><p>This keeps your application maintainable.</p><p>Example:</p><pre><code>[HttpGet(&#8221;export&#8221;)]
public async Task&lt;IActionResult&gt; Export()
{
    var data = await _reportService.GetDataAsync();

    var fileBytes = _exportService.GenerateCsv(data);

    return File(fileBytes, &#8220;text/csv&#8221;, &#8220;report.csv&#8221;);
}</code></pre><p>This pattern works for all formats.</p><h2>Generating CSV Files with CsvHelper</h2><p>CSV is the simplest format and often the most efficient.</p><p>It&#8217;s lightweight, fast, and easy to generate. It&#8217;s also widely supported.</p><p>We&#8217;ll use <strong>CsvHelper</strong>, a popular library for handling CSV data in .NET.</p><p>Install:</p><pre><code>dotnet add package CsvHelper</code></pre><p>Example:</p><pre><code>public byte[] GenerateCsv&lt;T&gt;(IEnumerable&lt;T&gt; data)
{
    using var memoryStream = new MemoryStream();
    using var writer = new StreamWriter(memoryStream);
    using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture);

    csv.WriteRecords(data);
    writer.Flush();

    return memoryStream.ToArray();
}</code></pre><p>This generates a CSV file directly in memory.</p><p>CSV works best when:</p><ul><li><p>You need raw data export</p></li><li><p>Formatting is not important</p></li><li><p>Performance matters<br></p></li></ul><h2>Generating Excel Files with EPPlus</h2><p>Excel is more powerful than CSV because it supports formatting, formulas, and multiple sheets.</p><p>We&#8217;ll use <strong>EPPlus</strong>.</p><p>Install:</p><pre><code>dotnet add package EPPlus</code></pre><p>Example:</p><pre><code>public byte[] GenerateExcel(List&lt;Product&gt; products)
{
    using var package = new ExcelPackage();
    var worksheet = package.Workbook.Worksheets.Add(&#8221;Products&#8221;);

    worksheet.Cells[1, 1].Value = &#8220;Id&#8221;;
    worksheet.Cells[1, 2].Value = &#8220;Name&#8221;;
    worksheet.Cells[1, 3].Value = &#8220;Price&#8221;;

    for (int i = 0; i &lt; products.Count; i++)
    {
        worksheet.Cells[i + 2, 1].Value = products[i].Id;
        worksheet.Cells[i + 2, 2].Value = products[i].Name;
        worksheet.Cells[i + 2, 3].Value = products[i].Price;
    }

    return package.GetAsByteArray();
}</code></pre><p>Excel is ideal when:</p><ul><li><p>Users need structured reports</p></li><li><p>Formatting matters</p></li><li><p>Data needs to be analyzed<br></p></li></ul><p>For large exports, you should avoid loading everything into memory at once. This is where streaming or batching becomes important, similar to techniques discussed in <a href="https://www.asptoday.com/p/file-upload-and-processing-in-aspnet">our file upload and processing guide</a>. </p><h2>Generating PDFs with QuestPDF</h2><p>PDFs are best for presentation-ready reports.</p><p>We&#8217;ll use <strong>QuestPDF</strong>, which is simple and modern.</p><p>Install:</p><pre><code>dotnet add package QuestPDF</code></pre><p>Example:</p><pre><code>public byte[] GeneratePdf(List&lt;Product&gt; products)
{
    return Document.Create(container =&gt;
    {
        container.Page(page =&gt;
        {
            page.Content().Column(column =&gt;
            {
                column.Item().Text(&#8221;Product Report&#8221;).FontSize(20);

                foreach (var product in products)
                {
                    column.Item().Text($&#8221;{product.Name} - {product.Price}&#8221;);
                }
            });
        });
    }).GeneratePdf();
}</code></pre><p>PDF is best when:</p><ul><li><p>You need formatted reports</p></li><li><p>Documents are shared externally</p></li><li><p>Layout matters<br></p></li></ul><h2>Handling Large Exports</h2><p>Large exports can slow down your application if handled incorrectly.</p><p>Instead of generating files during the request, you can:</p><ul><li><p>Queue the export job</p></li><li><p>Process it in the background</p></li><li><p>Notify the user when it&#8217;s ready<br></p></li></ul><p>This is especially important for large datasets.</p><p>You can use background services or queues, similar to patterns used in <a href="https://www.asptoday.com/p/aspnet-core-and-azure-service-bus">Azure Service Bus messaging systems</a>. </p><h2>Streaming File Downloads</h2><p>For large files, avoid loading everything into memory.</p><p>Instead, stream the response:</p><pre><code>return File(stream, &#8220;application/octet-stream&#8221;, &#8220;report.xlsx&#8221;);</code></pre><p>Streaming improves performance and reduces memory usage.</p><h2>Choosing the Right Format</h2><p>Each format serves a different purpose.</p><p>CSV is best for raw data.<br><br>Excel is best for analysis.<br><br>PDF is best for presentation.</p><p>In many applications, you&#8217;ll support all three.</p><h2>Real-World Example: Sales Report Export</h2><p>Imagine a dashboard where users can export sales data.</p><p>The workflow might look like this:</p><p>The user selects a date range.<br><br>The system retrieves data.<br><br>The user chooses a format.<br><br>The system generates the file.</p><p>For small datasets, this can happen instantly.</p><p>For large datasets, the export runs in the background and the user downloads it later.</p><p>This keeps the application responsive.</p><h2>Performance Considerations</h2><p>Exporting data impacts performance.</p><p>Keep in mind:</p><ul><li><p>Avoid large in-memory operations</p></li><li><p>Use async calls</p></li><li><p>Consider background processing </p></li><li><p>Monitor export usage<br></p></li></ul><p>Performance tuning plays a big role here. If you want to go deeper, see <a href="https://www.asptoday.com/p/performance-tuning-aspnet-core-applications">our performance tuning guide</a>. </p><h2>Closing Thoughts</h2><p>Data export systems are a core part of modern applications. They allow users to work with data outside your system and provide real value.</p><p><a href="https://www.asptoday.com/p/introduction-to-aspnet-core-whats-new-and-why-it-matters">ASP.NET Core</a> makes it easy to build these systems, but the key is structuring them properly. By separating concerns, using the right libraries, and handling large datasets carefully, you can build export features that scale.</p><p>Start simple with CSV, expand into Excel for richer reports, and use PDF when presentation matters.</p><h2>Join The Community</h2><p>Enjoyed this article? Subscribe to ASP Today for practical <a href="https://www.asptoday.com/">ASP.NET Core</a> insights. Join our Substack Chat to connect with developers building real-world applications. </p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.asptoday.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.asptoday.com/subscribe?"><span>Subscribe now</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[ASP.NET Core and Azure Service Bus: Message Queues, Topics, and Subscriptions]]></title><description><![CDATA[Learn how to build scalable, reliable messaging systems using Azure Service Bus with ASP.NET Core. Queues, topics, and real-world patterns explained. #ASPToday #aspnetcore #azure #dotnet #cloud]]></description><link>https://www.asptoday.com/p/aspnet-core-and-azure-service-bus</link><guid isPermaLink="false">https://www.asptoday.com/p/aspnet-core-and-azure-service-bus</guid><dc:creator><![CDATA[Jenny Muralidharan]]></dc:creator><pubDate>Tue, 17 Mar 2026 15:02:21 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!fsfH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24e310fe-0769-4cc1-ac36-ab470b98068e_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Modern applications rarely operate as a single, tightly connected system. Instead, they rely on messaging to communicate between services reliably and at scale. In this guide, we&#8217;ll explore how to use Azure Service Bus with ASP.NET Core to implement queues, topics, and subscriptions that keep your applications responsive, resilient, and ready for growth.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fsfH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24e310fe-0769-4cc1-ac36-ab470b98068e_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fsfH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24e310fe-0769-4cc1-ac36-ab470b98068e_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!fsfH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24e310fe-0769-4cc1-ac36-ab470b98068e_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!fsfH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24e310fe-0769-4cc1-ac36-ab470b98068e_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!fsfH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24e310fe-0769-4cc1-ac36-ab470b98068e_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fsfH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24e310fe-0769-4cc1-ac36-ab470b98068e_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/24e310fe-0769-4cc1-ac36-ab470b98068e_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2451593,&quot;alt&quot;:&quot;Central data hub connecting screens for payments, notifications, recommendations, and inventory. &quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.asptoday.com/i/191218803?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24e310fe-0769-4cc1-ac36-ab470b98068e_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Central data hub connecting screens for payments, notifications, recommendations, and inventory. " title="Central data hub connecting screens for payments, notifications, recommendations, and inventory. " srcset="https://substackcdn.com/image/fetch/$s_!fsfH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24e310fe-0769-4cc1-ac36-ab470b98068e_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!fsfH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24e310fe-0769-4cc1-ac36-ab470b98068e_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!fsfH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24e310fe-0769-4cc1-ac36-ab470b98068e_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!fsfH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24e310fe-0769-4cc1-ac36-ab470b98068e_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>As applications grow, direct communication between components becomes harder to manage. One service calling another synchronously might work early on, but it quickly introduces problems. If one service is slow or unavailable, everything depending on it starts to fail. That&#8217;s where messaging comes in.</p><p>Messaging allows systems to communicate without being directly connected. Instead of waiting for a response, one part of your application sends a message, and another part processes it when it&#8217;s ready. This decoupling is one of the key building blocks of scalable systems.</p><p>Azure Service Bus is Microsoft&#8217;s fully managed message broker designed for enterprise-grade messaging. It provides reliable message delivery, supports complex communication patterns, and integrates naturally with ASP.NET Core applications.</p><p>In this article, we&#8217;ll walk through how Azure Service Bus works, how to integrate it into your ASP.NET Core applications, and how to design messaging patterns that scale in real-world systems. </p><h2>Why Messaging Matters in Modern Applications</h2><p>In traditional applications, components often communicate directly using HTTP. While simple, this approach creates tight coupling. If one service is unavailable, the entire workflow can break.</p><p>Messaging solves this problem by introducing an intermediary layer.</p><p>Instead of Service A calling Service B directly, Service A sends a message to a queue. Service B processes that message independently. This means Service A doesn&#8217;t need to wait, retry, or even know whether Service B is currently online.</p><p>This approach improves:</p><p>Reliability, because messages are stored until processed<br>Scalability, because processing can happen in parallel<br>Resilience, because failures don&#8217;t cascade across services</p><p>Azure Service Bus is built specifically to handle these scenarios at scale. </p><p>According to Microsoft&#8217;s official documentation, it supports reliable message delivery, ordering, transactions, and advanced routing patterns<br><a href="https://learn.microsoft.com/en-us/azure/service-bus-messaging/service-bus-messaging-overview">https://learn.microsoft.com/azure/service-bus-messaging/service-bus-messaging-overview</a></p><h2>Understanding Azure Service Bus Concepts</h2><p>Before diving into implementation, it&#8217;s important to understand the core building blocks.</p><h2>Queues</h2><p>Queues follow a simple pattern. A sender places a message into a queue, and a receiver processes it.</p><p>Each message is processed by a single consumer. This is useful for background jobs, task processing, and workloads that should only run once.</p><p>Think of queues as a to-do list. Each task is picked up and completed by one worker.</p><h2>Topics and Subscriptions</h2><p>Topics extend the queue model by allowing multiple consumers to receive the same message.</p><p>Instead of one receiver, messages published to a topic can be delivered to multiple subscriptions. Each subscription acts like its own queue.</p><p>This pattern is called publish-subscribe.</p><p>For example, when an order is placed:</p><p>One service processes payment<br>Another updates inventory<br>Another sends notifications</p><p>All of them receive the same message independently.</p><h2>Dead-Letter Queues</h2><p>Sometimes messages fail to process. Instead of losing them, Azure Service Bus moves them to a dead-letter queue.</p><p>This allows developers to inspect failed messages, debug issues, and retry processing.</p><h2>Setting Up Azure Service Bus</h2><p>To get started, you&#8217;ll need an Azure Service Bus namespace.</p><p>You can create one in the Azure Portal and then create:</p><ul><li><p>A queue for simple messaging</p></li><li><p>A topic for publish-subscribe scenarios</p></li></ul><p>Once created, you&#8217;ll get a connection string, which your ASP.NET Core application will use.</p><p>Install the official SDK:</p><pre><code>dotnet add package Azure.Messaging.ServiceBus</code></pre><p>Microsoft&#8217;s SDK documentation provides details on installation and usage<br><a href="https://learn.microsoft.com/en-us/dotnet/api/overview/azure/messaging.servicebus-readme?view=azure-dotnet">https://learn.microsoft.com/dotnet/api/overview/azure/messaging.servicebus-readme</a></p><h2>Sending Messages from ASP.NET Core</h2><p>Let&#8217;s start with sending messages to a queue.</p><pre><code>var client = new ServiceBusClient(connectionString);
var sender = client.CreateSender(&#8221;orders-queue&#8221;);

var message = new ServiceBusMessage(&#8221;New order received&#8221;);

await sender.SendMessageAsync(message);</code></pre><p>This code creates a message and sends it to a queue.</p><p>In a real application, the message would likely be serialized JSON representing a business event.</p><h2>Receiving Messages in ASP.NET Core</h2><p>Receiving messages typically happens in a background service. </p><p>If you&#8217;re new to background processing patterns, you may also find our guide on batch processing in ASP.NET Core helpful:<br><a href="https://www.asptoday.com/p/bulk-operations-and-batch-processing">https://www.asptoday.com/p/implementing-bulk-operations-and-batch-processing-in-aspnet-core-applications</a></p><pre><code>public class OrderProcessor : BackgroundService
{
    private readonly ServiceBusProcessor _processor;

    public OrderProcessor(ServiceBusClient client)
    {
        _processor = client.CreateProcessor(&#8221;orders-queue&#8221;);
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _processor.ProcessMessageAsync += ProcessMessage;
        _processor.ProcessErrorAsync += ErrorHandler;

        await _processor.StartProcessingAsync(stoppingToken);
    }

    private async Task ProcessMessage(ProcessMessageEventArgs args)
    {
        var body = args.Message.Body.ToString();

        // Process message here

        await args.CompleteMessageAsync(args.Message);
    }

    private Task ErrorHandler(ProcessErrorEventArgs args)
    {
        // Log error
        return Task.CompletedTask;
    }
}</code></pre><p>This pattern allows your application to process messages continuously in the background.</p><h2>Using Topics and Subscriptions</h2><p>Publishing to a topic is similar to sending to a queue.</p><pre><code>var sender = client.CreateSender(&#8221;orders-topic&#8221;);

await sender.SendMessageAsync(new ServiceBusMessage(&#8221;Order placed&#8221;));</code></pre><p>Each subscription receives its own copy of the message.</p><p>Subscribers process messages independently, which allows multiple services to react to the same event. </p><h2>Message Serialization Best Practices</h2><p>Messages should be structured and versioned carefully.</p><p>A common approach is JSON serialization:</p><pre><code>var payload = JsonSerializer.Serialize(order);

var message = new ServiceBusMessage(payload);</code></pre><p>Avoid tightly coupling message formats to internal models. Instead, use contracts that can evolve over time.</p><h2>Reliability and Message Delivery</h2><p>Azure Service Bus guarantees at-least-once delivery. This means messages may be delivered more than once.</p><p>Your application should handle duplicate messages safely.</p><p>This is known as idempotency.</p><p>For example, if processing an order, ensure that processing the same message twice does not create duplicate records.</p><h2>Handling Failures and Retries</h2><p>Failures are inevitable in distributed systems.</p><p>Azure Service Bus includes built-in retry mechanisms. If processing fails, messages can be retried automatically.</p><p>If retries exceed a limit, messages move to the dead-letter queue.</p><p>You can then inspect and reprocess them.</p><h2>Scaling Message Processing</h2><p>One of the biggest advantages of messaging is scalability.</p><p>You can run multiple instances of your worker service, and Azure Service Bus will distribute messages across them.</p><p>This allows your system to handle increased workloads without changing your core logic.</p><h2>Integrating with ASP.NET Core Dependency Injection</h2><p>You should register Service Bus clients using dependency injection.</p><pre><code>builder.Services.AddSingleton(_ =&gt;
    new ServiceBusClient(connectionString));</code></pre><p>This ensures efficient reuse of connections and improves performance.</p><h2>Monitoring and Observability</h2><p>Monitoring messaging systems is critical.</p><p>You should track:</p><ul><li><p>message throughput</p></li><li><p>processing time</p></li><li><p>failure rates</p></li><li><p>dead-letter queue size</p></li></ul><p>Azure Monitor and Application Insights provide visibility into Service Bus operations<br><a href="https://learn.microsoft.com/en-us/azure/azure-monitor/">https://learn.microsoft.com/azure/azure-monitor</a> </p><p>Observability also ties closely with application performance. For a deeper dive into improving system performance, see our guide on performance tuning in ASP.NET Core:<br><a href="https://www.asptoday.com/p/performance-tuning-aspnet-core-applications">https://www.asptoday.com/p/performance-tuning-aspnet-core-applications</a></p><h2>Real-World Example: Order Processing System</h2><p>Imagine an e-commerce platform.</p><p>When a user places an order, the system publishes a message to a topic.</p><p>Different services react:</p><p>The payment service processes payment<br><br>The inventory service updates stock<br><br>The notification service sends confirmation emails</p><p>Each service operates independently.</p><p>If one service fails, others continue working.</p><p>This design improves resilience and scalability. </p><p>If you're building foundational knowledge, you can also start with our introduction to ASP.NET Core:<br><a href="https://www.asptoday.com/p/introduction-to-aspnet-core-whats-new-and-why-it-matters">https://www.asptoday.com/p/introduction-to-aspnet-core-whats-new-and-why-it-matters</a></p><h2>When to Use Queues vs Topics</h2><p>Queues are best when:</p><p>A task should be handled by only one consumer</p><p>Topics are best when:</p><p>Multiple services need to react to the same event</p><p>In most modern systems, both are used together.</p><h2>Closing Thoughts</h2><p>Azure Service Bus provides a powerful foundation for building reliable, scalable messaging systems in ASP.NET Core applications.</p><p>By using queues, topics, and subscriptions, you can decouple services, improve resilience, and scale your application more effectively.</p><p>Messaging isn&#8217;t just a technical feature. It&#8217;s an architectural shift that allows your system to grow without becoming fragile.</p><p>As your applications evolve, adopting messaging patterns early can save significant time and complexity later.</p><h2>Join The Community</h2><p>Enjoyed this article? Subscribe to ASP Today for practical insights into <a href="https://www.asptoday.com/">ASP.NET Core</a> and modern cloud architecture. Join our Substack Chat to connect with developers building scalable systems and share your experiences. </p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.asptoday.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.asptoday.com/subscribe?"><span>Subscribe now</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Bulk Operations and Batch Processing in ASP.NET Core Applications]]></title><description><![CDATA[Learn practical patterns for bulk operations and batch processing in ASP.NET Core, including database optimization, background processing, and scalable pipelines. #ASPToday #aspnetcore #dotnet #webdevelopment #backend]]></description><link>https://www.asptoday.com/p/bulk-operations-and-batch-processing</link><guid isPermaLink="false">https://www.asptoday.com/p/bulk-operations-and-batch-processing</guid><dc:creator><![CDATA[Jenny Muralidharan]]></dc:creator><pubDate>Tue, 10 Mar 2026 15:01:49 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-znK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5caec008-b803-49f5-9a4d-0eab713cfead_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Bulk operations and batch processing are essential techniques for handling large volumes of data efficiently in <a href="https://asptoday.substack.com/p/introduction-to-aspnet-core-whats-new-and-why-it-matters">ASP.NET Core</a> applications. Instead of processing records one at a time, these approaches allow developers to group work together, dramatically improving performance, scalability, and resource efficiency.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-znK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5caec008-b803-49f5-9a4d-0eab713cfead_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-znK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5caec008-b803-49f5-9a4d-0eab713cfead_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!-znK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5caec008-b803-49f5-9a4d-0eab713cfead_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!-znK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5caec008-b803-49f5-9a4d-0eab713cfead_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!-znK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5caec008-b803-49f5-9a4d-0eab713cfead_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-znK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5caec008-b803-49f5-9a4d-0eab713cfead_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5caec008-b803-49f5-9a4d-0eab713cfead_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2509830,&quot;alt&quot;:&quot;Automated smart warehouse with robotic arms, conveyor belts, and cloud data visualizations. &quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.asptoday.com/i/190270811?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5caec008-b803-49f5-9a4d-0eab713cfead_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Automated smart warehouse with robotic arms, conveyor belts, and cloud data visualizations. " title="Automated smart warehouse with robotic arms, conveyor belts, and cloud data visualizations. " srcset="https://substackcdn.com/image/fetch/$s_!-znK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5caec008-b803-49f5-9a4d-0eab713cfead_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!-znK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5caec008-b803-49f5-9a4d-0eab713cfead_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!-znK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5caec008-b803-49f5-9a4d-0eab713cfead_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!-znK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5caec008-b803-49f5-9a4d-0eab713cfead_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Modern web applications often deal with large datasets. Whether you&#8217;re importing thousands of customer records, updating product catalogs, processing financial transactions, or synchronizing data between services, handling records individually quickly becomes inefficient.</p><p>Processing data one record at a time might work during early development, but as applications grow, it introduces performance bottlenecks, increased database load, and longer processing times. That&#8217;s where bulk operations and batch processing become critical architectural patterns.</p><p>ASP.NET Core provides the flexibility to implement these techniques using a combination of efficient database access patterns, background services, and scalable cloud infrastructure. When implemented correctly, batch processing can significantly reduce server load while improving throughput and responsiveness.</p><p>In this guide, we&#8217;ll explore practical strategies for implementing bulk operations and batch workflows in ASP.NET Core applications. We&#8217;ll look at how to optimize database operations, design batch pipelines, and ensure that large data workloads remain reliable and scalable. </p><h2>Why Bulk Operations Matter in Modern Applications</h2><p>Bulk operations allow multiple records to be processed in a single operation instead of executing repeated commands. This approach reduces the number of database round trips, lowers network overhead, and improves overall system performance.</p><p>For example, imagine an application that needs to update 10,000 records. If the system updates each record individually, it might generate 10,000 separate database calls. Each call introduces latency, database locking, and transaction overhead.</p><p>A bulk update can perform the same task using a single optimized query.</p><p>This difference becomes particularly important in enterprise systems that deal with high data volumes.</p><p>Microsoft&#8217;s guidance on performance optimization for ASP.NET Core applications emphasizes minimizing unnecessary database calls and batching operations whenever possible:<br>https://learn.microsoft.com/aspnet/core/performance</p><p>Batch processing is commonly used for:</p><ul><li><p>Importing large CSV or Excel datasets</p></li><li><p>Synchronizing records with external APIs</p></li><li><p>Running scheduled data cleanup tasks</p></li><li><p>Processing financial or transactional workloads</p></li><li><p>Sending large volumes of notifications</p></li></ul><p>Instead of running these tasks during user requests, they are usually processed asynchronously in controlled batches. </p><h2>Understanding Batch Processing</h2><p>Batch processing refers to executing groups of tasks together as a single job. These jobs typically run in the background and handle large sets of records in stages.</p><p>A batch workflow often looks like this:</p><p>Data is collected or queued, grouped into manageable chunks, processed sequentially or in parallel, and then stored or transmitted.</p><p>This approach prevents large workloads from overwhelming application servers.</p><p>For example, instead of processing 100,000 records at once, a system might process batches of 1,000 records at a time.</p><p>This provides several advantages.</p><p>It limits memory consumption, reduces database locking conflicts, and allows the system to recover gracefully if a failure occurs. </p><h2>Designing Bulk Operations in ASP.NET Core</h2><p>Implementing bulk operations in ASP.NET Core often begins with optimizing how data is sent to the database.</p><p>Developers frequently rely on Object-Relational Mapping frameworks like Entity Framework Core, but naive implementations can introduce performance problems when handling large datasets.</p><p>Consider a typical example where records are inserted one at a time.</p><pre><code>foreach (var product in products)
{
    _context.Products.Add(product);
    await _context.SaveChangesAsync();
}</code></pre><p>While simple, this approach generates a database transaction for each record.</p><p>A better approach is batching the inserts and committing them together.</p><pre><code>_context.Products.AddRange(products);
await _context.SaveChangesAsync();</code></pre><p>This significantly reduces database overhead.</p><p>The official documentation for Entity Framework Core explains how change tracking and batching improve performance when handling multiple entities:<br><br>https://learn.microsoft.com/ef/core/performance/</p><p>However, when datasets grow very large, even <code>AddRange()</code> can become inefficient.</p><p>That&#8217;s where specialized bulk libraries can help.</p><h2>Using Bulk Libraries for High-Volume Operations</h2><p>Libraries such as <strong>EFCore.BulkExtensions</strong> enable high-performance bulk inserts, updates, and deletes.</p><p>These libraries bypass parts of Entity Framework&#8217;s change tracking system and communicate more directly with the database.</p><p>Example:</p><pre><code>await _context.BulkInsertAsync(products);</code></pre><p>Bulk operations like this can improve performance dramatically when importing or updating large datasets.</p><p>This is particularly useful when importing data from external sources such as CSV files or third-party APIs.</p><p>Bulk processing libraries internally leverage database capabilities like SQL Server&#8217;s <strong>BULK INSERT</strong> command, which is designed for high-throughput data ingestion.</p><p>Microsoft provides additional guidance on bulk importing techniques for SQL Server here:<br><br><a href="https://learn.microsoft.com/en-us/sql/relational-databases/import-export/bulk-import-and-export-of-data-sql-server">https://learn.microsoft.com/sql/relational-databases/import-export/bulk-import-and-export-of-data</a></p><h2>Processing Data in Batches</h2><p>Even when using bulk operations, it&#8217;s important to avoid loading massive datasets entirely into memory.</p><p>A better approach is to process records in smaller batches.</p><p>For example:</p><pre><code>const int batchSize = 1000;

for (int i = 0; i &lt; records.Count; i += batchSize)
{
    var batch = records.Skip(i).Take(batchSize).ToList();

    await _context.BulkInsertAsync(batch);
}</code></pre><p>This strategy ensures that the application uses memory efficiently and avoids large transaction sizes.</p><p>Batch size should be tuned based on:</p><ul><li><p>database capacity</p></li><li><p>network latency</p></li><li><p>memory usage</p></li><li><p>workload complexity</p></li></ul><p>In many systems, batch sizes between 500 and 5,000 records offer a good balance between efficiency and stability.</p><h2>Background Batch Processing in ASP.NET Core</h2><p>Long-running bulk operations should not block HTTP requests.</p><p>Instead, they should run in background services.</p><p>ASP.NET Core provides the <code>BackgroundService</code> class to support this model.</p><pre><code>public class BatchProcessingService : BackgroundService
{
    private readonly IServiceProvider _services;

    public BatchProcessingService(IServiceProvider services)
    {
        _services = services;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            using var scope = _services.CreateScope();

            var processor = scope.ServiceProvider.GetRequiredService&lt;IRecordProcessor&gt;();

            await processor.ProcessBatchAsync();

            await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
        }
    }
}</code></pre><p>This allows applications to process workloads independently of user requests.</p><p>Background services are particularly useful for scheduled jobs such as nightly imports or cleanup operations.</p><p>The official ASP.NET Core documentation explains hosted services and background tasks in detail:<br><br><a href="https://learn.microsoft.com/aspnet/core/fundamentals/host/hosted-services">https://learn.microsoft.com/aspnet/core/fundamentals/host/hosted-services</a></p><h2>Queue-Based Batch Processing</h2><p>In large systems, batch processing is often driven by message queues.</p><p>Instead of running periodic jobs, tasks are added to a queue and processed asynchronously by worker services.</p><p>Popular queue technologies include:</p><ul><li><p>Azure Service Bus</p></li><li><p>RabbitMQ</p></li><li><p>Apache Kafka</p></li></ul><p>A queue-based design provides several benefits.</p><p>It allows workloads to scale horizontally, ensures that tasks are processed reliably, and decouples processing logic from web APIs.</p><p>When a user uploads data or triggers an operation, the application can enqueue a processing task and return a response immediately.</p><p>Workers then process tasks in the background.</p><p>Microsoft&#8217;s Azure Service Bus documentation explains this asynchronous messaging model:<br><br><a href="https://learn.microsoft.com/en-us/azure/service-bus-messaging/">https://learn.microsoft.com/azure/service-bus-messaging</a></p><h2>Handling Large Data Imports</h2><p>A common scenario for batch processing is importing external data.</p><p>For example, a system might allow administrators to upload CSV files containing thousands of records.</p><p>Instead of inserting records directly during the upload request, a better workflow might be:</p><ol><li><p>Upload the file</p></li><li><p>Store the file temporarily</p></li><li><p>Add a processing job to a queue</p></li><li><p>Process records in batches</p></li><li><p>Record import results</p></li></ol><p>This design ensures that large imports do not slow down the main application.</p><p>It also allows administrators to track progress and review errors.</p><h2>Error Handling and Retry Strategies</h2><p>Batch processing systems must handle failures gracefully.</p><p>If a batch fails, the system should log the error, retry the operation when appropriate, and avoid losing data.</p><p>Retry strategies can include exponential backoff or retry queues.</p><p>For example, when processing records from an external API, network failures may occur temporarily.</p><p>Instead of failing permanently, the system should retry the operation after a short delay.</p><p>This pattern improves reliability and resilience.</p><h2>Monitoring Batch Processing Systems</h2><p>Visibility is essential for large-scale data processing systems.</p><p>You should monitor:</p><ul><li><p>batch execution time</p></li><li><p>failure rates</p></li><li><p>queue backlog</p></li><li><p>processing throughput</p></li></ul><p>Azure Application Insights provides telemetry capabilities that allow developers to track background job performance.</p><p>https://learn.microsoft.com/azure/azure-monitor/app/app-insights-overview</p><p>Monitoring helps teams detect issues early and scale processing infrastructure when workloads increase.</p><h2>Performance Considerations</h2><p>Several best practices can help ensure that bulk operations remain efficient.</p><p>Avoid loading massive datasets entirely into memory. Use streaming or paging to process records gradually.</p><p>Use asynchronous database calls to prevent thread blocking.</p><p>Optimize database indexes so batch operations execute quickly.</p><p>Finally, test batch workloads under realistic conditions to ensure that the system behaves predictably under heavy load.</p><h2>Real-World Example: Product Catalog Synchronization</h2><p>Imagine an e-commerce platform that synchronizes product catalogs with an external supplier.</p><p>The supplier provides a dataset containing 50,000 products each night.</p><p>Instead of processing all records during a single request, the application performs the following workflow.</p><p>First, the dataset is downloaded and stored temporarily.</p><p>Next, a background job begins processing products in batches of 1,000 records.</p><p>Bulk operations update or insert records efficiently.</p><p>Errors are logged for review.</p><p>Finally, the system updates inventory and search indexes.</p><p>This workflow allows the platform to synchronize large datasets reliably without affecting customer-facing performance.</p><h2>When to Use Bulk Operations vs Batch Processing</h2><p>Bulk operations focus on optimizing database operations.</p><p>Batch processing focuses on orchestrating large workloads over time.</p><p>In many applications, both techniques are used together.</p><p>Bulk operations improve the efficiency of each database operation, while batch processing ensures that large workloads are distributed safely.</p><p>Together, they form the foundation of scalable data processing systems.</p><h2>Final Thoughts</h2><p>Bulk operations and batch processing are powerful techniques for building scalable ASP.NET Core applications. As systems grow and data volumes increase, these patterns become essential for maintaining performance and reliability.</p><p>By grouping database operations, processing records in controlled batches, and running heavy workloads in background services, developers can ensure that their applications handle large datasets efficiently.</p><p>ASP.NET Core&#8217;s flexibility, combined with modern database capabilities and cloud infrastructure, makes it possible to design processing pipelines that scale with your application&#8217;s growth.</p><p>When implemented thoughtfully, batch processing systems allow teams to process massive workloads while keeping user-facing APIs fast and responsive.</p><h2>Join The Community</h2><p>Enjoyed this article? Subscribe to <strong><a href="https://www.asptoday.com/">ASP Today</a></strong> for practical insights and real-world guidance on ASP.NET Core development. Join our growing community on Substack Chat to connect with fellow developers, share ideas, and stay updated on modern .NET practices. </p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.asptoday.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.asptoday.com/subscribe?"><span>Subscribe now</span></a></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[File Upload and Processing in ASP.NET Core: Streaming, Validation, and Cloud Storage]]></title><description><![CDATA[Learn how to stream large uploads, validate files securely, and store them in the cloud using ASP.NET Core. Practical, production-ready guidance for modern apps. #ASPToday #aspnetcore #webdevelopment #dotnet #cloud]]></description><link>https://www.asptoday.com/p/file-upload-and-processing-in-aspnet</link><guid isPermaLink="false">https://www.asptoday.com/p/file-upload-and-processing-in-aspnet</guid><dc:creator><![CDATA[Jenny Muralidharan]]></dc:creator><pubDate>Tue, 03 Mar 2026 15:02:36 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!hPp3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F313b599a-44c7-456d-9848-76ff9e92992f_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Handling file uploads in <a href="https://www.asptoday.com/p/introduction-to-aspnet-core-whats-new-and-why-it-matters">ASP.NET Core</a> goes far beyond accepting a file and saving it to disk. In this guide, we&#8217;ll walk through streaming large uploads efficiently, validating files securely, and integrating with cloud storage in a way that scales for real-world production applications. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hPp3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F313b599a-44c7-456d-9848-76ff9e92992f_1536x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hPp3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F313b599a-44c7-456d-9848-76ff9e92992f_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!hPp3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F313b599a-44c7-456d-9848-76ff9e92992f_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!hPp3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F313b599a-44c7-456d-9848-76ff9e92992f_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!hPp3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F313b599a-44c7-456d-9848-76ff9e92992f_1536x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!hPp3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F313b599a-44c7-456d-9848-76ff9e92992f_1536x1024.png" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/313b599a-44c7-456d-9848-76ff9e92992f_1536x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2130224,&quot;alt&quot;:&quot;Instructor presenting a secure cloud data transfer diagram to a class of students with laptops.&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.asptoday.com/i/189526387?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F313b599a-44c7-456d-9848-76ff9e92992f_1536x1024.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Instructor presenting a secure cloud data transfer diagram to a class of students with laptops." title="Instructor presenting a secure cloud data transfer diagram to a class of students with laptops." srcset="https://substackcdn.com/image/fetch/$s_!hPp3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F313b599a-44c7-456d-9848-76ff9e92992f_1536x1024.png 424w, https://substackcdn.com/image/fetch/$s_!hPp3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F313b599a-44c7-456d-9848-76ff9e92992f_1536x1024.png 848w, https://substackcdn.com/image/fetch/$s_!hPp3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F313b599a-44c7-456d-9848-76ff9e92992f_1536x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!hPp3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F313b599a-44c7-456d-9848-76ff9e92992f_1536x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>File uploads seem simple at first. A user selects a file, clicks upload, and your application stores it somewhere. But in production systems, file handling quickly becomes one of the most sensitive and performance-heavy parts of your application.</p><p>Large files can consume memory. Malicious files can introduce security risks. Poor validation can open the door to exploitation. And local disk storage often doesn&#8217;t scale in cloud environments.</p><p>ASP.NET Core provides strong primitives for handling file uploads safely and efficiently. Combined with cloud storage providers like Azure Blob Storage or Amazon S3, you can build upload systems that are secure, scalable, and reliable. </p><p><strong>In this guide, we&#8217;ll cover:</strong></p><ul><li><p>Streaming vs buffering</p></li><li><p>Secure file validation</p></li><li><p>Safe file storage patterns</p></li><li><p>Processing uploads</p></li><li><p>Cloud storage integration</p></li><li><p>Performance and scalability considerations</p></li></ul><p>Let&#8217;s start with the fundamentals. </p><h2>Understanding How File Uploads Work in ASP.NET Core</h2><p>When a browser uploads a file, it sends it as part of a <code>multipart/form-data</code> request. ASP.NET Core processes that request and exposes uploaded files through the <code>IFormFile</code> interface.</p><p>According to the official Microsoft documentation on file uploads in ASP.NET Core, there are two primary approaches: buffered uploads and streaming uploads<br><a href="https://learn.microsoft.com/aspnet/core/mvc/models/file-uploads">https://learn.microsoft.com/aspnet/core/mvc/models/file-uploads</a></p><p>Buffered uploads load the entire file into memory or temporary storage before your code handles it. Streaming uploads process the file in chunks as it arrives.</p><p>For small files, buffering is acceptable. For large files or high-traffic systems, streaming is the safer and more scalable option. </p><h2>Basic File Upload Example</h2><p>Let&#8217;s begin with a simple controller-based example.</p><pre><code>[HttpPost]
public async Task&lt;IActionResult&gt; Upload(IFormFile file)
{
    if (file == null || file.Length == 0)
        return BadRequest(&#8221;No file selected.&#8221;);

    var filePath = Path.Combine(&#8221;uploads&#8221;, Path.GetFileName(file.FileName));

    using (var stream = new FileStream(filePath, FileMode.Create))
    {
        await file.CopyToAsync(stream);
    }

    return Ok(&#8221;File uploaded successfully.&#8221;);
}</code></pre><p>This works, but it&#8217;s not production-ready.</p><p>It trusts the file name. It doesn&#8217;t validate file type. It writes directly to disk. And it buffers the file in memory.</p><p>We need to improve this.</p><h2>Streaming File Uploads for Large Files</h2><p>When handling large files, you should avoid buffering them entirely in memory.</p><p>ASP.NET Core allows streaming using <code>Request.Body</code> or the <code>MultipartReader</code> class.</p><p>Here&#8217;s a simplified streaming approach:</p><pre><code>[HttpPost]
[DisableFormValueModelBinding]
public async Task&lt;IActionResult&gt; UploadLargeFile()
{
    var boundary = MultipartRequestHelper.GetBoundary(
        MediaTypeHeaderValue.Parse(Request.ContentType),
        70_000);

    var reader = new MultipartReader(boundary, Request.Body);
    var section = await reader.ReadNextSectionAsync();

    while (section != null)
    {
        if (ContentDispositionHeaderValue
            .TryParse(section.ContentDisposition, out var contentDisposition))
        {
            if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
            {
                var filePath = Path.Combine(&#8221;uploads&#8221;, Guid.NewGuid().ToString());

                using var targetStream = System.IO.File.Create(filePath);
                await section.Body.CopyToAsync(targetStream);
            }
        }

        section = await reader.ReadNextSectionAsync();
    }

    return Ok();
}</code></pre><p>Streaming keeps memory usage low, which is critical for high-load APIs.</p><p>Microsoft&#8217;s Kestrel server, designed for high-performance workloads, handles streaming efficiently<br><br>https://learn.microsoft.com/aspnet/core/fundamentals/servers/kestrel</p><p>If your application allows users to upload large videos or documents, streaming is not optional. It&#8217;s necessary.</p><h2>Validating Uploaded Files Securely</h2><p>Security is the most overlooked part of file uploads.</p><p>Never trust:</p><ul><li><p>File names</p></li><li><p>File extensions</p></li><li><p>Content type headers</p></li></ul><p>Attackers can rename malicious files to bypass simple validation checks.</p><p>A safer approach includes:</p><ol><li><p>Limiting file size</p></li><li><p>Checking allowed extensions</p></li><li><p>Verifying content signatures</p></li><li><p>Generating safe server-side file names</p></li></ol><h2>Limiting File Size</h2><p>You can configure limits globally:</p><pre><code>builder.Services.Configure&lt;FormOptions&gt;(options =&gt;
{
    options.MultipartBodyLengthLimit = 50 * 1024 * 1024; // 50 MB
});</code></pre><p>Or per action:</p><pre><code>[RequestSizeLimit(50 * 1024 * 1024)]</code></pre><p>This prevents memory exhaustion attacks.</p><h2>Validating File Extensions</h2><pre><code>var permittedExtensions = new[] { &#8220;.jpg&#8221;, &#8220;.png&#8221;, &#8220;.pdf&#8221; };
var extension = Path.GetExtension(file.FileName).ToLowerInvariant();

if (!permittedExtensions.Contains(extension))
{
    return BadRequest(&#8221;Invalid file type.&#8221;);
}</code></pre><p>But extension checking alone is insufficient.</p><h2>Validating File Signatures</h2><p>For stronger protection, inspect file signatures (magic numbers).</p><p>For example, PDF files begin with <code>%PDF</code>.</p><pre><code>byte[] header = new byte[4];
await file.OpenReadStream().ReadAsync(header, 0, 4);

if (header[0] != 0x25 || header[1] != 0x50)
{
    return BadRequest(&#8221;Invalid PDF file.&#8221;);
}</code></pre><p>For enterprise systems, consider antivirus scanning before final storage.</p><p>Microsoft recommends scanning uploaded files before making them available<br><br>https://learn.microsoft.com/aspnet/core/security/anti-request-forgery</p><h2>Generating Safe File Names</h2><p>Never store files using user-provided names.</p><p>Instead:</p><pre><code>var safeFileName = $&#8221;{Guid.NewGuid()}{extension}&#8221;;</code></pre><p>This avoids path traversal vulnerabilities and collisions.</p><p>You can still store the original name in a database for display purposes.</p><h2>Processing Files After Upload</h2><p>Often, uploading is just the first step.</p><p>Common processing tasks include:</p><ul><li><p>Image resizing</p></li><li><p>PDF parsing</p></li><li><p>Virus scanning</p></li><li><p>Metadata extraction</p></li><li><p>Video transcoding</p></li></ul><p>These operations should not block the HTTP request.</p><p>Instead, use background processing.</p><p>ASP.NET Core supports hosted background services:</p><pre><code>public class FileProcessingService : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            // Process queued files
            await Task.Delay(1000, stoppingToken);
        }
    }
}</code></pre><p>Register it:</p><pre><code>builder.Services.AddHostedService&lt;FileProcessingService&gt;();</code></pre><p>For larger systems, use message queues like Azure Service Bus or RabbitMQ.</p><h2>Storing Files in Cloud Storage</h2><p>Local disk storage works in development, but cloud-native applications should use object storage.</p><p>Azure Blob Storage is a natural fit for ASP.NET Core applications<br><br>https://learn.microsoft.com/azure/storage/blobs/storage-blobs-introduction </p><p>Install the package:</p><pre><code>dotnet add package Azure.Storage.Blobs</code></pre><p>Example upload:</p><pre><code>var blobClient = new BlobClient(connectionString, containerName, safeFileName);

await blobClient.UploadAsync(file.OpenReadStream());</code></pre><p>This provides:</p><ul><li><p>Automatic scalability</p></li><li><p>Redundancy</p></li><li><p>CDN integration</p></li><li><p>Global availability</p></li></ul><p>Amazon S3 offers similar capabilities<br><br><a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html">https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html</a></p><h2>Direct-to-Cloud Uploads</h2><p>For very large files, you can generate pre-signed URLs and allow clients to upload directly to cloud storage.</p><p>This avoids routing large files through your server entirely.</p><p>Azure supports SAS tokens<br><br>https://learn.microsoft.com/azure/storage/common/storage-sas-overview</p><p>This pattern reduces:</p><ul><li><p>Server load</p></li><li><p>Bandwidth costs</p></li><li><p>Scaling complexity</p></li></ul><h2>Performance Considerations</h2><p>File uploads impact:</p><ul><li><p>Memory usage</p></li><li><p>Thread pool utilization</p></li><li><p>Disk I/O</p></li><li><p>Network throughput</p></li></ul><p>Always:</p><ul><li><p>Use async I/O</p></li><li><p>Avoid loading full files into memory</p></li><li><p>Configure request limits</p></li><li><p>Monitor upload endpoints separately</p></li></ul><p>For high-scale APIs, consider separating upload services into their own microservice.</p><h2>Handling Concurrent Uploads</h2><p>High concurrency requires careful planning.</p><p>Monitor:</p><ul><li><p>CPU</p></li><li><p>Memory</p></li><li><p>Disk</p></li><li><p>Network</p></li></ul><p>Implement rate limiting using ASP.NET Core middleware:</p><pre><code>builder.Services.AddRateLimiter(options =&gt;
{
    options.AddFixedWindowLimiter(&#8221;upload&#8221;, limiterOptions =&gt;
    {
        limiterOptions.PermitLimit = 10;
        limiterOptions.Window = TimeSpan.FromMinutes(1);
    });
});</code></pre><p>This prevents abuse and protects resources.</p><h2>Logging and Monitoring</h2><p>Track:</p><ul><li><p>Upload failures</p></li><li><p>Validation errors</p></li><li><p>File size metrics</p></li><li><p>Processing duration</p></li></ul><p>Use Application Insights for telemetry<br><br>https://learn.microsoft.com/azure/azure-monitor/app/app-insights-overview</p><p>Monitoring upload endpoints separately helps identify bottlenecks early.</p><h2>Common Mistakes to Avoid</h2><p>Saving files with original names<br><br>Storing files inside wwwroot<br><br>Trusting Content-Type headers<br><br>Blocking requests during processing<br><br>Ignoring size limits<br><br>Skipping antivirus scanning</p><p>Small mistakes in upload systems can become serious vulnerabilities.</p><h2>Real-World Example: Profile Image Upload</h2><p>A simple pattern:</p><ol><li><p>Validate extension and size</p></li><li><p>Stream file</p></li><li><p>Store in Blob Storage</p></li><li><p>Save metadata in database</p></li><li><p>Return public URL</p></li></ol><p>This keeps your application clean and scalable.</p><h2>When to Use Streaming vs Buffered Uploads</h2><p>Buffered uploads are acceptable when:</p><ul><li><p>Files are small</p></li><li><p>Traffic is low</p></li><li><p>Simplicity is preferred</p></li></ul><p>Streaming is preferred when:</p><ul><li><p>Files exceed a few MB</p></li><li><p>Concurrent uploads are expected</p></li><li><p>Cloud-native scaling matters</p></li></ul><h2>Final Thoughts</h2><p>File upload handling is one of those features that seems simple until it isn&#8217;t.</p><p>Done correctly, it improves scalability and security. Done carelessly, it becomes a performance bottleneck or security risk.</p><p>ASP.NET Core gives you the tools. Cloud storage platforms give you scalability. Combining both thoughtfully is what makes production-ready systems possible.</p><h2>Join The Community</h2><p>If you found this guide helpful, subscribe to <a href="https://www.asptoday.com/">ASP Today</a> on Substack for practical, real-world ASP.NET Core insights. Join us in Substack Chat to connect with other developers building modern .NET applications. </p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.asptoday.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.asptoday.com/subscribe?"><span>Subscribe now</span></a></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p>]]></content:encoded></item></channel></rss>