<?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>Tue, 01 Sep 2026 10:01:21 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[Configuration at Scale in ASP.NET Core: Dynamic Settings, Validation, and Safe Reloading]]></title><description><![CDATA[Build safer ASP.NET Core configuration with validation, dynamic settings, and controlled reloads. #ASPToday #aspnetcore #dotnet #configuration]]></description><link>https://www.asptoday.com/p/configuration-at-scale-in-aspnet</link><guid isPermaLink="false">https://www.asptoday.com/p/configuration-at-scale-in-aspnet</guid><dc:creator><![CDATA[Jenny Muralidharan]]></dc:creator><pubDate>Tue, 25 Aug 2026 15:03:26 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!oMYG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38323388-6443-4be0-a52b-d6c8705b97b0_1672x941.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Configuration starts simply in <a href="https://asptoday.substack.com/p/introduction-to-aspnet-core-whats-new-and-why-it-matters">ASP.NET Core</a>. We add a few values to <code>appsettings.json</code>, inject <code>IConfiguration</code>, and move on. But as applications grow across services, environments, regions, and deployment platforms, configuration becomes part of the architecture itself. A single incorrect setting can disable a feature, overwhelm a dependency, or prevent an application from starting. Modern ASP.NET Core gives us powerful tools for strongly typed settings, validation, environment-specific configuration, and runtime updates. The challenge is using those tools safely. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!oMYG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38323388-6443-4be0-a52b-d6c8705b97b0_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!oMYG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38323388-6443-4be0-a52b-d6c8705b97b0_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!oMYG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38323388-6443-4be0-a52b-d6c8705b97b0_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!oMYG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38323388-6443-4be0-a52b-d6c8705b97b0_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!oMYG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38323388-6443-4be0-a52b-d6c8705b97b0_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!oMYG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38323388-6443-4be0-a52b-d6c8705b97b0_1672x941.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/38323388-6443-4be0-a52b-d6c8705b97b0_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;:2671932,&quot;alt&quot;:&quot;Miniature engineers adjust a giant watch mechanism, symbolizing safe configuration changes. &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/212511659?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38323388-6443-4be0-a52b-d6c8705b97b0_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Miniature engineers adjust a giant watch mechanism, symbolizing safe configuration changes. " title="Miniature engineers adjust a giant watch mechanism, symbolizing safe configuration changes. " srcset="https://substackcdn.com/image/fetch/$s_!oMYG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38323388-6443-4be0-a52b-d6c8705b97b0_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!oMYG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38323388-6443-4be0-a52b-d6c8705b97b0_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!oMYG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38323388-6443-4be0-a52b-d6c8705b97b0_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!oMYG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F38323388-6443-4be0-a52b-d6c8705b97b0_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 aria-hidden="true" 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><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 build a <a href="https://asptoday.substack.com/p/building-scalable-applications-with">scalable</a> configuration approach that validates settings early, supports controlled changes, and allows applications to react to updated configuration without introducing unpredictable production behavior.</p><h3>Configuration Looks Easy Until the Application Grows</h3><p>Consider a small ASP.NET Core application.</p><p>Its configuration might contain:</p><pre><code><code>{
  "Catalog": {
    "PageSize": 25
  }
}</code></code></pre><p>Reading the value is straightforward.</p><pre><code><code>var pageSize =
    builder.Configuration.GetValue&lt;int&gt;("Catalog:PageSize");</code></code></pre><p>For a small application, this works perfectly well.</p><p>Now imagine the same system two years later.</p><p>It contains:</p><ul><li><p>Multiple ASP.NET Core services</p></li><li><p>Development, staging, and production environments</p></li><li><p>Several geographic regions</p></li><li><p>Feature settings</p></li><li><p>Retry policies</p></li><li><p>API endpoints</p></li><li><p>Cache expiration rules</p></li><li><p>Queue settings</p></li><li><p>Connection information</p></li><li><p>Operational limits</p></li></ul><p>Suddenly, configuration is no longer just a file.</p><p>It is a distributed operational dependency.</p><h3>Why Configuration Deserves Architectural Attention</h3><p>A configuration change can alter application behavior without changing application code.</p><p>That is powerful.</p><p>It is also dangerous.</p><p>Imagine someone changes:</p><pre><code><code>{
  "Payments": {
    "TimeoutSeconds": 30
  }
}</code></code></pre><p>to:</p><pre><code><code>{
  "Payments": {
    "TimeoutSeconds": 3000
  }
}</code></code></pre><p>The application still compiles.</p><p>The deployment may succeed.</p><p>But requests could now remain active far longer than intended.</p><p>Configuration errors are particularly difficult because they often look like application failures.</p><p>The code has not changed, yet production behavior has.</p><p>That is why mature systems treat configuration with many of the same disciplines applied to code.</p><h3>Understanding ASP.NET Core Configuration Providers</h3><p>ASP.NET Core builds configuration from one or more configuration providers.</p><p>Common sources include:</p><ul><li><p>JSON files</p></li><li><p>Environment variables</p></li><li><p>Command-line arguments</p></li><li><p>User secrets during development</p></li><li><p>In-memory configuration</p></li><li><p>External configuration systems</p></li></ul><p>These sources are combined into a single configuration model.</p><p>A simplified setup might look like:</p><pre><code><code>var builder = WebApplication.CreateBuilder(args);

builder.Configuration
    .AddJsonFile(
        "appsettings.json",
        optional: false,
        reloadOnChange: true)
    .AddJsonFile(
        $"appsettings.{builder.Environment.EnvironmentName}.json",
        optional: true,
        reloadOnChange: true)
    .AddEnvironmentVariables();</code></code></pre><p>The order matters because later providers can override values supplied by earlier providers.</p><p>That gives us a useful layering model.</p><pre><code><code>Base Configuration
        &#8595;
Environment Configuration
        &#8595;
Environment Variables
        &#8595;
Final Effective Configuration</code></code></pre><p>The application consumes the final result.</p><h3>Configuration Precedence Matters</h3><p>Suppose <code>appsettings.json</code> contains:</p><pre><code><code>{
  "Catalog": {
    "PageSize": 20
  }
}</code></code></pre><p>Production configuration contains:</p><pre><code><code>{
  "Catalog": {
    "PageSize": 50
  }
}</code></code></pre><p>And an environment variable supplies:</p><pre><code><code>Catalog__PageSize=100</code></code></pre><p>The effective value may become:</p><pre><code><code>100</code></code></pre><p>That flexibility is useful for deployments, but it can also create confusion.</p><p>When debugging configuration problems, the important question is often not:</p><p><strong>What does </strong><code>appsettings.json</code><strong> say?</strong></p><p>It is:</p><p><strong>Which provider supplied the value the application is actually using?</strong></p><h3>Stop Passing IConfiguration Everywhere</h3><p>A common early-stage pattern looks like this:</p><pre><code><code>public class CatalogService
{
    private readonly IConfiguration _configuration;

    public CatalogService(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public int GetPageSize()
    {
        return _configuration
            .GetValue&lt;int&gt;("Catalog:PageSize");
    }
}</code></code></pre><p>This works.</p><p>But it has several weaknesses.</p><p>The service depends on string-based configuration paths.</p><p>Typos become runtime problems.</p><p>The expected configuration structure is hidden inside application logic.</p><p>Validation is difficult.</p><p>Testing requires constructing configuration.</p><p>For larger applications, the Options pattern is usually a better approach.</p><h3>Strongly Typed Configuration</h3><p>Let&#8217;s define configuration as a class.</p><pre><code><code>public sealed class CatalogOptions
{
    public const string SectionName = "Catalog";

    public int PageSize { get; init; }

    public int CacheMinutes { get; init; }
}</code></code></pre><p>Our configuration becomes:</p><pre><code><code>{
  "Catalog": {
    "PageSize": 50,
    "CacheMinutes": 10
  }
}</code></code></pre><p>Then register it:</p><pre><code><code>builder.Services.Configure&lt;CatalogOptions&gt;(
    builder.Configuration.GetSection(
        CatalogOptions.SectionName));</code></code></pre><p>Now application code works with normal C# properties rather than configuration strings.</p><pre><code><code>public class CatalogService
{
    private readonly CatalogOptions _options;

    public CatalogService(
        IOptions&lt;CatalogOptions&gt; options)
    {
        _options = options.Value;
    }
}</code></code></pre><p>This gives configuration a clear contract.</p><h3>Why Strong Typing Matters at Scale</h3><p>Compare these:</p><pre><code><code>configuration["Payments:Retry:MaximumAttempts"]</code></code></pre><p>and:</p><pre><code><code>options.MaximumAttempts</code></code></pre><p>The second approach is easier to understand, test, refactor, and validate.</p><p>More importantly, it tells developers exactly which settings a component expects.</p><p>Configuration stops being an invisible collection of strings and becomes part of the application&#8217;s type system.</p><h3>Configuration Must Be Validated</h3><p>Strong typing does not automatically make configuration correct.</p><p>This is perfectly valid C#:</p><pre><code><code>{
  "Catalog": {
    "PageSize": -500,
    "CacheMinutes": 0
  }
}</code></code></pre><p>The values bind successfully.</p><p>But they make no business sense.</p><p>ASP.NET Core&#8217;s options infrastructure allows us to validate configuration.</p><p>For example:</p><pre><code><code>builder.Services
    .AddOptions&lt;CatalogOptions&gt;()
    .Bind(
        builder.Configuration.GetSection(
            CatalogOptions.SectionName))
    .Validate(
        options =&gt; options.PageSize &gt; 0,
        "PageSize must be greater than zero.")
    .Validate(
        options =&gt; options.CacheMinutes &gt; 0,
        "CacheMinutes must be greater than zero.");</code></code></pre><p>Now invalid values can be detected deliberately rather than producing strange behavior later.</p><h3>Fail Fast with ValidateOnStart</h3><p>There is another important question.</p><p><strong>When should invalid configuration be discovered?</strong></p><p>Suppose a payment configuration is invalid.</p><p>One option is to discover the problem when the first customer attempts a payment.</p><p>That is far too late.</p><p>For critical configuration, we usually want the application to fail during startup.</p><pre><code><code>builder.Services
    .AddOptions&lt;PaymentOptions&gt;()
    .Bind(
        builder.Configuration.GetSection("Payments"))
    .Validate(
        options =&gt; options.TimeoutSeconds &gt; 0,
        "Payment timeout must be positive.")
    .ValidateOnStart();</code></code></pre><p>Now configuration is checked when the application starts.</p><p>This is an important production principle:</p><p><strong>A service that cannot operate correctly should fail before it starts accepting traffic.</strong></p><h3>Data Annotation Validation</h3><p>For straightforward rules, data annotations can make option classes easy to understand.</p><pre><code><code>public sealed class PaymentOptions
{
    [Range(1, 120)]
    public int TimeoutSeconds { get; init; }

    [Range(0, 10)]
    public int MaximumRetries { get; init; }
}</code></code></pre><p>Then:</p><pre><code><code>builder.Services
    .AddOptions&lt;PaymentOptions&gt;()
    .BindConfiguration("Payments")
    .ValidateDataAnnotations()
    .ValidateOnStart();</code></code></pre><p>The rules now live directly beside the properties they protect.</p><h3>Cross-Property Validation</h3><p>Real configuration often has relationships between values.</p><p>Imagine:</p><pre><code><code>public sealed class UploadOptions
{
    public long WarningSizeBytes { get; init; }

    public long MaximumSizeBytes { get; init; }
}</code></code></pre><p>Both numbers could individually be positive while still being logically incorrect.</p><p>For example:</p><pre><code><code>WarningSize = 100 MB
MaximumSize = 50 MB</code></code></pre><p>We need a rule such as:</p><pre><code><code>.Validate(
    options =&gt;
        options.WarningSizeBytes &lt;
        options.MaximumSizeBytes,
    "WarningSizeBytes must be lower than MaximumSizeBytes.")</code></code></pre><p>This is why configuration validation should express <strong>operational rules</strong>, not merely type correctness.</p><h3>IOptions, IOptionsSnapshot, and IOptionsMonitor</h3><p>ASP.NET Core provides several ways to consume options, and understanding the differences becomes important when configuration can change.</p><p>The three common interfaces are:</p><pre><code><code>IOptions&lt;T&gt;
IOptionsSnapshot&lt;T&gt;
IOptionsMonitor&lt;T&gt;</code></code></pre><p>They sound similar, but they serve different purposes.</p><h3>IOptions&lt;T&gt;</h3><p><code>IOptions&lt;T&gt;</code> provides an options value that is effectively consumed as a stable value by the application.</p><p>It works well when configuration is not expected to change during the process lifetime.</p><pre><code><code>public CatalogService(
    IOptions&lt;CatalogOptions&gt; options)
{
    _options = options.Value;
}</code></code></pre><p>For many settings, this is exactly what we want.</p><p><a href="https://asptoday.substack.com/p/microservices-architecture-with-aspnet">Database architecture</a>, protocol choices, and other structural settings should not necessarily change while the application is running.</p><h3>IOptionsSnapshot&lt;T&gt;</h3><p><code>IOptionsSnapshot&lt;T&gt;</code> is scoped and recalculates options once per request scope.</p><p>This can be useful in request-based applications where updated configuration should become visible to subsequent requests.</p><pre><code><code>public CatalogController(
    IOptionsSnapshot&lt;CatalogOptions&gt; options)
{
    _options = options.Value;
}</code></code></pre><p>A request sees a consistent snapshot.</p><p>A later request can receive updated settings.</p><h3>IOptionsMonitor&lt;T&gt;</h3><p><code>IOptionsMonitor&lt;T&gt;</code> is designed for scenarios where an application needs access to the current option value and may need to react when that value changes.</p><pre><code><code>public class PricingService
{
    private readonly IOptionsMonitor&lt;PricingOptions&gt; _options;

    public PricingService(
        IOptionsMonitor&lt;PricingOptions&gt; options)
    {
        _options = options;
    }

    public decimal Calculate(decimal price)
    {
        var settings = _options.CurrentValue;

        return price * settings.Multiplier;
    }
}</code></code></pre><p>If the underlying configuration provider supports reloads, <code>CurrentValue</code> can reflect updated configuration.</p><h3>Reacting to Configuration Changes</h3><p><code>IOptionsMonitor&lt;T&gt;</code> also exposes change notifications.</p><pre><code><code>public class PricingConfigurationObserver
{
    private readonly IDisposable? _subscription;

    public PricingConfigurationObserver(
        IOptionsMonitor&lt;PricingOptions&gt; options,
        ILogger&lt;PricingConfigurationObserver&gt; logger)
    {
        _subscription = options.OnChange(updated =&gt;
        {
            logger.LogInformation(
                "Pricing configuration changed. Multiplier: {Multiplier}",
                updated.Multiplier);
        });
    }
}</code></code></pre><p>This looks extremely powerful.</p><p>Change a configuration value and the application reacts immediately.</p><p>But this is also where configuration becomes dangerous.</p><h3>Dynamic Configuration Is Not Automatically Better</h3><p>Imagine changing:</p><pre><code><code>MaximumConcurrentJobs = 100</code></code></pre><p>to:</p><pre><code><code>MaximumConcurrentJobs = 10</code></code></pre><p>while 75 jobs are already running.</p><p>What should happen?</p><p>Should 65 jobs be cancelled?</p><p>Should the new limit apply only to future work?</p><p>Should the application wait until concurrency naturally falls below 10?</p><p>The configuration system cannot answer those questions.</p><p>They are application semantics.</p><p>This leads to one of the most important rules in dynamic configuration:</p><p><strong>Being able to reload a setting does not mean the setting is safe to reload.</strong></p><h3>Classify Configuration by Behavior</h3><p>A useful production approach is to classify settings into three groups.</p><h4>Static Settings</h4><p>These require application restart.</p><p>Examples:</p><ul><li><p>Fundamental infrastructure choices</p></li><li><p>Certain hosting settings</p></li><li><p>Major dependency topology</p></li><li><p>Settings used only during service registration</p></li></ul><h4>Reloadable Settings</h4><p>These can change safely while the application is running.</p><p>Examples might include:</p><ul><li><p>Some timeout values</p></li><li><p>UI behavior</p></li><li><p>Operational thresholds</p></li><li><p>Non-critical feature behavior</p></li></ul><h4>Controlled Dynamic Settings</h4><p>These can change at runtime, but only through explicit application logic.</p><p>Examples include:</p><ul><li><p>Concurrency limits</p></li><li><p>Pricing rules</p></li><li><p>Traffic percentages</p></li><li><p>Resource thresholds</p></li></ul><p>This classification prevents teams from treating every configuration value as equally dynamic.</p><h3>Safe Reloading Requires Boundaries</h3><p>Suppose we have:</p><pre><code><code>public sealed class WorkerOptions
{
    public int BatchSize { get; init; }
}</code></code></pre><p>A background worker could read:</p><pre><code><code>var batchSize =
    _optionsMonitor.CurrentValue.BatchSize;</code></code></pre><p>before beginning each batch.</p><p>That gives us a natural boundary.</p><pre><code><code>Batch 1 uses 100
        &#8595;
Configuration changes to 50
        &#8595;
Batch 2 uses 50</code></code></pre><p>The current batch remains consistent.</p><p>The new value applies to future work.</p><p>That is far safer than changing behavior halfway through an operation.</p><h3>Configuration Should Be Immutable During an Operation</h3><p>Consider a request that performs several calculations.</p><p>If configuration can change halfway through the request, we could theoretically get:</p><pre><code><code>Step 1 &#8594; Tax rate 10%
Step 2 &#8594; configuration reload
Step 3 &#8594; Tax rate 12%</code></code></pre><p>One operation has now used two different business rules.</p><p>For important calculations, capture the required configuration once at the start of the operation.</p><p>Then use that snapshot consistently.</p><p>Dynamic configuration should change <strong>between operations</strong>, not unpredictably inside them.</p><h3>What Happens When Reloaded Configuration Is Invalid?</h3><p>This is where production systems need careful design.</p><p>Suppose:</p><pre><code><code>{
  "Worker": {
    "BatchSize": 100
  }
}</code></code></pre><p>changes to:</p><pre><code><code>{
  "Worker": {
    "BatchSize": -10
  }
}</code></code></pre><p>The new value is invalid.</p><p>A mature system should not blindly adopt it.</p><p>Validation needs to remain part of the options pipeline, and operationally you should also make configuration failures visible through logging and monitoring.</p><p>The goal is simple:</p><pre><code><code>New Configuration
       &#8595;
Validation
   /       \
Valid     Invalid
 &#8595;           &#8595;
Use it    Surface failure</code></code></pre><p>Never make &#8220;the file changed&#8221; equivalent to &#8220;the change is safe.&#8221;</p><h3>External Configuration Systems</h3><p>Once an organization runs many application instances, local JSON files become difficult to manage dynamically.</p><p>Imagine 50 instances of the same service.</p><p>Changing a JSON file manually on each server is obviously not a scalable strategy.</p><p>This is where centralized configuration providers can help.</p><p>Depending on the environment, organizations may use systems such as:</p><ul><li><p>Azure App Configuration</p></li><li><p>Cloud configuration services</p></li><li><p>Kubernetes configuration mechanisms</p></li><li><p>Dedicated internal configuration platforms</p></li></ul><p>The architectural pattern becomes:</p><pre><code><code>Central Configuration
        &#8595;
    Application A
    Application B
    Application C
    Application D</code></code></pre><p>Teams can manage settings centrally rather than maintaining independent copies everywhere.</p><h3>Configuration Is Not the Same as Secrets</h3><p>This distinction matters.</p><p>Configuration might contain:</p><pre><code><code>PageSize = 50
RetryCount = 3
FeatureMode = Standard</code></code></pre><p>Secrets include things such as:</p><pre><code><code>Database password
API credential
Private key
Signing secret</code></code></pre><p>Both may enter an application through configuration providers, but they should not necessarily be managed in the same place or with the same access policies.</p><p>Secrets deserve stronger controls, auditing, rotation, and restricted visibility.</p><p>Do not turn <code>appsettings.json</code> into a password vault.</p><h3>Environment Variables</h3><p>Environment variables remain extremely useful for containerized and cloud deployments.</p><p>Nested configuration keys use double underscores.</p><p>For example:</p><pre><code><code>Payments__TimeoutSeconds=20</code></code></pre><p>maps to:</p><pre><code><code>Payments:TimeoutSeconds</code></code></pre><p>This allows deployment platforms to override application defaults without modifying files inside the application package.</p><h3>Environment-Specific Files</h3><p>ASP.NET Core commonly uses files such as:</p><pre><code><code>appsettings.json
appsettings.Development.json
appsettings.Production.json</code></code></pre><p>This is useful for environment-specific defaults.</p><p>But avoid allowing these files to drift into completely different application configurations.</p><p>If production contains 40 settings that development does not even know exist, configuration becomes difficult to test.</p><p>The environments should differ in <strong>values</strong>, not unexpectedly in the fundamental shape of configuration.</p><h3>Validate Production-Like Configuration Before Production</h3><p>Configuration validation during application startup is valuable.</p><p>But discovering an invalid setting during the production deployment is still later than ideal.</p><p>A stronger delivery pipeline validates production configuration earlier.</p><p>Conceptually:</p><pre><code><code>Configuration Change
       &#8595;
Schema / Options Validation
       &#8595;
Automated Tests
       &#8595;
Staging
       &#8595;
Production</code></code></pre><p>Treat configuration changes as deployable changes.</p><p>They deserve review and testing.</p><h3>Configuration Versioning</h3><p>Imagine an incident begins at 14:32.</p><p>The code has been running successfully for three days.</p><p>What changed?</p><p>A mature configuration system should make it possible to answer:</p><pre><code><code>What changed?
Who changed it?
When?
From what value?
To what value?
Which services received it?</code></code></pre><p>This is why version history becomes important at scale.</p><p>Configuration without history makes incident investigation much harder.</p><h3>Rollback Matters</h3><p>If configuration can be changed quickly, it should also be possible to reverse the change quickly.</p><p>Suppose a new timeout causes failures.</p><p>A rollback path should look like:</p><pre><code><code>Version 41
   &#8595;
Version 42
   &#8595;
Problems detected
   &#8595;
Rollback
   &#8595;
Version 41</code></code></pre><p>The ability to restore a known-good configuration is often more valuable during an incident than the ability to edit individual values manually.</p><h3>Avoid Giant Global Configuration Objects</h3><p>Another common mistake is creating:</p><pre><code><code>public class ApplicationSettings
{
    // 150 unrelated properties
}</code></code></pre><p>Then every service depends on it.</p><p>This creates unnecessary coupling.</p><p>Instead, define configuration around capabilities.</p><pre><code><code>PaymentOptions
CatalogOptions
CacheOptions
ShippingOptions
NotificationOptions</code></code></pre><p>Each component receives only the configuration it needs.</p><p>This mirrors the same modularity principles we apply to application architecture.</p><h3>Names Matter</h3><p>Avoid settings such as:</p><pre><code><code>Timeout = 30
Limit = 100
Enabled = true</code></code></pre><p>Thirty what?</p><p>Seconds?</p><p>Milliseconds?</p><p>What limit?</p><p>What exactly is enabled?</p><p>Prefer explicit names:</p><pre><code><code>RequestTimeoutSeconds
MaximumConcurrentRequests
EnableAutomaticRetry</code></code></pre><p>Configuration is an interface between developers and operators.</p><p>Clarity matters.</p><h3>Do Not Use Configuration as a Database</h3><p>Once dynamic configuration becomes convenient, teams sometimes begin putting everything into it.</p><p>That is a mistake.</p><p>Configuration is excellent for controlling application behavior.</p><p>It is not a replacement for transactional business data.</p><p>A customer&#8217;s current account balance does not belong in configuration.</p><p>Neither does an order&#8217;s status.</p><p>Ask:</p><p><strong>Is this describing how the application should behave, or is this information the application owns?</strong></p><p>If it is business data, store it as business data.</p><h3>Feature Flags Are a Separate Concern</h3><p>Feature flags and configuration overlap, but they solve different problems.</p><p>Configuration usually describes operational behavior.</p><p>Feature flags control whether particular functionality is available, often for specific users, tenants, or rollout groups.</p><p>For example:</p><pre><code><code>RequestTimeoutSeconds = 20</code></code></pre><p>is configuration.</p><pre><code><code>EnableNewCheckoutForBetaCustomers = true</code></code></pre><p>is closer to feature management.</p><p>Keeping that distinction clear prevents configuration systems from becoming overloaded with rollout logic.</p><h3>Observing Configuration Changes</h3><p>Configuration changes should produce operational signals.</p><p>At minimum, important changes should be logged.</p><p>For example:</p><pre><code><code>logger.LogInformation(
    "Catalog configuration updated. PageSize={PageSize}",
    options.PageSize);</code></code></pre><p>Be careful not to log secrets.</p><p>For important systems, useful telemetry can include:</p><ul><li><p>Configuration reload count</p></li><li><p>Validation failures</p></li><li><p>Last successful refresh</p></li><li><p>Configuration version</p></li><li><p>Refresh latency</p></li></ul><p>Configuration is an operational dependency, so it deserves observability.</p><h3>Health Checks and Configuration</h3><p>Some configuration failures can also affect service health.</p><p>Suppose an application depends on centrally refreshed configuration and has not successfully refreshed for several hours.</p><p>Whether that should make the service unhealthy depends on the system.</p><p>Do not automatically mark a service unhealthy simply because a configuration provider is temporarily unavailable if the application can safely continue using its last known configuration.</p><p>This is an important resilience principle:</p><p><strong>A temporary inability to refresh configuration should not necessarily destroy an otherwise healthy application.</strong></p><h3>Last Known Good Configuration</h3><p>That leads to another useful pattern.</p><p>Imagine the configuration service becomes unavailable.</p><p>The application already has a valid configuration.</p><p>Should it stop working?</p><p>Often, no.</p><p>A resilient architecture can continue using the last known good configuration while reporting that refresh attempts are failing.</p><pre><code><code>Configuration Provider
        X
        &#9474;
Application
        &#9474;
        &#9492;&#9472;&#9472; Continue with last known good settings</code></code></pre><p>This prevents a configuration outage from automatically becoming an application outage.</p><p>Of course, some security-sensitive settings may require stricter behavior.</p><p>Again, the correct policy depends on what the setting controls.</p><h3>Configuration Across Multiple Instances</h3><p>Suppose we run ten instances of an Orders API.</p><p>A configuration value changes.</p><p>The instances may not all observe the update at exactly the same instant.</p><p>For a brief period:</p><pre><code><code>Instance 1 &#8594; Version 18
Instance 2 &#8594; Version 18
Instance 3 &#8594; Version 17
Instance 4 &#8594; Version 18</code></code></pre><p>Can the system tolerate that?</p><p>For many operational settings, yes.</p><p>For some business rules, absolutely not.</p><p>If every instance must change atomically, ordinary dynamic configuration may be the wrong mechanism.</p><p>This is another reason to distinguish operational settings from transactional business state.</p><h3>Configuration and .NET Aspire</h3><p>In our previous article, we explored how .NET Aspire helps describe and connect distributed applications.</p><p>Configuration fits naturally into that story.</p><p>Aspire can help applications receive connection information and resource references without requiring developers to manually coordinate endpoints across multiple projects.</p><p>The larger architectural principle remains the same:</p><p><strong>Applications should depend on named capabilities and structured configuration rather than scattered hardcoded infrastructure details.</strong></p><p>As distributed systems grow, this becomes increasingly important.</p><h3>A Practical Production Pattern</h3><p>Imagine an ASP.NET Core order-processing service.</p><p>It needs:</p><pre><code><code>OrderProcessingOptions
PaymentOptions
ShippingOptions
QueueOptions</code></code></pre><p>Each option type is:</p><ol><li><p>Strongly typed.</p></li><li><p>Bound to its own configuration section.</p></li><li><p>Validated.</p></li><li><p>Validated during startup when appropriate.</p></li><li><p>Classified as static or reloadable.</p></li><li><p>Monitored when dynamic changes are required.</p></li></ol><p>Critical settings fail startup if invalid.</p><p>Reloadable settings change only at safe operational boundaries.</p><p>Configuration changes are logged.</p><p>The configuration platform maintains version history.</p><p>Rollbacks are possible.</p><p>Secrets are managed separately.</p><p>This is no longer &#8220;reading settings.&#8221;</p><p>It is configuration engineering.</p><h3>A Real-World Scenario</h3><p>Imagine a large ticket-booking platform.</p><p>During normal traffic:</p><pre><code><code>MaximumSearchResults = 100
SearchTimeoutSeconds = 5
MaximumConcurrentSearches = 500
CacheMinutes = 10</code></code></pre><p>A major concert goes on sale.</p><p>Traffic suddenly increases dramatically.</p><p>Operations decides to reduce:</p><pre><code><code>MaximumSearchResults</code></code></pre><p>from:</p><pre><code><code>100</code></code></pre><p>to:</p><pre><code><code>50</code></code></pre><p>The goal is to reduce backend work per search.</p><p>A properly designed configuration system allows the change to be introduced without rebuilding the application.</p><p>New requests use the updated limit.</p><p>Existing requests continue using the configuration they started with.</p><p><a href="https://asptoday.substack.com/p/deep-dive-into-distributed-tracing">Telemetry</a> confirms the new configuration version has reached application instances.</p><p>Latency begins falling.</p><p>If the change causes unexpected behavior, operations rolls back to the previous version.</p><p>That is the value of dynamic configuration when it is designed correctly.</p><h3>The Configuration Maturity Ladder</h3><p>We can think of configuration maturity in stages.</p><pre><code><code>Level 1
Hardcoded values

        &#8595;

Level 2
appsettings.json

        &#8595;

Level 3
Environment-specific configuration

        &#8595;

Level 4
Strongly typed options

        &#8595;

Level 5
Validation

        &#8595;

Level 6
Centralized configuration

        &#8595;

Level 7
Safe dynamic reload

        &#8595;

Level 8
Versioning, auditing, rollback, and observability</code></code></pre><p>Not every application needs Level 8.</p><p>But as systems become more distributed and operationally important, configuration deserves increasingly deliberate engineering.</p><h3>Common Mistakes to Avoid</h3><p>The most common problems usually come from treating configuration as an afterthought.</p><p>Avoid:</p><ul><li><p>Hardcoded infrastructure addresses</p></li><li><p>Configuration strings scattered throughout application code</p></li><li><p>Missing validation</p></li><li><p>Secrets committed to configuration files</p></li><li><p>Reloading every setting dynamically</p></li><li><p>Changing values halfway through operations</p></li><li><p>Giant global settings classes</p></li><li><p>Unclear units and names</p></li><li><p>No version history</p></li><li><p>No rollback mechanism</p></li><li><p>Logging secret values</p></li><li><p>Assuming every instance reloads simultaneously</p></li></ul><p>Most of these mistakes are easy to avoid when configuration is treated as an explicit architectural concern.</p><h2>How This Fits Our ASP.NET Core Journey</h2><p>The previous article introduced .NET Aspire and showed how distributed applications can be modeled as connected resources.</p><p>That naturally leads to configuration.</p><p>Once applications span multiple services, environments, and infrastructure dependencies, every component needs reliable information about how it should behave.</p><p>The challenge is no longer simply loading that information.</p><p>The challenge becomes controlling its lifecycle.</p><p>We need to know whether configuration is valid, when it can change, how changes reach running services, what happens when the provider fails, and how we recover when a bad value reaches production.</p><p>Those questions become increasingly important as ASP.NET Core systems grow.</p><p>And they lead directly into our next architectural problem.</p><p>What happens when a perfectly configured application receives more work than it can possibly process?</p><p>That is where <strong>backpressure and overload protection</strong> enter the picture.</p><h2>Coming Next</h2><p>In the next article, we&#8217;ll explore <strong>Designing Backpressure in ASP.NET Core: Handling Overload Without Crashing Your System</strong>.</p><p>We&#8217;ll look at what happens when producers generate work faster than consumers can process it, why unlimited queues can quietly destroy application stability, and how bounded channels, concurrency limits, load shedding, cancellation, and graceful degradation help ASP.NET Core applications remain responsive under extreme load.</p><h2>Closing Thoughts</h2><p>Configuration begins as a convenience and eventually becomes part of the operational architecture of an application.</p><p>ASP.NET Core gives us a strong foundation through configuration providers, the Options pattern, validation, startup validation, snapshots, and monitoring. But the framework cannot decide which settings are safe to change while an application is running.</p><p>That responsibility belongs to us.</p><p>The most reliable systems distinguish static configuration from genuinely dynamic settings. They validate important values before accepting traffic, apply runtime changes at safe boundaries, preserve known-good settings when appropriate, record configuration history, and provide a fast rollback path when something goes wrong.</p><p>At scale, configuration should not be a collection of mysterious values scattered across files and environment variables.</p><p>It should be a controlled, validated, observable input into application behavior.</p><p>When we treat it that way, configuration stops being one of the easiest ways to accidentally break production and becomes one of the safest ways to operate a system without constantly redeploying it.</p><h2>Subscribe Now</h2><p>Enjoying the series? <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 tutorials, advanced architecture deep dives, and production-focused .NET engineering guides. Join our Substack Chat to discuss configuration strategies, distributed systems, and the challenges of running modern ASP.NET Core applications at scale. </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 Distributed Applications with .NET Aspire: Orchestration, Service Discovery, and Observability]]></title><description><![CDATA[Discover how .NET Aspire simplifies orchestration, service discovery, and observability. #ASPToday #aspnetcore #dotnetaspire #dotnet #cloudnative]]></description><link>https://www.asptoday.com/p/building-distributed-applications</link><guid isPermaLink="false">https://www.asptoday.com/p/building-distributed-applications</guid><dc:creator><![CDATA[Jenny Muralidharan]]></dc:creator><pubDate>Tue, 18 Aug 2026 15:01:05 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!9xvC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07025538-2ced-4246-ae8e-8137a6643117_1672x941.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Distributed applications give us flexibility and scalability, but they also introduce a frustrating amount of operational complexity. An <a href="https://www.asptoday.com/p/introduction-to-aspnet-core-whats-new-and-why-it-matters">ASP.NET Core</a> application may depend on several APIs, databases, caches, message brokers, and background workers, each with its own endpoint, configuration, startup requirements, and telemetry. <a href="https://aspire.dev/">.NET Aspire</a> helps bring these moving pieces together. It provides a code-first way to describe a distributed application, connect its resources, discover services, coordinate startup, and observe the entire system from one place. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9xvC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07025538-2ced-4246-ae8e-8137a6643117_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9xvC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07025538-2ced-4246-ae8e-8137a6643117_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!9xvC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07025538-2ced-4246-ae8e-8137a6643117_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!9xvC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07025538-2ced-4246-ae8e-8137a6643117_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!9xvC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07025538-2ced-4246-ae8e-8137a6643117_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9xvC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07025538-2ced-4246-ae8e-8137a6643117_1672x941.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/07025538-2ced-4246-ae8e-8137a6643117_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;:2907351,&quot;alt&quot;:&quot;Spacecraft modules connect inside a vast hangar, representing orchestration and service discovery. &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/211543549?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07025538-2ced-4246-ae8e-8137a6643117_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Spacecraft modules connect inside a vast hangar, representing orchestration and service discovery. " title="Spacecraft modules connect inside a vast hangar, representing orchestration and service discovery. " srcset="https://substackcdn.com/image/fetch/$s_!9xvC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07025538-2ced-4246-ae8e-8137a6643117_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!9xvC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07025538-2ced-4246-ae8e-8137a6643117_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!9xvC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07025538-2ced-4246-ae8e-8137a6643117_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!9xvC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07025538-2ced-4246-ae8e-8137a6643117_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 aria-hidden="true" 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><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'll build a practical mental model of .NET Aspire and explore how orchestration, service discovery, health checks, and <a href="https://www.asptoday.com/p/deep-dive-into-distributed-tracing">OpenTelemetry</a> can make distributed ASP.NET Core development much easier. </p><h2>The Problem with Distributed Applications</h2><p>A traditional ASP.NET Core application might be relatively simple.</p><pre><code><code>Browser
   &#8595;
ASP.NET Core
   &#8595;
Database</code></code></pre><p>As the application grows, that picture changes.</p><p>We might introduce:</p><pre><code><code>Web Frontend
     &#8595;
API Gateway
     &#8595;
 &#9484;&#9472;&#9472;&#9472;&#9532;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
 &#8595;   &#8595;         &#8595;
Orders Products Payments
 &#8595;       &#8595;        &#8595;
SQL    Redis    Message Queue</code></code></pre><p>Soon, running the application locally means starting several projects and infrastructure dependencies.</p><p>Developers need to know which ports services use.</p><p>Configuration files contain connection strings.</p><p>One service must start before another.</p><p>Databases and containers need to be running.</p><p>Telemetry is spread across multiple consoles.</p><p>The architecture may be well designed, but the development experience becomes increasingly difficult.</p><p>This is one of the problems Aspire is designed to solve.</p><h2>What Is .NET Aspire?</h2><p>Aspire provides tooling for building and operating distributed applications, with a strong emphasis on orchestration and observability.</p><p>Rather than treating each application, database, cache, and supporting service as an unrelated component, Aspire lets us describe the entire system as a collection of connected <strong>resources</strong>.</p><p>Microsoft currently describes Aspire as a code-first orchestration and observability layer for distributed applications.</p><p>That distinction matters.</p><p>Aspire is not another web framework replacing ASP.NET Core.</p><p>Your APIs remain ASP.NET Core applications.</p><p>Your background workers remain .NET workers.</p><p>Redis remains Redis.</p><p>PostgreSQL remains PostgreSQL.</p><p>Aspire provides a model that connects these components and makes the distributed application easier to develop and understand.</p><h2>Think of Aspire as the Application Coordinator</h2><p>Imagine an orchestra.</p><p>The musicians already know how to play their instruments.</p><p>The conductor does not replace them.</p><p>Instead, the conductor coordinates when everyone starts, how the pieces fit together, and how the entire performance behaves.</p><p>Aspire plays a similar role.</p><p>Your services already know how to perform their individual jobs.</p><p>Aspire helps coordinate the system around them.</p><h2>The AppHost</h2><p>The center of an Aspire application is the <strong>AppHost</strong>.</p><p>The AppHost describes your distributed application.</p><p>Instead of maintaining a collection of disconnected scripts and manually configured ports, you describe your resources and relationships in code.</p><p>A simplified AppHost might look like this:</p><pre><code><code>var builder = DistributedApplication.CreateBuilder(args);

var api = builder.AddProject&lt;Projects.CatalogApi&gt;("catalog");

builder.AddProject&lt;Projects.WebApp&gt;("web")
       .WithReference(api);

builder.Build().Run();</code></code></pre><p>We now have two resources:</p><pre><code><code>Catalog API
     &#8593;
     &#9474;
   Web App</code></code></pre><p>The important part is the relationship.</p><p>The web application references the API.</p><p>Aspire can use that relationship to provide connection information and service discovery rather than requiring the application to depend on a manually configured port.</p><p>Microsoft&#8217;s current Aspire guidance describes the AppHost as a code-first model for defining application architecture and relationships.</p><h2>Resources Are More Than ASP.NET Core Projects</h2><p>An Aspire resource does not have to be an ASP.NET Core project.</p><p>A distributed application might contain:</p><ul><li><p>ASP.NET Core APIs</p></li><li><p>Worker services</p></li><li><p>Databases</p></li><li><p><a href="https://www.asptoday.com/p/implementing-distributed-caching">Redis</a></p></li><li><p>Message brokers</p></li><li><p>Containers</p></li><li><p>Executables</p></li><li><p>Frontend applications</p></li></ul><p>The AppHost becomes a map of the entire application.</p><p>For example:</p><pre><code><code>             Web
              &#9474;
              &#9660;
          Catalog API
           /       \
          &#9660;         &#9660;
     PostgreSQL    Redis</code></code></pre><p>Instead of expecting every developer to reconstruct this architecture mentally, we can express it directly.</p><h2>Adding Infrastructure</h2><p>Suppose our Catalog API requires Redis.</p><p>Conceptually, our AppHost might define the cache and reference it from the API:</p><pre><code><code>var builder = DistributedApplication.CreateBuilder(args);

var cache = builder.AddRedis("cache");

var api = builder
    .AddProject&lt;Projects.CatalogApi&gt;("catalog")
    .WithReference(cache);

builder.Build().Run();</code></code></pre><p><code>WithReference()</code> tells Aspire that the API depends on the cache.</p><p>Aspire integrations can then provide the consuming application with the connection information it needs.</p><p>This is far cleaner than telling every developer:</p><p>&#8220;Start Redis manually, make sure it uses this port, then copy this connection string into your local configuration.&#8221;</p><h2>Why Code-First Orchestration Matters</h2><p>Infrastructure relationships often exist only in documentation.</p><p>That documentation becomes outdated.</p><p>Someone changes a port.</p><p>A new database is introduced.</p><p>A service gets renamed.</p><p>Another developer joins the team and spends half a day figuring out how everything fits together.</p><p>With Aspire, the application topology lives alongside the application.</p><p>That makes relationships:</p><ul><li><p>Visible</p></li><li><p>Version controlled</p></li><li><p>Repeatable</p></li><li><p>Easier to modify</p></li></ul><p>The architecture becomes something developers can run rather than something they merely read about.</p><h2>Orchestration Does Not Mean Business Orchestration</h2><p>There is an important distinction here.</p><p>Aspire application orchestration is not the same thing as orchestrating business workflows.</p><p>For example, Aspire does not replace the Saga pattern we explored earlier.</p><p>A Saga might coordinate:</p><pre><code><code>Create Order
    &#8595;
Reserve Stock
    &#8595;
Process Payment
    &#8595;
Arrange Shipping</code></code></pre><p>Aspire coordinates the resources required to run the application.</p><pre><code><code>Start Database
      &#8595;
Start API
      &#8595;
Start Frontend</code></code></pre><p>These solve completely different problems.</p><h2>Controlling Startup Dependencies</h2><p>Distributed applications often suffer from startup races.</p><p>Imagine the frontend starts immediately and calls the API.</p><p>The API is still starting.</p><p>The first request fails.</p><p>Or perhaps the API starts before its database is ready.</p><p>Aspire allows resource relationships to express readiness dependencies.</p><p>For example:</p><pre><code><code>var api = builder
    .AddProject&lt;Projects.CatalogApi&gt;("catalog");

builder
    .AddProject&lt;Projects.WebApp&gt;("web")
    .WithReference(api)
    .WaitFor(api);</code></code></pre><p><code>WaitFor()</code> can prevent the dependent resource from starting until the referenced resource is ready according to its lifecycle or health state. Microsoft&#8217;s current Aspire getting-started guidance demonstrates this alongside health checks to prevent startup race conditions.</p><p>This sounds like a small improvement.</p><p>Across a system with many dependencies, it becomes extremely valuable.</p><h2>Service Discovery</h2><p>Now we reach one of the biggest headaches in distributed development.</p><p>How does one service find another?</p><p>Suppose our Catalog API runs locally at:</p><p>https://localhost:7143</p><p>A developer might configure:</p><pre><code><code>{
  "CatalogApi": "https://localhost:7143"
}</code></code></pre><p>But another developer might receive:</p><p>https://localhost:7288</p><p>Production uses a completely different hostname.</p><p>Containers introduce another network environment.</p><p>Hardcoded addresses become fragile quickly.</p><h2>Logical Service Names</h2><p>Service discovery solves this problem.</p><p>Instead of thinking:</p><pre><code><code>Call https://localhost:7143</code></code></pre><p>the application thinks:</p><pre><code><code>Call catalog</code></code></pre><p>Aspire knows where <code>catalog</code> actually lives.</p><p>A client can therefore use a logical service address rather than a hardcoded endpoint.</p><p>For example:</p><pre><code><code>builder.Services.AddHttpClient&lt;CatalogClient&gt;(client =&gt;
{
    client.BaseAddress =
        new Uri("https+http://catalog");
});</code></code></pre><p>Aspire&#8217;s service discovery resolves the logical service name to the appropriate endpoint at runtime. <a href="https://aspire.dev/fundamentals/service-discovery/">Microsoft&#8217;s documentation</a> specifically describes this model as using logical names instead of hardcoded URLs.</p><h2>Why This Changes Local Development</h2><p>Imagine five developers working on the same system.</p><p>Without service discovery, everyone needs matching port configuration.</p><p>With service discovery:</p><pre><code><code>Web
 &#8595;
"catalog"
 &#8595;
Service Discovery
 &#8595;
Actual Catalog Endpoint</code></code></pre><p>The consuming code no longer cares whether Catalog runs:</p><pre><code><code>localhost:5237</code></code></pre><p>or somewhere else entirely.</p><p>The same basic service relationship can work across different environments because endpoint resolution is separated from application logic.</p><h2>Adding a Database</h2><p>Let&#8217;s make the example more realistic.</p><p>Suppose the Catalog API needs PostgreSQL.</p><p>The AppHost can model that dependency too.</p><pre><code><code>var builder = DistributedApplication.CreateBuilder(args);

var postgres = builder.AddPostgres("postgres");

var catalogDb =
    postgres.AddDatabase("catalogdb");

var catalog = builder
    .AddProject&lt;Projects.CatalogApi&gt;("catalog")
    .WithReference(catalogDb);

builder.AddProject&lt;Projects.WebApp&gt;("web")
       .WithReference(catalog)
       .WaitFor(catalog);

builder.Build().Run();</code></code></pre><p>Our topology is now:</p><pre><code><code>Web
 &#8595;
Catalog API
 &#8595;
PostgreSQL
 &#8595;
Catalog Database</code></code></pre><p>The code itself explains the relationship.</p><h2>Adding Redis</h2><p>Now suppose product information is frequently requested.</p><p>We introduce Redis.</p><pre><code><code>var cache = builder.AddRedis("cache");

var catalog = builder
    .AddProject&lt;Projects.CatalogApi&gt;("catalog")
    .WithReference(catalogDb)
    .WithReference(cache);</code></code></pre><p>The architecture becomes:</p><pre><code><code>             Web
              &#9474;
              &#9660;
          Catalog API
           /       \
          &#9660;         &#9660;
   PostgreSQL      Redis</code></code></pre><p>Notice what is happening.</p><p>We aren&#8217;t adding Redis-specific orchestration logic to our web application.</p><p>We&#8217;re describing the dependency at the application-model level.</p><h2>Aspire Integrations</h2><p>Aspire provides integrations for many common infrastructure components and services.</p><p>These integrations help model resources and provide appropriate configuration to applications consuming them.</p><p>The important idea is not memorizing a list of integrations.</p><p>It is understanding the pattern:</p><pre><code><code>Define Resource
      &#8595;
Reference Resource
      &#8595;
Aspire Supplies Configuration
      &#8595;
Application Uses Resource</code></code></pre><p>This creates a much more consistent way to wire distributed applications together.</p><h2>Service Defaults</h2><p>Distributed applications tend to repeat the same infrastructure configuration.</p><p>Every service needs:</p><ul><li><p><a href="https://www.asptoday.com/p/aspnet-core-health-checks-keeping">Health checks</a></p></li><li><p>Telemetry</p></li><li><p>Service discovery</p></li><li><p>Resilience configuration</p></li></ul><p>Copying the same setup into every project creates maintenance problems.</p><p>Aspire addresses this through <strong>Service Defaults</strong>.</p><p>A Service Defaults project centralizes common configuration that can be reused across projects in the solution.</p><p>Microsoft&#8217;s Aspire templates describe Service Defaults as reusable configuration for areas including resilience, service discovery, and telemetry.</p><h2>Why Service Defaults Matter</h2><p>Imagine ten ASP.NET Core services.</p><p>Without shared defaults:</p><pre><code><code>Orders     &#8594; configure telemetry
Payments   &#8594; configure telemetry
Catalog    &#8594; configure telemetry
Shipping   &#8594; configure telemetry
Identity   &#8594; configure telemetry</code></code></pre><p>Someone eventually configures one differently.</p><p>With shared defaults:</p><pre><code><code>          Service Defaults
        /    /    |    \    \
       &#8595;    &#8595;     &#8595;     &#8595;    &#8595;
Orders Payments Catalog Shipping Identity</code></code></pre><p>The teams begin with a consistent baseline.</p><p>Individual applications can still add their own configuration where necessary.</p><p>Observability from the Beginning</p><p>Distributed applications are difficult to debug because a single user action may cross several services.</p><p>Imagine a checkout request:</p><pre><code><code>Browser
   &#8595;
Web
   &#8595;
Orders API
   &#8595;
Inventory API
   &#8595;
Payment API
   &#8595;
Database</code></code></pre><p>The user sees:</p><pre><code><code>Checkout failed.</code></code></pre><p>Where did it fail?</p><p>Without distributed observability, developers may need to inspect several log files and manually correlate timestamps.</p><p>Aspire encourages observability as part of the development experience rather than something added just before production.</p><h2>The Aspire Dashboard</h2><p>One of Aspire&#8217;s most useful features is its dashboard.</p><p>When you run an AppHost, the dashboard provides a centralized view of the resources in your distributed application.</p><p>Instead of switching between terminal windows, developers get a place to inspect the system.</p><p>The dashboard works with OpenTelemetry data and can surface logs, traces, metrics, resource information, and endpoints. Microsoft&#8217;s documentation notes that the dashboard launches automatically in common AppHost development workflows.</p><p>This dramatically improves the local debugging experience.</p><h2>Structured Logs</h2><p>Suppose the Orders API produces:</p><pre><code><code>logger.LogInformation(
    "Processing order {OrderId}",
    order.Id);</code></code></pre><p>Structured logging keeps <code>OrderId</code> as meaningful data rather than burying it inside a text string.</p><p>Across distributed services, this becomes particularly useful because developers can search and correlate events much more effectively.</p><h2>Distributed Traces</h2><p>We explored OpenTelemetry and distributed tracing earlier in this series.</p><p>Aspire makes those concepts immediately useful during development.</p><p>Suppose a request travels:</p><pre><code><code>Web
 &#9474;
 &#9492;&#9472;&#9472; Orders API          35 ms
       &#9474;
       &#9500;&#9472;&#9472; Inventory     18 ms
       &#9474;
       &#9492;&#9472;&#9472; Payments     840 ms</code></code></pre><p>The problem becomes obvious.</p><p>Payments is taking most of the time.</p><p>Instead of guessing which service caused the delay, the trace shows the request&#8217;s journey.</p><h2>Metrics</h2><p>Logs tell us what happened.</p><p>Traces show how a request moved.</p><p>Metrics tell us how the system behaves over time.</p><p>Useful metrics might include:</p><pre><code><code>Requests per second
Request duration
Failed requests
CPU usage
Memory usage
Dependency latency</code></code></pre><p>Aspire&#8217;s Service Defaults can configure OpenTelemetry collection for logging, tracing, and metrics, while OTLP allows telemetry to be exported to compatible monitoring systems.</p><h2>Health Checks</h2><p>Observability tells us what is happening.</p><p>Health checks answer another important question:</p><p><strong>Can this service actually do its job?</strong></p><p>An application process may technically be running while its database is unavailable.</p><p>That service isn&#8217;t truly healthy.</p><p>ASP.NET Core health checks can represent conditions such as:</p><pre><code><code>API running            &#10003;
Database reachable     &#10003;
Redis reachable        &#10003;
Message broker         &#10007;</code></code></pre><p>Aspire can incorporate health information into resource orchestration and visibility.</p><h2>A More Complete Example</h2><p>Let&#8217;s imagine we&#8217;re building an online store.</p><p>Our system contains:</p><pre><code><code>Web Frontend
Catalog API
Orders API
PostgreSQL
Redis</code></code></pre><p>The AppHost could conceptually describe the system like this:</p><pre><code><code>var builder =
    DistributedApplication.CreateBuilder(args);

var postgres =
    builder.AddPostgres("postgres");

var ordersDb =
    postgres.AddDatabase("ordersdb");

var cache =
    builder.AddRedis("cache");

var catalog =
    builder.AddProject&lt;Projects.CatalogApi&gt;("catalog")
           .WithReference(cache);

var orders =
    builder.AddProject&lt;Projects.OrdersApi&gt;("orders")
           .WithReference(ordersDb)
           .WithReference(catalog)
           .WaitFor(ordersDb);

builder.AddProject&lt;Projects.WebApp&gt;("web")
       .WithReference(catalog)
       .WithReference(orders)
       .WaitFor(catalog)
       .WaitFor(orders);

builder.Build().Run();</code></code></pre><p>Now look at what the application model communicates.</p><pre><code><code>                 Web
                /   \
               &#9660;     &#9660;
          Catalog   Orders
             &#9474;       /  \
             &#9660;      &#9660;    &#9660;
           Redis   DB   Catalog</code></code></pre><p>Someone joining the project can understand much of the topology simply by reading the AppHost.</p><h2>Adding a New Developer</h2><p>This is where Aspire can make a noticeable difference.</p><p>Imagine joining a project containing eight services.</p><p>Traditionally, onboarding instructions might say:</p><ol><li><p>Install PostgreSQL.</p></li><li><p>Install Redis.</p></li><li><p>Configure these connection strings.</p></li><li><p>Start Orders first.</p></li><li><p>Start Catalog.</p></li><li><p>Make sure Catalog uses port 7182.</p></li><li><p>Start the frontend.</p></li><li><p>Open three terminals for logs.</p></li><li><p>Configure your tracing environment.</p></li></ol><p>That is a lot of tribal knowledge.</p><p>A well-designed Aspire application can encode much more of that setup in the application model.</p><p>The developer starts the AppHost.</p><p>The system topology comes to life.</p><h2>Aspire and Containers</h2><p>Containers remain useful.</p><p>Aspire does not make Docker irrelevant.</p><p>Instead, it can model containerized dependencies alongside your application projects.</p><p>For local development, that means infrastructure such as Redis or PostgreSQL can participate in the same application model rather than being treated as completely separate setup tasks.</p><p>This complements the <a href="https://www.asptoday.com/p/building-scalable-applications-with">Docker</a> and <a href="https://www.asptoday.com/p/aspnet-core-and-kubernetes-from-development">Kubernetes</a> concepts we&#8217;ve already explored in this series. </p><h2>Aspire and Kubernetes</h2><p>Another important distinction:</p><p>Aspire is not Kubernetes.</p><p>Kubernetes is an orchestration platform for deploying and operating containerized workloads.</p><p>Aspire&#8217;s AppHost provides a code-first application model that can be used during development and can participate in deployment workflows.</p><p>You should not think:</p><pre><code><code>Aspire OR Kubernetes</code></code></pre><p>Think:</p><pre><code><code>Application Model
        &#8595;
Deployment Environment</code></code></pre><p>The deployment environment might eventually involve containers, Kubernetes, Azure services, or another supported target.</p><p>Aspire&#8217;s current documentation explicitly separates application modeling from deployment topology and supports multiple deployment directions rather than tying the model to a single hosting destination.</p><h2>Aspire Is Not Only for New Applications</h2><p>You do not need to rebuild an existing ASP.NET Core system from scratch.</p><p>Aspire can be introduced incrementally.</p><p>For example, you might start by adding an AppHost that models:</p><pre><code><code>Existing API
Existing Worker
Redis
PostgreSQL</code></code></pre><p>Then introduce common telemetry.</p><p>Later, add service discovery.</p><p>Then model more infrastructure.</p><p>Microsoft&#8217;s current guidance specifically supports adding Aspire to existing applications and gradually modeling containers, databases, caches, queues, workers, and other resources.</p><p>That makes adoption much less risky.</p><h2>Where Aspire Helps Most</h2><p>Aspire becomes particularly valuable when an application contains enough moving parts that local development starts becoming painful.</p><p>Imagine:</p><pre><code><code>Frontend
API Gateway
Identity Service
Catalog Service
Orders Service
Payment Service
Worker
Redis
PostgreSQL
RabbitMQ</code></code></pre><p>Running all of that manually is frustrating.</p><p>Understanding it is harder.</p><p>Debugging it is harder still.</p><p>The value of a unified application model grows with the number of dependencies.</p><h2>Where Aspire May Be Unnecessary</h2><p>Not every ASP.NET Core application needs distributed orchestration.</p><p>Suppose you have:</p><pre><code><code>Razor Pages App
      &#8595;
SQL Database</code></code></pre><p>The system is simple.</p><p>Adding additional tooling may provide little value.</p><p>This is an important architectural lesson we&#8217;ve repeated throughout this series:</p><p><strong>Use complexity to solve complexity. Do not introduce complexity merely because a technology is interesting.</strong></p><h2>Common Mistake: Treating Aspire as Magic</h2><p>Aspire does not make distributed systems simple.</p><p>Network failures still happen.</p><p>Databases still become unavailable.</p><p>Services still experience latency.</p><p>Distributed transactions are still difficult.</p><p>Security still matters.</p><p>What Aspire improves is our ability to <strong>model, connect, run, and observe</strong> those systems.</p><p>Good architecture remains essential.</p><h2>Common Mistake: Hiding Architecture</h2><p>Because Aspire makes adding resources convenient, developers may be tempted to keep adding services.</p><p>That would be the wrong lesson.</p><p>If one ASP.NET Core application solves the problem well, splitting it into six services does not automatically improve the architecture.</p><p>The AppHost should describe a sensible architecture.</p><p>It should not justify an unnecessarily complicated one.</p><h2>Common Mistake: Ignoring Production Differences</h2><p>A beautiful local environment does not guarantee a healthy production environment.</p><p>Production introduces:</p><ul><li><p>Real network latency</p></li><li><p><a href="https://www.asptoday.com/p/advanced-authentication-patterns">Authentication</a></p></li><li><p>Secrets</p></li><li><p>Scaling</p></li><li><p>Regional failures</p></li><li><p>Cost</p></li><li><p>Resource limits</p></li></ul><p>Use Aspire to improve development and application modeling, but continue designing explicitly for the environment where the system will actually operate.</p><h2>A Real-World Scenario</h2><p>Imagine a logistics company building a parcel-processing platform.</p><p>Its system contains:</p><pre><code><code>Operations Portal
       &#8595;
Shipment API
       &#8595;
 &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9532;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
 &#9660;     &#9660;          &#9660;
Pricing Tracking Notifications
 &#9474;       &#9474;
 &#9660;       &#9660;
Redis PostgreSQL

Background Workers
       &#8595;
Message Broker</code></code></pre><p>Without a unified development model, every engineer must understand how to launch and configure all of these pieces.</p><p>With Aspire, the AppHost describes them as resources and relationships.</p><p>Developers start the distributed application.</p><p>Service discovery connects the services.</p><p>Health checks reveal whether dependencies are ready.</p><p>OpenTelemetry records activity.</p><p>The dashboard exposes the system&#8217;s behavior.</p><p>When a shipment request becomes slow, developers inspect its trace.</p><p>They discover that Pricing is waiting on a downstream dependency.</p><p>Instead of searching through five terminal windows, they can follow the request through the distributed system.</p><p>That is where Aspire becomes much more than a convenient project launcher.</p><p>It becomes a development environment for understanding the architecture itself.</p><h2>How This Fits Our ASP.NET Core Journey</h2><p>Over the course of this series, we&#8217;ve explored many pieces of distributed application architecture individually.</p><p>We&#8217;ve covered:</p><ul><li><p>Microservices</p></li><li><p>Docker</p></li><li><p>Kubernetes</p></li><li><p>Event-driven architecture</p></li><li><p>gRPC</p></li><li><p>Health checks</p></li><li><p>Resilience</p></li><li><p>Distributed caching</p></li><li><p>Distributed tracing</p></li><li><p>API Gateways</p></li><li><p>Service meshes</p></li><li><p>Internal Developer Platforms</p></li></ul><p>Aspire does not replace those concepts.</p><p>It provides a way to bring many of them into a more coherent developer experience.</p><p>Instead of thinking about an application as ten unrelated projects and five infrastructure components, we can model it as one distributed system.</p><p>That is an important shift.</p><h2>What Aspire Gives Us</h2><p>The easiest way to remember Aspire is through four questions.</p><p><strong>What runs?</strong></p><p>The AppHost describes the resources.</p><p><strong>What depends on what?</strong></p><p>References describe relationships.</p><p><strong>How do services find each other?</strong></p><p>Service discovery resolves logical names to endpoints.</p><p><strong>How do we understand what is happening?</strong></p><p>OpenTelemetry and the Aspire dashboard provide observability.</p><p>Together, those capabilities attack some of the most frustrating parts of distributed application development.</p><h2>Closing Thoughts</h2><p>Distributed architecture gives development teams powerful ways to scale applications, isolate responsibilities, and choose the right technology for each workload. But every new service also creates another endpoint, dependency, configuration problem, health state, and source of telemetry.</p><p>Aspire tackles that operational complexity from the developer&#8217;s perspective.</p><p>With a code-first AppHost, we can describe our distributed application as a system rather than a collection of unrelated processes. Service discovery removes the need to hardcode changing endpoints. Shared defaults give services a consistent foundation for resilience, health checks, and telemetry. OpenTelemetry and the Aspire dashboard then help us see how those services behave together.</p><p>The result is not a simpler architecture by magic.</p><p>It is an architecture that is <strong>easier to run, easier to understand, easier to observe, and easier for the next developer to work with</strong>.</p><p>And as our ASP.NET Core systems become increasingly distributed, that developer experience matters just as much as the individual services themselves.</p><h2>Subscribe Now</h2><p>Enjoying the series? <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 tutorials, advanced architecture guides, and real-world .NET engineering strategies. Join our Substack Chat to discuss distributed applications, share development experiences, and connect with developers building modern ASP.NET Core 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[Edge Computing with ASP.NET Core: Running Applications Closer to Users]]></title><description><![CDATA[Discover how edge computing brings ASP.NET Core workloads closer to users and devices for faster responses, local processing, and greater resilience. #ASPToday #aspnetcore #edgecomputing #cloudnative #dotnet]]></description><link>https://www.asptoday.com/p/edge-computing-with-aspnet-core-running</link><guid isPermaLink="false">https://www.asptoday.com/p/edge-computing-with-aspnet-core-running</guid><dc:creator><![CDATA[Jenny Muralidharan]]></dc:creator><pubDate>Tue, 11 Aug 2026 15:01:08 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!rCs1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73cc23c4-4834-4591-89d7-97e9ec5fc636_1672x941.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Cloud computing transformed the way we build applications by giving us centralized infrastructure that can scale almost instantly. But sending every request and every piece of data back to a distant cloud region is not always the best solution. Some applications need extremely fast responses, must continue working when connectivity disappears, or generate so much data that constantly sending everything to the cloud becomes inefficient. Edge computing addresses these challenges by moving part of the application closer to the people, devices, and machines using 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_!rCs1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73cc23c4-4834-4591-89d7-97e9ec5fc636_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rCs1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73cc23c4-4834-4591-89d7-97e9ec5fc636_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!rCs1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73cc23c4-4834-4591-89d7-97e9ec5fc636_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!rCs1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73cc23c4-4834-4591-89d7-97e9ec5fc636_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!rCs1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73cc23c4-4834-4591-89d7-97e9ec5fc636_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rCs1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73cc23c4-4834-4591-89d7-97e9ec5fc636_1672x941.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/73cc23c4-4834-4591-89d7-97e9ec5fc636_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;:2586682,&quot;alt&quot;:&quot;Remote mountain sensors process data locally at an edge station before syncing with the cloud. &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/210442196?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73cc23c4-4834-4591-89d7-97e9ec5fc636_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Remote mountain sensors process data locally at an edge station before syncing with the cloud. " title="Remote mountain sensors process data locally at an edge station before syncing with the cloud. " srcset="https://substackcdn.com/image/fetch/$s_!rCs1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73cc23c4-4834-4591-89d7-97e9ec5fc636_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!rCs1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73cc23c4-4834-4591-89d7-97e9ec5fc636_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!rCs1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73cc23c4-4834-4591-89d7-97e9ec5fc636_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!rCs1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73cc23c4-4834-4591-89d7-97e9ec5fc636_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 aria-hidden="true" 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><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 what edge computing means for <a href="https://www.asptoday.com/p/introduction-to-aspnet-core-whats-new-and-why-it-matters">ASP.NET Core</a> developers, where it makes sense, how edge and cloud systems work together, and how to design practical applications that remain fast, secure, and resilient. </p><h2>What Does &#8220;The Edge&#8221; Actually Mean?</h2><p>We&#8217;ve spent years moving applications into the cloud.</p><p>Now we&#8217;re talking about moving some of them back?</p><p>Not exactly.</p><p>Edge computing does not mean abandoning the cloud. It means deciding <strong>where each piece of work should happen</strong>.</p><p>Imagine a factory in Germany sending sensor readings to a cloud data center hundreds of kilometers away. If every machine decision requires a round trip to that cloud, network latency and connectivity become part of the production process.</p><p>Instead, the factory can run some application services locally.</p><p>Those services process important information immediately and send selected data to the cloud afterward.</p><p>That local computing environment is the edge.</p><p>The edge could be a small server in a factory, a computer inside a retail store, an industrial gateway, a regional data center, or another computing system located near the source of the data.</p><p>The exact hardware matters less than the architectural idea.</p><p><strong>Move time-sensitive processing closer to where the data is created or consumed. </strong></p><h2>Cloud Computing vs Edge Computing</h2><p>Traditional cloud architecture often looks like this:</p><pre><code><code>Device
   &#9474;
   &#9474; Internet
   &#9660;
Cloud Application
   &#9474;
   &#9660;
Cloud Database</code></code></pre><p>Every important operation depends on reaching the cloud.</p><p>An edge architecture introduces another layer:</p><pre><code><code>Device
   &#9474;
   &#9660;
Edge Application
   &#9474;
   &#9500;&#9472;&#9472; Local Processing
   &#9500;&#9472;&#9472; Local Storage
   &#9492;&#9472;&#9472; Immediate Decisions
   &#9474;
   &#9474; Internet
   &#9660;
Cloud Platform</code></code></pre><p>The edge handles work that benefits from being local.</p><p>The cloud continues handling workloads that benefit from centralized infrastructure.</p><p>This distinction is important because edge computing and cloud computing are not competitors. In most real applications, they complement each other. </p><h2>Why Put ASP.NET Core at the Edge?</h2><p>ASP.NET Core is well suited to many edge scenarios because .NET runs across Windows and Linux, can be deployed in containers, provides high-performance web APIs, and includes mature support for <a href="https://www.asptoday.com/p/dependency-injection-in-aspnet-core">dependency injection</a>, configuration, background services, security, and observability.</p><p>An ASP.NET Core application running at an edge location might expose APIs to local devices, process incoming information, store temporary data, make immediate decisions, and periodically communicate with cloud services.</p><p>Developers can use many of the same programming patterns they already know.</p><p>The biggest difference is the environment.</p><p>At the edge, you cannot assume the network is always available. </p><h2>The Latency Problem</h2><p>Latency is the delay between sending a request and receiving a response.</p><p>For many business applications, a little network latency is perfectly acceptable.</p><p>If someone requests a monthly sales report, an extra fraction of a second probably does not matter.</p><p>But consider a manufacturing system detecting a dangerous machine condition.</p><p>Waiting for:</p><pre><code><code>Machine
   &#8595;
Internet
   &#8595;
Cloud
   &#8595;
Analysis
   &#8595;
Internet
   &#8595;
Machine</code></code></pre><p>may be unnecessary when the decision could happen locally.</p><p>With edge processing:</p><pre><code><code>Machine
   &#8595;
Local ASP.NET Core Service
   &#8595;
Immediate Response</code></code></pre><p>The cloud can receive the event afterward for analytics, reporting, and long-term storage. </p><h2>Edge Computing Is About More Than Speed</h2><p>Latency receives most of the attention, but it is only one reason to use edge computing.</p><p>Connectivity is another.</p><p>Imagine a delivery warehouse where handheld scanners depend entirely on a cloud API.</p><p>If the internet connection fails, workers may no longer be able to:</p><ul><li><p>Scan packages</p></li><li><p>Locate inventory</p></li><li><p>Confirm collections</p></li><li><p>Record deliveries</p></li></ul><p>An edge service inside the warehouse can keep those operations running locally.</p><p>When connectivity returns, the edge system synchronizes its data with the cloud.</p><p>That is a major architectural advantage. </p><h2>Designing for Intermittent Connectivity</h2><p>Cloud applications are often designed around an assumption:</p><blockquote><p>The network is available.</p></blockquote><p>Edge applications should make the opposite assumption:</p><blockquote><p>The network will eventually fail.</p></blockquote><p>That changes how we design software.</p><p>Instead of immediately sending every operation to the cloud, an edge application may record work locally.</p><p>For example:</p><pre><code><code>public async Task RecordScanAsync(PackageScan scan)
{
    await _localRepository.SaveAsync(scan);

    await _syncQueue.EnqueueAsync(scan.Id);
}</code></code></pre><p>The scan succeeds locally.</p><p>A background process can attempt cloud synchronization separately.</p><p>This prevents temporary connectivity problems from stopping the user&#8217;s work. </p><h2>Background Synchronization</h2><p>ASP.NET Core&#8217;s hosted services are useful for synchronization workloads.</p><p>A simplified worker might look like this:</p><pre><code><code>public class CloudSyncWorker : BackgroundService
{
    private readonly ISyncService _syncService;

    public CloudSyncWorker(ISyncService syncService)
    {
        _syncService = syncService;
    }

    protected override async Task ExecuteAsync(
        CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            await _syncService.PushPendingChangesAsync(
                stoppingToken);

            await Task.Delay(
                TimeSpan.FromSeconds(30),
                stoppingToken);
        }
    }
}</code></code></pre><p>Production synchronization is more complicated, but the pattern is straightforward.</p><p>Perform the user&#8217;s work locally first.</p><p>Synchronize with the central platform when possible.</p><h2>Local Storage at the Edge</h2><p>Once an application can operate offline, it usually needs local storage.</p><p>Depending on the workload, that might include a relational database, embedded database, local files, cache, or message queue.</p><p>The edge service might store:</p><ul><li><p>Pending transactions</p></li><li><p>Device readings</p></li><li><p>Configuration</p></li><li><p>Product information</p></li><li><p>Inventory data</p></li><li><p>Synchronization state</p></li></ul><p>The important question is not simply what database to choose.</p><p>You must decide <strong>which data belongs at the edge and which data remains authoritative in the cloud</strong>.</p><p>That distinction prevents synchronization from becoming chaotic. </p><h2>Data Synchronization Gets Complicated</h2><p>Suppose a retail store loses its internet connection.</p><p>While offline, the store sells the final item in stock.</p><p>At the same time, the online store sells what it believes is the same final item.</p><p>When the connection returns, two valid systems disagree.</p><p>Which one wins?</p><p>This is a synchronization conflict.</p><p>Edge systems need explicit rules for situations like this.</p><p>Possible strategies include timestamps, version numbers, optimistic concurrency, business-specific conflict resolution, or manual review.</p><p>There is no universal answer.</p><p>The correct strategy depends on what the data represents. </p><h2>Idempotency Matters Again</h2><p>Our earlier discussion of idempotency becomes particularly useful at the edge.</p><p>Suppose an edge application sends an order to the cloud.</p><p>The cloud processes it successfully, but the network disconnects before the edge receives the response.</p><p>Did the operation succeed?</p><p>The edge cannot know.</p><p>If it blindly retries, the customer could receive two orders.</p><p>An idempotency key allows the cloud service to recognize the repeated operation.</p><p>For example:</p><pre><code><code>POST /orders
Idempotency-Key: 91d57e2f-2e8a-4b11</code></code></pre><p>If the same request arrives again, the server can return the previous result rather than creating another order.</p><p>Reliable synchronization depends heavily on patterns like this. </p><h2>Practical Use Case: Manufacturing</h2><p>Factories are a natural environment for edge computing.</p><p>Modern equipment can generate enormous streams of sensor data.</p><p>Sending every temperature reading, vibration measurement, and machine event to the cloud may be unnecessary.</p><p>An edge ASP.NET Core application can collect the readings locally.</p><p>It can detect abnormal conditions immediately.</p><p>For example:</p><pre><code><code>if (reading.Temperature &gt; safetyLimit)
{
    await _machineController.StopAsync();
}</code></code></pre><p>The edge application reacts immediately.</p><p>Meanwhile, summarized measurements and important events can still be sent to the cloud for long-term analytics. </p><h2>Practical Use Case: Retail Stores</h2><p>Imagine a supermarket with:</p><ul><li><p>Checkout systems</p></li><li><p>Barcode scanners</p></li><li><p>Electronic price displays</p></li><li><p>Inventory terminals</p></li></ul><p>If every interaction depends on the internet, a network outage could disrupt the entire store.</p><p>A local ASP.NET Core service can maintain essential product and pricing information.</p><p>Checkout operations continue.</p><p>Transactions are queued.</p><p>When connectivity returns, the store synchronizes with central systems.</p><p>The cloud still provides centralized management, but the store is no longer completely dependent on it. </p><h2>Practical Use Case: Logistics</h2><p>Delivery and logistics systems are another excellent example.</p><p>Warehouses may process thousands of package scans every hour.</p><p>A local edge application can validate scans, update warehouse state, coordinate sorting equipment, and maintain operations even during temporary cloud outages.</p><p>The central platform receives synchronized events as connectivity permits.</p><p>The warehouse gets responsiveness.</p><p>Head office gets centralized visibility.</p><p>Both benefit. </p><h2>Practical Use Case: Remote Locations</h2><p>Not every application runs in a city with excellent connectivity.</p><p>Consider:</p><ul><li><p>Ships</p></li><li><p>Mines</p></li><li><p>Farms</p></li><li><p>Construction sites</p></li><li><p>Remote energy facilities</p></li></ul><p>Network connections may be slow, expensive, intermittent, or unavailable.</p><p>Edge computing allows important operations to continue locally.</p><p>The cloud becomes a synchronization and management layer rather than a constant dependency. </p><h2>Edge AI</h2><p>Our previous article explored integrating AI and machine learning with ASP.NET Core.</p><p>Edge computing adds another interesting possibility.</p><p>Some AI inference can happen locally.</p><p>Imagine a manufacturing camera inspecting products on a conveyor belt.</p><p>Sending every image to the cloud introduces bandwidth usage and delay.</p><p>Instead, a local model can inspect the image.</p><p>Only unusual results might be uploaded.</p><p>The workflow becomes:</p><pre><code><code>Camera
   &#8595;
Edge AI Model
   &#8595;
ASP.NET Core Service
   &#8595;
Immediate Decision
   &#8595;
Selected Results
   &#8595;
Cloud</code></code></pre><p>This combination of AI and edge computing can be particularly useful when low latency, privacy, or bandwidth efficiency matters. </p><h2>Containers at the Edge</h2><p>Containers make edge deployment much easier.</p><p>An ASP.NET Core service can be packaged into a container and deployed consistently across many locations.</p><p>The same application image might run in:</p><ul><li><p>50 stores</p></li><li><p>200 warehouses</p></li><li><p>1,000 industrial sites</p></li></ul><p>Containers provide consistency, but deployment at this scale creates another challenge.</p><p>How do you update all those locations safely? </p><h2>Managing Edge Deployments</h2><p>Centralized management is essential.</p><p>You need to know:</p><ul><li><p>Which application version each location runs</p></li><li><p>Whether deployments succeeded</p></li><li><p>Which devices are online</p></li><li><p>Whether an edge node is healthy</p></li><li><p>When updates failed</p></li></ul><p>Updates should also tolerate interruptions.</p><p>If connectivity disappears halfway through an upgrade, the site should not become unusable.</p><p>Versioned deployments and rollback strategies become especially important. </p><h2>Security at the Edge</h2><p>Edge computing changes your security boundary.</p><p>A cloud server usually lives inside a carefully controlled data center.</p><p>An edge server might live:</p><ul><li><p>Behind a checkout counter</p></li><li><p>Inside a warehouse</p></li><li><p>On a factory floor</p></li><li><p>In a remote cabinet</p></li></ul><p>Someone may be able to physically access it.</p><p>That means security must account for both network and physical threats.</p><p>Sensitive information stored locally should be protected appropriately, credentials should not be hardcoded into deployments, and each edge node should have a strong identity. </p><h2>Zero Trust at the Edge</h2><p>Our earlier Zero Trust discussion applies particularly well here.</p><p>The cloud should not automatically trust an edge device simply because it belongs to the company.</p><p>Each device or service should authenticate itself.</p><p>Likewise, edge services should verify cloud endpoints.</p><p>This can involve certificates, managed identities where supported, short-lived credentials, or other workload identity mechanisms.</p><p>The principle remains simple:</p><p><strong>Verify the identity of every participant rather than trusting its network location. </strong></p><h2>Protecting Local Data</h2><p>Edge systems sometimes contain sensitive information.</p><p>That data may include:</p><ul><li><p>Customer details</p></li><li><p>Transactions</p></li><li><p>Operational information</p></li><li><p><a href="https://www.asptoday.com/p/advanced-authentication-patterns">Authentication</a> credentials</p></li></ul><p>Encryption at rest becomes important, especially when hardware could be lost or stolen. </p><p>Data retention should also be considered carefully. </p><p>If an edge node only needs seven days of local information, keeping six months may create unnecessary risk. </p><p>Store only what the edge actually needs.  </p><h2>Observability at the Edge</h2><p>Monitoring a cloud application is relatively straightforward because the infrastructure is continuously connected.</p><p>Edge environments are different.</p><p>A node might disappear for hours.</p><p>Does that mean the application failed?</p><p>Or did the internet connection fail?</p><p>Your observability strategy needs to distinguish between the two.</p><p>Useful signals include:</p><ul><li><p>Application <a href="https://www.asptoday.com/p/aspnet-core-health-checks-keeping">health</a></p></li><li><p>CPU and memory usage</p></li><li><p>Synchronization backlog</p></li><li><p>Last successful cloud connection</p></li><li><p>Local storage capacity</p></li><li><p>Application version</p></li></ul><p>When connectivity returns, telemetry may also need to be uploaded in batches. </p><h2>Distributed Tracing</h2><p><a href="https://www.asptoday.com/p/deep-dive-into-distributed-tracing">Distributed tracing</a> becomes interesting when a request begins at the edge and continues in the cloud.</p><p>A correlation or trace identifier can follow the operation across both environments.</p><p>For example:</p><pre><code><code>Warehouse Scanner
      &#8595;
Edge API
      &#8595;
Local Queue
      &#8595;
Cloud Sync
      &#8595;
Order API
      &#8595;
Inventory Service</code></code></pre><p>A distributed trace helps developers understand the complete journey.</p><p>OpenTelemetry remains valuable here, although telemetry collection needs to account for intermittent connections and buffering. </p><h2>API Gateways and Edge Computing</h2><p>Our recent API Gateway article focused on providing a central entrance to backend services.</p><p>Edge architectures may introduce gateways at multiple levels.</p><p>A local gateway might route requests between devices and edge services.</p><p>A cloud gateway handles requests entering the central platform.</p><p>The architecture could resemble:</p><pre><code><code>Local Devices
      &#8595;
Edge Gateway
      &#8595;
ASP.NET Core Edge Services
      &#8595;
Cloud API Gateway
      &#8595;
Cloud Microservices</code></code></pre><p>The same architectural concepts appear at different physical locations. </p><h2>Service Meshes at the Edge</h2><p>Our previous article explored service meshes.</p><p>In larger edge installations with multiple local services, mesh-style capabilities may also be useful for secure service-to-service communication, traffic management, and observability.</p><p>However, complexity matters.</p><p>A small store running two services probably does not need the same infrastructure as a <a href="https://www.asptoday.com/p/aspnet-core-and-kubernetes-from-development">Kubernetes cluster</a> running hundreds of microservices.</p><p>Edge architecture rewards simplicity.</p><p>Every extra component must operate when connectivity is poor and remote troubleshooting is difficult. </p><h2>What Should Stay in the Cloud?</h2><p>Edge computing does not mean moving everything locally.</p><p>Centralized workloads often belong in the cloud.</p><p>Examples include:</p><ul><li><p>Organization-wide analytics</p></li><li><p>Long-term storage</p></li><li><p>Central reporting</p></li><li><p>Model training</p></li><li><p>Cross-location coordination</p></li><li><p>Large-scale batch processing</p></li></ul><p>The edge should handle workloads that genuinely benefit from proximity.</p><p>A useful question is:</p><blockquote><p>What breaks if the connection to the cloud disappears for an hour?</p></blockquote><p>The answer often reveals which capabilities belong locally.  </p><h2>Avoiding the &#8220;Mini Cloud Everywhere&#8221; Trap</h2><p>A common mistake is recreating the entire cloud platform at every edge location.</p><p>That can become extremely expensive and difficult to operate.</p><p>Imagine maintaining dozens of databases, message brokers, monitoring systems, gateways, and orchestration platforms across hundreds of locations.</p><p>The operational burden grows quickly.</p><p>Instead, keep edge environments focused.</p><p>Run only the services necessary for local operation.</p><p>Centralize everything else where practical. </p><h2>A Complete Edge Architecture</h2><p>Imagine a chain of smart manufacturing facilities.</p><p>Each factory runs an ASP.NET Core edge service.</p><p>Locally it:</p><ul><li><p>Receives machine telemetry.</p></li><li><p>Stores recent readings.</p></li><li><p>Detects safety conditions.</p></li><li><p>Controls local workflows.</p></li><li><p>Queues cloud synchronization.</p></li></ul><p>The cloud platform:</p><ul><li><p>Stores long-term data.</p></li><li><p>Compares performance across factories.</p></li><li><p>Trains predictive models.</p></li><li><p>Distributes configuration.</p></li><li><p>Provides central dashboards.</p></li></ul><p>The two layers work together.</p><p>If the cloud disappears temporarily, the factory keeps operating.</p><p>When connectivity returns, synchronization resumes.</p><p>That is the real strength of edge architecture. </p><h2>When You Probably Don&#8217;t Need Edge Computing</h2><p>Not every application needs an edge layer.</p><p>A traditional business website serving users over the internet may work perfectly well from cloud infrastructure.</p><p>Adding edge services introduces:</p><ul><li><p>Deployment complexity</p></li><li><p>Synchronization challenges</p></li><li><p>Additional security concerns</p></li><li><p>Remote monitoring requirements</p></li><li><p>More infrastructure to maintain</p></li></ul><p>Use edge computing when there is a clear reason.</p><p>Low latency, intermittent connectivity, local autonomy, privacy, or bandwidth constraints are good reasons.</p><p>&#8220;Edge is popular&#8221; is not. </p><h2>How This Fits Your ASP.NET Core Journey</h2><p>Our recent articles have gradually expanded the boundaries of an ASP.NET Core application.</p><p>We started with services and distributed architecture.</p><p>Then we explored API Gateways for managing incoming traffic.</p><p>Service meshes helped manage communication inside the platform.</p><p>AI and machine learning introduced intelligent processing.</p><p>Now edge computing asks a different architectural question:</p><p><strong>Where should that processing actually happen?</strong></p><p>Sometimes the answer is the cloud.</p><p>Sometimes it is a server sitting meters away from the user or machine.</p><p>Increasingly, the answer is both. </p><h2>Closing Thoughts</h2><p>Edge computing is less about a particular technology and more about making intelligent decisions about where software should run.</p><p>ASP.NET Core gives .NET developers a strong foundation for building edge services because the same APIs, dependency injection patterns, background workers, security practices, and observability tools used in cloud applications can also be applied closer to users and devices. </p><p>The architectural challenge is learning to design for a world where connectivity is not guaranteed. Applications need local autonomy, reliable synchronization, secure device identities, careful data management, and strong observability.</p><p>When those pieces come together, edge computing allows cloud and local infrastructure to complement each other rather than compete.</p><p>The cloud provides scale and centralized intelligence.</p><p>The edge provides proximity and independence.</p><p>Together, they can create ASP.NET Core applications that keep working exactly where they&#8217;re needed most. </p><h2>Subscribe Now</h2><p>Enjoying the series? Subscribe to <strong><a href="https://www.asptoday.com/">ASP Today</a></strong> for practical ASP.NET Core tutorials, cloud-native architecture guides, and real-world development strategies. Join our Substack Chat to discuss modern .NET development, share ideas, and connect with developers building the next generation of 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><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p> </p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><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[Integrating AI and Machine Learning in ASP.NET Core Applications: Practical Use Cases]]></title><description><![CDATA[Discover practical ways to integrate AI and machine learning into ASP.NET Core applications using ML.NET, Azure AI, OpenAI APIs, and real-world automation. #ASPToday #aspnetcore #artificialintelligence #machinelearning #dotnet]]></description><link>https://www.asptoday.com/p/integrating-ai-and-machine-learning</link><guid isPermaLink="false">https://www.asptoday.com/p/integrating-ai-and-machine-learning</guid><dc:creator><![CDATA[Jenny Muralidharan]]></dc:creator><pubDate>Tue, 04 Aug 2026 15:02:32 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!UxDz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2f13a1a-4074-4749-bc63-b52067de0001_1672x941.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Artificial Intelligence has rapidly moved from research labs into everyday business software. From intelligent search and chatbots to fraud detection and document analysis, AI is becoming an essential capability rather than a luxury feature.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UxDz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2f13a1a-4074-4749-bc63-b52067de0001_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UxDz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2f13a1a-4074-4749-bc63-b52067de0001_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!UxDz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2f13a1a-4074-4749-bc63-b52067de0001_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!UxDz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2f13a1a-4074-4749-bc63-b52067de0001_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!UxDz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2f13a1a-4074-4749-bc63-b52067de0001_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UxDz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2f13a1a-4074-4749-bc63-b52067de0001_1672x941.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e2f13a1a-4074-4749-bc63-b52067de0001_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;:2088716,&quot;alt&quot;:&quot;Engineers integrate AI services into an ASP.NET Core application using intelligent automation. &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/209463430?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2f13a1a-4074-4749-bc63-b52067de0001_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Engineers integrate AI services into an ASP.NET Core application using intelligent automation. " title="Engineers integrate AI services into an ASP.NET Core application using intelligent automation. " srcset="https://substackcdn.com/image/fetch/$s_!UxDz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2f13a1a-4074-4749-bc63-b52067de0001_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!UxDz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2f13a1a-4074-4749-bc63-b52067de0001_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!UxDz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2f13a1a-4074-4749-bc63-b52067de0001_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!UxDz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2f13a1a-4074-4749-bc63-b52067de0001_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 aria-hidden="true" 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><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>Fortunately, <a href="https://www.asptoday.com/p/introduction-to-aspnet-core-whats-new-and-why-it-matters">ASP.NET Core</a> provides an excellent foundation for integrating modern AI services and machine learning models into existing applications. Whether you're calling cloud-hosted large language models, running local ML.NET predictions, or combining multiple AI services into a single workflow, today's tools make it easier than ever to build intelligent applications. </p><p>In this guide, we'll explore practical ways to add AI to ASP.NET Core applications, understand where machine learning fits into modern software architecture, and learn how to build AI features that solve real business problems. </p><h2>Why AI Matters Today</h2><p>Almost every modern application now contains some form of intelligence.</p><p>Examples include:</p><ul><li><p>Product recommendations</p></li><li><p>Smart search</p></li><li><p>Image recognition</p></li><li><p>Voice assistants</p></li><li><p>Email categorization</p></li><li><p>Fraud detection</p></li><li><p>Predictive maintenance</p></li><li><p>Customer support chatbots</p></li></ul><p>Users increasingly expect applications to understand context instead of simply responding to button clicks.</p><p>The good news is that developers no longer need a PhD in machine learning to build these capabilities.</p><p>Many AI services are available through straightforward APIs that integrate naturally with ASP.NET Core. </p><h2>Artificial Intelligence vs Machine Learning</h2><p>These terms are often used interchangeably, but they describe different concepts.</p><p>Artificial Intelligence is the broader goal of building systems that perform tasks requiring human-like intelligence.</p><p>Machine Learning is one approach to achieving that goal.</p><p>Instead of programming every rule manually, machine learning models learn patterns from data.</p><p>Think of it this way.</p><p>Traditional software follows instructions.</p><p>Machine learning discovers instructions from examples.</p><h2>A Simple Example</h2><p>Suppose you&#8217;re building an email application.</p><p>Traditional code might look like this:</p><pre><code><code>If subject contains "Invoice"
Move to Finance folder.</code></code></pre><p>Machine learning instead examines thousands of previous emails and learns which messages belong together, even when the word &#8220;invoice&#8221; never appears. </p><h2>Where ASP.NET Core Fits</h2><p>ASP.NET Core rarely performs heavy AI training itself.</p><p>Instead, it acts as the application layer.</p><p>It:</p><ul><li><p>Receives requests </p></li><li><p>Sends prompts or data to AI services </p></li><li><p>Validates responses </p></li><li><p>Stores results </p></li><li><p>Serves users </p></li></ul><p>This separation keeps applications maintainable while allowing AI models to evolve independently. </p><h2>Common AI Integration Patterns</h2><p>Most ASP.NET Core applications use one of three approaches.</p><h2>Cloud AI APIs</h2><p>The application calls hosted AI services.</p><p>Examples include:</p><ul><li><p>Azure AI Foundry </p></li><li><p>Azure AI Vision </p></li><li><p>Azure AI Document Intelligence </p></li><li><p>Azure AI Speech </p></li><li><p>OpenAI API<br></p></li></ul><p>This is often the quickest way to introduce AI features.</p><h2>ML.NET Models</h2><p>Sometimes organizations prefer running prediction models locally.</p><p>ML.NET enables developers to train and execute machine learning models directly within .NET applications.</p><p><a href="https://learn.microsoft.com/dotnet/machine-learning/">Official documentation</a>:</p><p>This approach works well when:</p><ul><li><p>Internet connectivity is limited. </p></li><li><p>Sensitive information cannot leave the organization. </p></li><li><p>Predictions must happen with very low latency. <br></p></li></ul><h2>Hybrid Solutions</h2><p>Many enterprise applications combine both approaches.</p><p>For example:</p><ul><li><p>ML.NET performs fraud scoring. </p></li><li><p>Azure AI extracts document text. </p></li><li><p>OpenAI summarizes the results. </p></li></ul><p>Each tool performs the task it handles best. </p><h2>Using AI Through Dependency Injection</h2><p>AI clients fit naturally into ASP.NET Core.</p><p>Example:</p><pre><code><code>builder.Services.AddSingleton&lt;IAIService, AIService&gt;();</code></code></pre><p>Controllers remain focused on business logic while dedicated services manage AI interactions.</p><h2>Calling an AI Service</h2><p>Example:</p><pre><code><code>public class ProductController : ControllerBase
{
    private readonly IAIService _ai;

    public ProductController(IAIService ai)
    {
        _ai = ai;
    }

    [HttpPost("summarize")]
    public async Task&lt;string&gt; Summarize(string text)
    {
        return await _ai.SummarizeAsync(text);
    }
}</code></code></pre><p>Notice how the controller doesn&#8217;t know which AI provider is being used.</p><p>This abstraction makes future changes much easier.</p><h2>Practical Use Case: Intelligent Customer Support</h2><p>Suppose customers submit support requests.</p><p>Instead of manually categorizing tickets, AI can automatically determine:</p><ul><li><p>Billing issue </p></li><li><p>Technical problem </p></li><li><p>Shipping question </p></li><li><p>Feature request </p></li></ul><p>Support teams spend less time sorting requests and more time solving problems.</p><h2>Practical Use Case: Smart Search</h2><p>Traditional keyword searches often disappoint users.</p><p>AI-powered semantic search understands intent.</p><p>Searching for:</p><blockquote><p>&#8220;Laptop won&#8217;t charge&#8221;</p></blockquote><p>can return documents discussing:</p><ul><li><p>Power adapter failures </p></li><li><p>Battery problems </p></li><li><p>Charging ports </p></li></ul><p>even when those exact words never appear.</p><h2>Practical Use Case: Document Processing</h2><p>Many businesses process:</p><ul><li><p>Contracts </p></li><li><p>Invoices </p></li><li><p>Insurance claims </p></li><li><p>Medical forms </p></li></ul><p>Azure AI Document Intelligence can extract structured information automatically.</p><p>ASP.NET Core then validates, stores, and presents the results.</p><p>Manual data entry is dramatically reduced.</p><p><a href="https://learn.microsoft.com/azure/ai-services/document-intelligence/">Official documentation</a>:</p><h2>Practical Use Case: Chatbots</h2><p>Modern chatbots go far beyond scripted conversations.</p><p>Using large language models, chatbots can:</p><ul><li><p>Answer product questions </p></li><li><p>Explain documentation </p></li><li><p>Guide customers through troubleshooting </p></li><li><p>Escalate complex issues </p></li></ul><p>ASP.NET Core becomes the orchestration layer connecting users with AI services.</p><h2>Recommendation Engines</h2><p>Streaming platforms and online stores rely heavily on recommendations.</p><p>Machine learning analyzes:</p><ul><li><p>Purchase history </p></li><li><p>Viewing habits </p></li><li><p>User preferences<br></p></li></ul><p>Applications can then recommend relevant products automatically.</p><h2>Fraud Detection</h2><p>Financial systems constantly evaluate transactions.</p><p>Machine learning models examine:</p><ul><li><p>Transaction amounts </p></li><li><p>Purchase frequency </p></li><li><p>Device information </p></li><li><p>Geographic location </p></li></ul><p>Suspicious activity receives higher fraud scores.</p><p>Human investigators review only the highest-risk transactions.</p><h2>Image Recognition</h2><p>Applications can analyze uploaded images.</p><p>Examples include:</p><ul><li><p>Detecting damaged vehicles </p></li><li><p>Classifying products </p></li><li><p>Identifying plant diseases </p></li><li><p>Reading handwritten forms </p></li></ul><p>ASP.NET Core uploads the image while specialized AI services perform analysis.</p><h2>Voice Applications</h2><p>Speech services allow applications to:</p><ul><li><p>Convert speech into text. </p></li><li><p>Convert text into speech. </p></li><li><p>Translate conversations.<br></p></li></ul><p>Customer service systems increasingly combine speech recognition with language models.</p><h2>Responsible AI</h2><p>Adding AI introduces new responsibilities.</p><p>Developers should consider:</p><ul><li><p>Privacy </p></li><li><p>Security  </p></li><li><p>Bias </p></li><li><p>Transparency<br></p></li></ul><p>Users should understand when AI generates content or makes recommendations.</p><p>Human review remains important for high-impact decisions.</p><h2>Protecting Sensitive Data</h2><p>Never send unnecessary confidential information to AI services.</p><p>Before submitting requests:</p><ul><li><p>Remove sensitive identifiers. </p></li><li><p>Minimize personal data. </p></li><li><p>Encrypt communication. </p></li><li><p>Follow organizational compliance requirements.<br></p></li></ul><p>This connects directly with our earlier articles on Data Protection and Zero Trust Architecture.</p><h2>Observability</h2><p>AI systems should be monitored like every other service.</p><p>Track:</p><ul><li><p>Response time </p></li><li><p>Token usage </p></li><li><p>Error rates </p></li><li><p>Model latency </p></li><li><p>Request volume<br></p></li></ul><p>OpenTelemetry integrates well with AI-powered applications.</p><p>Observability helps teams understand cost as well as performance.</p><h2>Prompt Engineering</h2><p>Large language models respond according to the instructions they receive.</p><p>Well-designed prompts produce more reliable results.</p><p>Rather than asking:</p><blockquote><p>&#8220;Summarize this.&#8221;</p></blockquote><p>Consider:</p><blockquote><p>&#8220;Summarize this customer complaint in three bullet points highlighting the primary issue, urgency level, and recommended next action.&#8221;</p></blockquote><p>Clear prompts often improve output quality dramatically.</p><h2>Cost Management</h2><p>AI services usually charge per request or per token.</p><p>Applications should:</p><ul><li><p>Cache repeated responses. </p></li><li><p>Limit unnecessary requests. </p></li><li><p>Select appropriate model sizes. </p></li><li><p>Monitor usage.<br></p></li></ul><p>Good architecture reduces operational costs significantly.</p><h2>Real-World Example</h2><p>Imagine a global logistics company.</p><p>Customers upload shipping documents.</p><p>The workflow becomes:</p><ol><li><p>ASP.NET Core receives the document. </p></li><li><p><a href="https://www.asptoday.com/p/deploying-aspnet-core-applications">Azure AI</a> extracts structured information. </p></li><li><p>ML.NET predicts shipping delays. </p></li><li><p>A language model summarizes unusual issues. </p></li><li><p>Results appear inside the operations dashboard. </p></li></ol><p>No single AI model performs everything.</p><p>Each specializes in one task.</p><p>ASP.NET Core coordinates the entire workflow.</p><h2>How This Fits Your ASP.NET Core Journey</h2><p>Throughout this series we&#8217;ve explored:</p><ul><li><p>Microservices </p></li><li><p>API Gateways </p></li><li><p>Service Meshes </p></li><li><p>Distributed Tracing </p></li><li><p>Zero Trust Security </p></li><li><p>Internal Developer Platforms </p></li></ul><p>Artificial Intelligence becomes another service within this architecture.</p><p>Rather than replacing traditional software design, AI enhances existing systems by making them more intelligent, adaptive, and helpful.</p><h2>Closing Thoughts</h2><p>Artificial Intelligence is changing how users interact with software, but successful AI applications still rely on strong software engineering principles.</p><p>ASP.NET Core provides an excellent foundation for integrating AI services, managing workflows, securing sensitive data, and exposing intelligent capabilities through clean APIs.</p><p>Whether you&#8217;re building chatbots, recommendation engines, document processing systems, or predictive analytics platforms, treating AI as one component of a well-designed architecture leads to applications that are easier to maintain, easier to scale, and far more valuable to users.</p><p>The future of software isn&#8217;t simply AI-powered. It&#8217;s thoughtfully engineered systems where traditional software and intelligent services work together to solve real business problems.</p><h2>Subscribe Now</h2><p>Enjoying this series? Subscribe to <strong><a href="https://www.asptoday.com/">ASP Today</a></strong> for practical ASP.NET Core tutorials, cloud-native architecture guides, AI integration strategies, and enterprise development best practices. Join our Substack Chat to discuss modern .NET 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[Service Mesh Concepts for ASP.NET Core Developers: Traffic, Security, and Observability]]></title><description><![CDATA[Discover how service meshes simplify secure communication, traffic management, and observability for ASP.NET Core microservices. #ASPToday #aspnetcore #servicemesh #microservices #cloudnative]]></description><link>https://www.asptoday.com/p/service-mesh-concepts-for-aspnet</link><guid isPermaLink="false">https://www.asptoday.com/p/service-mesh-concepts-for-aspnet</guid><dc:creator><![CDATA[Jenny Muralidharan]]></dc:creator><pubDate>Tue, 28 Jul 2026 15:02:03 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Y83A!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f247eba-6a9a-4072-9d30-4e4a53a3a782_1672x941.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In our previous article, we explored how <a href="https://www.asptoday.com/p/implementing-api-gateways-in-aspnet">API Gateways</a> provide a single entry point for clients accessing your microservices. But what happens after a request enters your system? Once dozens or even hundreds of services begin communicating with one another, managing traffic, securing every connection, and understanding what is happening inside the network becomes a major challenge.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Y83A!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f247eba-6a9a-4072-9d30-4e4a53a3a782_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Y83A!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f247eba-6a9a-4072-9d30-4e4a53a3a782_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!Y83A!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f247eba-6a9a-4072-9d30-4e4a53a3a782_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!Y83A!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f247eba-6a9a-4072-9d30-4e4a53a3a782_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!Y83A!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f247eba-6a9a-4072-9d30-4e4a53a3a782_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Y83A!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f247eba-6a9a-4072-9d30-4e4a53a3a782_1672x941.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0f247eba-6a9a-4072-9d30-4e4a53a3a782_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;:2172579,&quot;alt&quot;:&quot;Engineers monitor a holographic service mesh controlling secure traffic between 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/208443421?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f247eba-6a9a-4072-9d30-4e4a53a3a782_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Engineers monitor a holographic service mesh controlling secure traffic between microservices. " title="Engineers monitor a holographic service mesh controlling secure traffic between microservices. " srcset="https://substackcdn.com/image/fetch/$s_!Y83A!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f247eba-6a9a-4072-9d30-4e4a53a3a782_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!Y83A!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f247eba-6a9a-4072-9d30-4e4a53a3a782_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!Y83A!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f247eba-6a9a-4072-9d30-4e4a53a3a782_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!Y83A!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f247eba-6a9a-4072-9d30-4e4a53a3a782_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 aria-hidden="true" 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><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>This is where a service mesh comes in. Rather than adding networking logic to every <a href="https://www.asptoday.com/">ASP.NET Core</a> application, a service mesh transparently manages communication between services, providing traffic control, encryption, security policies, and deep observability without requiring changes to your application code. </p><h2>Life Before a Service Mesh</h2><p>Imagine an online retail platform built with ASP.NET Core.</p><p>It contains separate services for:</p><ul><li><p>Products</p></li><li><p>Orders</p></li><li><p>Inventory</p></li><li><p>Payments</p></li><li><p>Shipping</p></li><li><p>Notifications</p></li><li><p>Customer Accounts</p></li></ul><p>The API Gateway successfully routes incoming requests.</p><p>However, after the request enters your platform, the services begin talking to one another.</p><p>An order service might contact inventory, payment, shipping, and notification services during a single customer purchase.</p><p>As the system grows, each service must handle:</p><ul><li><p>Secure communication</p></li><li><p><a href="https://www.asptoday.com/p/authentication-and-authorization">Authentication</a></p></li><li><p>Retries</p></li><li><p>Timeouts</p></li><li><p>Load balancing</p></li><li><p>Logging</p></li><li><p><a href="https://www.asptoday.com/p/deep-dive-into-distributed-tracing">Distributed tracing</a></p></li><li><p>Encryption</p></li><li><p>Circuit breakers</p></li></ul><p>Every development team ends up writing nearly identical networking code.</p><p>Maintaining that code across dozens of services quickly becomes difficult.</p><h2>What Is a Service Mesh?</h2><p>A service mesh is dedicated infrastructure that manages communication between your microservices.</p><p>Instead of placing networking logic inside every ASP.NET Core application, the mesh handles those responsibilities automatically.</p><p>Your application continues focusing on business logic while the service mesh manages communication behind the scenes.</p><p>Unlike an API Gateway, which handles traffic entering your platform, a service mesh focuses on traffic moving inside your platform.</p><h2>Understanding Sidecar Proxies</h2><p>The heart of a service mesh is the sidecar proxy.</p><p>Each ASP.NET Core service runs alongside its own lightweight proxy.</p><pre><code><code>Client
   &#9474;
   &#9660;
API Gateway
   &#9474;
   &#9660;
Order Service &#8592;&#8594; Sidecar Proxy
                     &#9474;
                     &#9660;
            Inventory Sidecar
                     &#9474;
                     &#9660;
           Inventory Service</code></code></pre><p>Every request passes through these proxies.</p><p>The application never communicates directly with another service.</p><p>Instead, the sidecars handle networking responsibilities.</p><h2>Why Sidecars Matter</h2><p>Because every request flows through a proxy, the service mesh can automatically provide:</p><ul><li><p>Mutual TLS encryption </p></li><li><p>Traffic routing </p></li><li><p>Load balancing </p></li><li><p>Retries </p></li><li><p>Timeouts </p></li><li><p>Observability </p></li><li><p><a href="https://www.asptoday.com/p/authentication-and-authorization">Authentication</a> </p></li><li><p>Authorization </p></li><li><p>Policy enforcement </p></li></ul><p>Your ASP.NET Core applications remain clean and focused.</p><h2>Traffic Management</h2><p>Suppose version two of your Product Service has just been deployed.</p><p>Instead of sending every request to the new version immediately, the mesh can gradually shift traffic.</p><p>For example:</p><ul><li><p>95% of requests continue using Version 1 </p></li><li><p>5% use Version 2</p></li></ul><p>If everything performs well, the percentage gradually increases.</p><p>This approach enables safer deployments.</p><h2>Canary Releases</h2><p>Canary deployments become extremely simple.</p><p>Rather than modifying application code, the service mesh controls how traffic flows between service versions.</p><p>If problems appear, traffic can immediately return to the previous version.</p><p>No code changes are required.</p><h2>Load Balancing</h2><p>Suppose your Inventory Service now runs on six different containers.</p><p>The service mesh distributes traffic automatically across healthy instances.</p><p>Applications simply call Inventory Service.</p><p>The mesh decides which instance should receive the request.</p><h2>Automatic Retries</h2><p>Temporary failures happen frequently.</p><p>A container may restart.</p><p>A network connection might briefly fail.</p><p>Instead of every ASP.NET Core application implementing retry logic independently, the mesh retries failed requests automatically according to configurable policies.</p><h2>Timeouts</h2><p>Long-running requests can consume valuable resources.</p><p>The mesh applies timeout policies before failures spread throughout the system.</p><p>Applications don&#8217;t need to implement timeout logic repeatedly.</p><h2>Circuit Breaking</h2><p>We previously discussed circuit breakers.</p><p>Service meshes implement them automatically.</p><p>When a downstream service becomes unhealthy, the mesh temporarily stops forwarding requests.</p><p>This prevents cascading failures across your platform.</p><h2>Security Between Services</h2><p>One of the biggest advantages of a service mesh is security.</p><p>Without a mesh, developers often need to configure secure communication manually.</p><p>With a mesh, secure communication becomes the default.</p><h2>Mutual TLS</h2><p>Most developers are familiar with HTTPS protecting communication between browsers and servers.</p><p>Inside a microservices platform, every service should also verify the identity of every other service.</p><p>Service meshes accomplish this using Mutual TLS (mTLS).</p><p>Every connection becomes:</p><ul><li><p>Authenticated </p></li><li><p>Encrypted </p></li><li><p>Verified </p></li></ul><p>No application code changes are required.</p><h2>Identity Instead of IP Addresses</h2><p>Traditional systems often trust network locations.</p><p>Modern service meshes trust service identities instead.</p><p>Even if containers move between servers, identities remain consistent.</p><p>This aligns closely with the Zero Trust principles discussed in earlier articles.</p><h2>Policy Enforcement</h2><p>Administrators can define rules such as:</p><ul><li><p>Payment Service can call Inventory Service. </p></li><li><p>Notification Service cannot call Payment Service. </p></li><li><p>Reporting Service has read-only access. </p></li></ul><p>Policies are managed centrally rather than inside dozens of applications.</p><h2>Observability</h2><p>Understanding distributed systems is difficult.</p><p>One customer request may travel through fifteen services.</p><p>Finding performance problems without visibility becomes almost impossible.</p><p>Service meshes automatically collect <a href="https://www.asptoday.com/p/deep-dive-into-distributed-tracing">telemetry</a>.</p><h2>Metrics</h2><p>Without changing your code, the mesh records:</p><ul><li><p>Request count </p></li><li><p>Latency </p></li><li><p>Success rate </p></li><li><p>Error rate </p></li><li><p>Response times </p></li></ul><p>These metrics help teams understand application health.</p><h2>Distributed Tracing</h2><p>Each request receives a trace identifier.</p><p>As requests move between services, the mesh records every step.</p><p>Developers can visualize exactly where delays occur.</p><p>This works beautifully alongside OpenTelemetry, which we explored in our previous observability article.</p><h2>Logging</h2><p>Because every request passes through sidecars, the mesh automatically records communication between services.</p><p>This simplifies troubleshooting without requiring custom logging logic in every application.</p><h2>Popular Service Mesh Technologies</h2><p>Several mature service mesh platforms are widely used.</p><p>The most common include:</p><ul><li><p>Istio </p></li><li><p>Linkerd </p></li><li><p>Kuma </p></li><li><p>Consul Connect </p></li></ul><p>Most integrate naturally with <a href="https://www.asptoday.com/p/aspnet-core-and-kubernetes-from-development">Kubernetes</a>, where ASP.NET Core applications are commonly deployed. </p><h2>Does ASP.NET Core Need Special Code?</h2><p>One of the biggest advantages is that your application usually requires very little modification. </p><p>Your ASP.NET Core services continue communicating using standard HTTP or gRPC.</p><p>The mesh intercepts traffic transparently.</p><p>Business logic remains completely separate from networking infrastructure.</p><h2>API Gateway vs Service Mesh</h2><p>These technologies complement one another rather than compete.</p><p>API Gateway responsibilities include:</p><ul><li><p>Client authentication </p></li><li><p>External routing </p></li><li><p>Rate limiting </p></li><li><p>Public APIs </p></li></ul><p>Service Mesh responsibilities include:</p><ul><li><p>Internal <a href="https://www.asptoday.com/p/routing-in-aspnet-core-beginners-guide">routing</a> </p></li><li><p>Service authentication </p></li><li><p>Mutual TLS </p></li><li><p>Retries </p></li><li><p>Traffic shaping </p></li><li><p>Observability </p></li><li><p>Policy enforcement </p></li></ul><p>Many modern cloud-native systems use both together.</p><h2>Common Mistakes</h2><p>Avoid expecting the service mesh to replace good application design.</p><p>A mesh improves networking, but it does not replace:</p><ul><li><p>Clean architecture </p></li><li><p>Domain boundaries </p></li><li><p>Reliable code </p></li><li><p>Proper error handling </p></li></ul><p>It should enhance your platform, not compensate for poor software design.</p><h2>Real-World Example</h2><p>Imagine a global airline booking platform.</p><p>A customer books a flight.</p><p>The API Gateway accepts the request.</p><p>Behind the scenes:</p><ul><li><p>Booking Service contacts Inventory. </p></li><li><p>Inventory contacts Pricing. </p></li><li><p>Pricing contacts Loyalty. </p></li><li><p>Booking contacts Payments. </p></li><li><p>Payments contacts Notifications. </p></li></ul><p>The customer sees a single confirmation screen.</p><p>Meanwhile, the service mesh securely manages dozens of encrypted service-to-service conversations, balances traffic, retries temporary failures, collects telemetry, and records distributed traces without requiring each application to implement those capabilities individually.</p><h2>How This Fits Your ASP.NET Core Journey</h2><p>So far, we&#8217;ve explored:</p><ul><li><p>API Gateways </p></li><li><p>Zero Trust Security </p></li><li><p>Distributed Tracing </p></li><li><p><a href="https://www.asptoday.com/p/chaos-engineering-for-aspnet-core">Chaos Engineering</a> </p></li><li><p>Internal Developer Platforms </p></li><li><p>Advanced Authorization </p></li></ul><p>Service meshes tie these concepts together by providing a secure, observable communication layer for modern microservices.</p><h2>Closing Thoughts</h2><p>As ASP.NET Core applications continue evolving toward cloud-native architectures, reliable communication between services becomes just as important as the services themselves.</p><p>A service mesh removes much of the complexity traditionally embedded inside application code. By managing traffic, encryption, retries, authentication, and observability at the infrastructure layer, it allows development teams to focus on building business features instead of repeatedly solving networking challenges.</p><p>Whether you&#8217;re deploying a handful of services or operating hundreds across Kubernetes clusters, understanding service mesh concepts will help you build systems that are more secure, more resilient, and significantly easier to operate.</p><h2>Subscribe Now</h2><p>If you&#8217;re enjoying this cloud-native architecture series, subscribe to <strong><a href="https://www.asptoday.com/">ASP Today</a></strong> for practical ASP.NET Core tutorials, real-world architecture guides, and enterprise development best practices. Join our Substack Chat to discuss modern .NET development with developers building scalable applications 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[Implementing API Gateways in ASP.NET Core: Routing, Aggregation, and Security]]></title><description><![CDATA[Discover how API Gateways simplify routing, strengthen security, and improve performance in ASP.NET Core microservices. #ASPToday #aspnetcore #microservices #apigateway #dotnet]]></description><link>https://www.asptoday.com/p/implementing-api-gateways-in-aspnet</link><guid isPermaLink="false">https://www.asptoday.com/p/implementing-api-gateways-in-aspnet</guid><dc:creator><![CDATA[Jenny Muralidharan]]></dc:creator><pubDate>Tue, 21 Jul 2026 15:01:41 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!UJ-K!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bc91857-e5af-4787-8d99-2a2f8e2538f9_1672x941.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As applications evolve into dozens of independent microservices, managing communication between clients and services becomes increasingly complex. Without a central entry point, clients often need to know where every service lives, how each service authenticates requests, and which endpoints to call. API Gateways solve this problem by acting as a single front door for your 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_!UJ-K!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bc91857-e5af-4787-8d99-2a2f8e2538f9_1672x941.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UJ-K!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bc91857-e5af-4787-8d99-2a2f8e2538f9_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!UJ-K!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bc91857-e5af-4787-8d99-2a2f8e2538f9_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!UJ-K!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bc91857-e5af-4787-8d99-2a2f8e2538f9_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!UJ-K!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bc91857-e5af-4787-8d99-2a2f8e2538f9_1672x941.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UJ-K!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bc91857-e5af-4787-8d99-2a2f8e2538f9_1672x941.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5bc91857-e5af-4787-8d99-2a2f8e2538f9_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;:2191359,&quot;alt&quot;:&quot;Shoppers gather at a central mall information desk guiding visitors to stores across the complex. &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/207617722?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bc91857-e5af-4787-8d99-2a2f8e2538f9_1672x941.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Shoppers gather at a central mall information desk guiding visitors to stores across the complex. " title="Shoppers gather at a central mall information desk guiding visitors to stores across the complex. " srcset="https://substackcdn.com/image/fetch/$s_!UJ-K!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bc91857-e5af-4787-8d99-2a2f8e2538f9_1672x941.png 424w, https://substackcdn.com/image/fetch/$s_!UJ-K!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bc91857-e5af-4787-8d99-2a2f8e2538f9_1672x941.png 848w, https://substackcdn.com/image/fetch/$s_!UJ-K!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bc91857-e5af-4787-8d99-2a2f8e2538f9_1672x941.png 1272w, https://substackcdn.com/image/fetch/$s_!UJ-K!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5bc91857-e5af-4787-8d99-2a2f8e2538f9_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 aria-hidden="true" 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><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 API Gateways work, how to implement them using <a href="https://www.asptoday.com/p/introduction-to-aspnet-core-whats-new-and-why-it-matters">ASP.NET Core</a>, and how they improve routing, security, scalability, and developer experience in modern distributed systems. </p><h2>Why Microservices Need a Front Door</h2><p>Imagine an online shopping application built using microservices.</p><p>Instead of one application, you now have:</p><ul><li><p>Product Service</p></li><li><p>Orders Service</p></li><li><p>Inventory Service</p></li><li><p>Payment Service</p></li><li><p>Shipping Service</p></li><li><p>Notification Service</p></li></ul><p>If a mobile application communicates directly with every service, several problems appear.</p><p>The client must know:</p><ul><li><p>Where each service lives</p></li><li><p>Which URLs to call</p></li><li><p>Which authentication tokens are required</p></li><li><p>Which service owns which data</p></li></ul><p>Every new microservice increases complexity.</p><p>This quickly becomes difficult to maintain. </p><h2>What Is an API Gateway?</h2><p>An API Gateway sits between clients and your microservices.</p><p>Instead of calling services directly, every request goes through the gateway.</p><pre><code><code>Mobile App
Web App
Desktop App
        &#9474;
        &#9660;
   API Gateway
        &#9474;
 &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9532;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
 &#9660;      &#9660;        &#9660;
Orders Products Payments</code></code></pre><p>The gateway becomes the single public entry point.</p><p>Clients only need to know one address. </p><h2>Why API Gateways Matter</h2><p>API Gateways simplify communication.</p><p>Instead of exposing every internal service, the gateway handles:</p><ul><li><p>Routing</p></li><li><p>Authentication</p></li><li><p>Authorization</p></li><li><p>Rate limiting</p></li><li><p>Request aggregation</p></li><li><p>Logging</p></li><li><p>Monitoring</p></li><li><p>Response transformation</p></li></ul><p>Microservices remain focused on business logic.</p><p>The gateway handles cross-cutting concerns. </p><h2>Understanding Request Routing</h2><p>The primary responsibility of an API Gateway is routing.</p><p>For example:</p><pre><code><code>/api/products
        &#9474;
        &#9660;
Product Service

/api/orders
        &#9474;
        &#9660;
Orders Service

/api/payments
        &#9474;
        &#9660;
Payment Service</code></code></pre><p>Clients don&#8217;t need to know where services are hosted.</p><p>The gateway forwards requests automatically. </p><h2>Building an API Gateway with ASP.NET Core</h2><p>ASP.NET Core provides several options.</p><p>One of the most popular is <strong>YARP (Yet Another Reverse Proxy)</strong>, developed by Microsoft.</p><p><a href="https://microsoft.github.io/reverse-proxy/">YARP documentation</a></p><p>Install:</p><pre><code><code>dotnet add package Yarp.ReverseProxy</code></code></pre><h2>Configuring YARP</h2><p>Register the reverse proxy.</p><pre><code><code>builder.Services
    .AddReverseProxy()
    .LoadFromConfig(
        builder.Configuration
    .GetSection("ReverseProxy"));</code></code></pre><p>Then enable it.</p><pre><code><code>app.MapReverseProxy();</code></code></pre><p>Your gateway is now ready to forward requests. </p><h2>Configuring Routes</h2><p>Example configuration:</p><pre><code><code>{
  "ReverseProxy": {
    "Routes": {
      "products": {
        "ClusterId": "products",
        "Match": {
          "Path": "/products/{**catch-all}"
        }
      }
    }
  }
}</code></code></pre><p>Clients continue calling one API while YARP forwards traffic to the correct service. </p><h2>Request Aggregation</h2><p>Sometimes a client needs data from multiple services.</p><p>Imagine loading a product page.</p><p>The application needs:</p><ul><li><p>Product details</p></li><li><p>Inventory</p></li><li><p>Reviews</p></li><li><p>Pricing</p></li></ul><p>Without a gateway:</p><p>The browser makes four separate requests.</p><p>With aggregation:</p><p>The browser makes one request.</p><p>The gateway gathers information from multiple services before returning a single response.</p><p>This reduces network traffic and improves performance. </p><h2>Example Aggregation Flow</h2><pre><code><code>Client
   &#9474;
   &#9660;
Gateway
 &#9500;&#9472;&#9472; Product Service
 &#9500;&#9472;&#9472; Inventory Service
 &#9500;&#9472;&#9472; Review Service
 &#9492;&#9472;&#9472; Pricing Service
        &#9474;
        &#9660;
 Combined Response</code></code></pre><p>The client receives one complete response. </p><h2>Authentication at the Gateway</h2><p>One of the biggest advantages of an API Gateway is centralized authentication.</p><p>Instead of every service validating JWT tokens independently, the gateway performs the initial authentication.</p><p>Example:</p><pre><code><code>builder.Services
.AddAuthentication()
.AddJwtBearer();</code></code></pre><p>Only authenticated requests reach internal services.</p><p>This simplifies service development.</p><h2>Authorization</h2><p>Authentication identifies users.</p><p>Authorization determines what they can access.</p><p>Policy-based authorization works well inside gateways.</p><p>Example:</p><pre><code><code>[Authorize(Policy = "Orders.Read")]</code></code></pre><p>Unauthorized requests are rejected before reaching downstream services. </p><h2>Rate Limiting</h2><p>Gateways are an ideal location for rate limiting.</p><p>ASP.NET Core includes built-in support.</p><p>Example:</p><pre><code><code>builder.Services.AddRateLimiter(options =&gt;
{
    options.AddFixedWindowLimiter(
        "api",
        limiter =&gt;
        {
            limiter.PermitLimit = 100;
            limiter.Window =
                TimeSpan.FromMinutes(1);
        });
});</code></code></pre><p>Rate limiting protects services from abuse. </p><h2>Response Caching</h2><p>Frequently requested data can be cached.</p><p>Instead of contacting the Product Service repeatedly, the gateway serves cached responses.</p><p>Benefits include:</p><ul><li><p>Faster responses</p></li><li><p>Reduced backend load</p></li><li><p>Lower infrastructure costs</p></li></ul><p>Caching should be applied carefully to avoid serving stale data. </p><h2>Load Balancing</h2><p>A Product Service may run multiple instances.</p><pre><code><code>Gateway
   &#9474;
 &#9484;&#9472;&#9532;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
 &#9660; &#9660; &#9660;
Instance A
Instance B
Instance C</code></code></pre><p>The gateway distributes traffic across available instances.</p><p>Users experience higher availability without knowing multiple servers exist. </p><h2>Service Discovery</h2><p>Microservices often change location.</p><p>Containers restart.</p><p>Pods move.</p><p>New instances appear.</p><p>Instead of hardcoding addresses, the gateway integrates with service discovery solutions.</p><p>Examples include:</p><ul><li><p>Kubernetes</p></li><li><p>Consul</p></li><li><p>Azure Container Apps</p></li></ul><p>Requests always reach healthy instances. </p><h2>Circuit Breakers</h2><p>If a downstream service fails, the gateway should avoid repeatedly sending requests.</p><p>Circuit breakers temporarily stop traffic to unhealthy services.</p><p>This improves overall system stability.</p><p>It also complements the resilience strategies we explored in previous articles. </p><h2>Logging Requests</h2><p>Every request passes through the gateway.</p><p>This makes it an excellent place to capture:</p><ul><li><p>Request paths</p></li><li><p>Response codes</p></li><li><p>Processing times</p></li><li><p>Client IP addresses</p></li></ul><p>These logs become valuable for troubleshooting. </p><h2>Distributed Tracing</h2><p>The gateway also plays an important role in observability.</p><p>Using OpenTelemetry, it can:</p><ul><li><p>Start trace spans</p></li><li><p>Propagate trace context</p></li><li><p>Record latency</p></li><li><p>Capture downstream service performance</p></li></ul><p>This connects directly with our earlier article on distributed tracing. </p><h2>API Versioning</h2><p>Gateways simplify API evolution.</p><p>Clients may continue using:</p><pre><code><code>/api/v1/orders</code></code></pre><p>While newer clients use:</p><pre><code><code>/api/v2/orders</code></code></pre><p>The gateway routes requests appropriately without exposing internal service complexity. </p><h2>Security Benefits</h2><p>The gateway strengthens security by:</p><ul><li><p>Hiding internal services</p></li><li><p>Centralizing authentication</p></li><li><p>Applying authorization policies</p></li><li><p>Enforcing HTTPS</p></li><li><p>Performing request validation</p></li><li><p>Blocking malicious traffic</p></li></ul><p>Internal services remain inaccessible from the public internet. </p><h2>Common Mistakes</h2><p>Avoid making the gateway responsible for business logic.</p><p>The gateway should coordinate requests.</p><p>Business decisions belong inside services.</p><p>Also avoid:</p><ul><li><p>Creating a single massive gateway</p></li><li><p>Hardcoding routes</p></li><li><p>Ignoring monitoring</p></li><li><p>Forgetting rate limiting</p></li><li><p>Bypassing authentication</p></li></ul><p>A lightweight gateway is easier to maintain. </p><h2>Real-World Example</h2><p>Imagine an airline booking platform.</p><p>Customers use one mobile app.</p><p>Behind the scenes the gateway communicates with:</p><ul><li><p>Flight Service</p></li><li><p>Booking Service</p></li><li><p>Payment Service</p></li><li><p>Loyalty Service</p></li><li><p>Notification Service</p></li></ul><p>The mobile app makes a single request.</p><p>The gateway handles routing, authentication, aggregation, and response formatting.</p><p>The customer experiences one seamless interaction despite multiple backend services working together. </p><h2>How This Fits Your ASP.NET Core Journey</h2><p>Our recent articles explored:</p><ul><li><p>Microservices</p></li><li><p>Zero Trust Architecture</p></li><li><p>Data Protection</p></li><li><p>Advanced Authorization</p></li><li><p>Internal Developer Platforms</p></li></ul><p>API Gateways bring these ideas together.</p><p>They become the secure front door through which every request enters your distributed system.</p><p>They enforce security, simplify communication, improve observability, and create a better experience for both developers and users. </p><h2>Closing Thoughts</h2><p>As applications continue moving toward microservices, API Gateways become an essential architectural component.</p><p>They reduce client complexity, centralize security, simplify routing, and provide a single location for cross-cutting concerns such as authentication, logging, caching, and monitoring.</p><p>ASP.NET Core, together with Microsoft&#8217;s YARP project, provides an excellent foundation for building lightweight, high-performance API Gateways that scale with modern cloud-native applications.</p><p>Rather than exposing dozens of services directly, let your gateway become the intelligent front door that protects, organizes, and simplifies your entire platform. </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 tutorials, cloud-native architecture guides, and enterprise development best practices. Join the Substack Chat to discuss modern ASP.NET Core development with developers building scalable applications 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><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><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 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 aria-hidden="true" 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><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 aria-hidden="true" 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><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 aria-hidden="true" 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><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 aria-hidden="true" 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><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 aria-hidden="true" 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><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 aria-hidden="true" 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><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 aria-hidden="true" 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><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 aria-hidden="true" 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><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 aria-hidden="true" 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><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 aria-hidden="true" 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><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 aria-hidden="true" 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><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 aria-hidden="true" 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><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 aria-hidden="true" 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><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 aria-hidden="true" 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><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></channel></rss>