Wednesday, 26 August 2026

Deferring Slow, Fallible Work Without Losing Visibility

The problem

A user action triggers a second piece of work that is slow, fans out across many calls to an external system, and can partially fail. Done inline, it looks like this:

public async Task<Result> Handle(SetReadyCommand command, CancellationToken stopToken)
{

    var entity = await LoadAsync(command.EntityId, stopToken);

    entity.SetReady();

    await dbContext.SaveChangesWithResultAsync(stopToken);

    // Slow, fans out, and fails per-item. But it's in the same request, so a failure

    // here either rolls back or taints a transition that had legitimately succeeded.

    foreach (var item in entity.Items)
    {
        await refreshService.RefreshAsync(item, stopToken);
    }

    return Result.Success();
}


The first is latency. The work takes long enough that holding the HTTP request open for it is unacceptable, and it scales with the size of the thing being operated on, so there's no safe upper bound.


The second, and more important one, is coupling. The secondary work is best-effort: if it fails, the system is still in a valid state, the previous data remains usable, and the user's primary action should stand. But if it runs inside the same request, a failure in the secondary work either rolls back or taints the primary action that had otherwise succeeded. You can't express "this part is allowed to fail" while it shares a transaction and a response code with a part that isn't.


So the work has to leave the request thread. The moment it does, you inherit a third problem: the user no longer finds out what happened. A fire-and-forget background task that silently fails is worse than a slow request.

The solution

Three pieces, and the third is the one people skip.

The triggering request validates, does the part that must be transactional, persists it, and returns — 202 Accepted if the whole request is deferred work, or a normal success response if the deferred work is a follow-on from something that did complete. Before returning, it hands a work item to an in-process queue.

The triggering handler does the transactional part, hands the rest to a queue, and returns:

var request = new WorkRequest(
    command.EntityId,
    itemIds,
    new WorkActor(currentUser.Username,
         currentUser.UserId,
         currentUser.Name),
    currentDateTime.UtcNow);

if (!queue.TryWrite(request))
{
    return Result.Failure(Errors.AlreadyInProgress(command.EntityId));
}

return Result.Success();   // the endpoint maps this to 202 Accepted


A single background worker drains the queue, one item at a time, each in its own dependency-injection scope so every pass gets fresh scoped dependencies — a database context in particular, since the long-lived worker must not share one.

Each pass writes its own progress and outcome to a status record on the entity being worked on, and the read endpoint for that entity exposes it. That record is the contract with the client: it moves to "requested" when the pass starts and settles on a terminal outcome that distinguishes complete success, partial success, and total failure, with enough per-item detail to say what didn't work. The client polls the thing it was already going to read.

Why an in-process queue rather than a message broker
Durability costs infrastructure, configuration, and a poison-message story, so it should be bought only when losing the work actually matters.

The queue is a channel plus a dictionary of entities that already have work in flight:

internal sealed class WorkQueue
{
    private readonly ConcurrentDictionary<long, byte> _active = new();

    private readonly Channel<WorkRequest> _channel =
        Channel.CreateUnbounded<WorkRequest>(new UnboundedChannelOptions { SingleReader = true });

    public bool TryWrite(WorkRequest request)
    {
        // Claim the entity before queueing: this is a per-entity lock, not just a buffer.
        if (!_active.TryAdd(request.EntityId, 0))
        {
            return false;
        }

        if (_channel.Writer.TryWrite(request))
        {
            return true;
        }

        _active.TryRemove(request.EntityId, out _);
        return false;
    }

    public void Complete(long entityId) => _active.TryRemove(entityId, out _);

    public IAsyncEnumerable<WorkRequest> ReadAllAsync(CancellationToken stopToken) =>

        _channel.Reader.ReadAllAsync(stopToken);
    public bool TryRead(out WorkRequest? request) => _channel.Reader.TryRead(out request);
}


Here it doesn't. The work is regenerable: if a pass never runs, the previous state remains valid and the user can trigger it again. Nothing is lost that can't be recreated by pressing the button a second time. That's the test — if the answer had been "a failed pass leaves the system inconsistent" or "this is a write we're obliged to make," it would need a durable outbox or a broker instead.

The in-memory option is a bounded channel abstraction with a single reader, which gives a lock-free producer/consumer handoff without writing locking code.

The design decisions that mattered

Per-key exclusion, not just deduplication. A concurrent dictionary keyed by entity id is checked before the queue write. This is not about avoiding redundant work — it's that two overlapping passes over the same entity race on the same rows and the same status record, and the second silently overwrites the first's result. A rejected enqueue returns a clean false, which the caller turns into a conflict response rather than accepting a request it won't honour. A useful side effect is that the queue is bounded in practice by the number of distinct entities, so "unbounded" is safe.


Carrying the caller's identity across the thread boundary. Background work has no request context, so the ambient "current user" service isn't resolvable in the worker's scope. The identity must be captured on the request thread and travel with the work item. This is easy to miss and shows up later as background writes attributed to nobody.


Writing the status record must not be blocked by unrelated changes. Originally the status was saved through a tracked entity, which put unrelated columns into the concurrency check. If anything else changed on that entity while the pass ran, the final write failed and the record was left on "requested" permanently — a client polling forever. The fix was a set-only update touching exactly the status column. The general lesson: a progress record has different concurrency requirements from the data it describes, and shouldn't share an optimistic-concurrency token with it.


Every exit path must end the poll. Three of them exist and all three need handling. An unexpected exception during a pass writes a failed outcome and leaves the worker alive rather than killing it. Shutdown cancelling a pass mid-flight writes a failed outcome, but only if the record is still "requested," so it doesn't overwrite a result that already landed. Shutdown with items still queued drains them and fails each one. The releasing of the per-entity lock sits in a finally, so a crashed pass can't lock an entity out of all future work.

Re-check preconditions between units of work. The entity's state can change while a long pass is running. Checking before each item means the pass can stop cleanly, keep what it already did, and report the rest as not done — rather than finishing against stale assumptions.

Monday, 7 July 2025

Quick Look: Controlling Concurrency with SemaphoreSlim in C#

When building high-throughput or asynchronous applications in .NET, it is easy to overwhelm external resources—like hitting an external API rate limit or exhausting database connection pools—by running too many tasks at once.

While lock or Monitor limits access to a single thread at a time, C# provides SemaphoreSlim when you need to allow a controlled number of concurrent tasks to access a resource.


What Problem Does SemaphoreSlim Solve?

Imagine you have 1,000 items to process, and each item requires calling a third-party REST API. If you run them all concurrently using Task.WhenAll, you might fire 1,000 simultaneous HTTP requests, leading to:

  • HTTP 429 Too Many Requests or rate-limiting errors.
  • Socket exhaustion or high resource contention.
  • Database thread-pool starvation under heavy loads.

SemaphoreSlim acts as a gatekeeper with a fixed capacity. If you set its capacity to 3, it allows up to 3 tasks to run concurrently. A 4th task must wait in line until one of the first three completes and releases its slot.

Why SemaphoreSlim over Semaphore? 

SemaphoreSlim is a lightweight, non-OS-level sync primitive optimized for speed within a single process. Crucially, it supports WaitAsync(), making it fully non-blocking and ideal for modern async/await code.

 

The Code Example

Here is a practical pattern for throttling concurrent asynchronous operations:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

public class ApiBatchProcessor
{
    // Limit concurrency to a maximum of 3 tasks at any given time
    private static readonly SemaphoreSlim Throttler = new SemaphoreSlim(initialCount: 3, maxCount: 3);

    public async Task ProcessItemsAsync(IEnumerable<int> itemIds)
    {
        var tasks = itemIds.Select(id => ProcessSingleItemAsync(id));
        await Task.WhenAll(tasks);
    }

    private async Task<int> ProcessSingleItemAsync(int id)
    {
        // Asynchronously wait to enter the semaphore without blocking the thread
        await Throttler.WaitAsync();

        try
        {
            Console.WriteLine($"[Processing] Item {id} started on Thread {Environment.CurrentManagedThreadId}");
            
            // Simulate an expensive API call or database query
            await Task.Delay(1000); 

            return id;
        }
        finally
        {
            // ALWAYS release the semaphore in a finally block to prevent deadlocks
            Throttler.Release();
        }
    }
}

Key Takeaways & Best Practices

  1. Always Use try...finally for Release():
Always call Release() inside a finally block. If the task throws an unhandled exception before releasing the semaphore, that slot is lost forever, leading to an eventual application deadlock.

 

      2. Non-Blocking with WaitAsync():

Never call Wait() inside an async method—use await WaitAsync(). Wait() blocks the underlying thread pool thread while waiting, whereas WaitAsync() frees the thread to do other work until a slot opens up.

 

      3.  Configuring Limits: 

The constructor SemaphoreSlim(initialCount, maxCount) defines how many slots are initially available and the absolute maximum allowed limit. Typically, both values are set to the same number (e.g., new SemaphoreSlim(5, 5)).

Saturday, 5 July 2025

Quick Look: Clean In-Memory Caching Wrapper in .NET

Caching expensive operations—like database queries or external API calls—is one of the easiest ways to improve performance in .NET applications. While IMemoryCache from Microsoft.Extensions.Caching.Memory is easy to use out of the box, boilerplate setup often litters repository and service classes.

Here is a quick look at a simple, reusable CacheService wrapper that leverages C# primary constructors and attributes to make caching clean and declarative.

The Code

using System.Runtime.CompilerServices;
using Microsoft.Extensions.Caching.Memory;

namespace UWAuth.Services;

/// <summary>
/// Implements a service for caching data using <see cref="IMemoryCache"/>.
/// </summary>
public class CacheService(IMemoryCache cache)
{
    private const int CacheDuration = 5;

    /// <summary>
    /// Gets the cached data for the specified key or fetches it using the provided fetch task if not cached.
    /// </summary>
    /// <typeparam name="T">The type of the data to cache.</typeparam>
    /// <param name="fetchTask">The task to fetch the data if not cached.</param>
    /// <param name="customCacheDuration">The custom cache duration in minutes. Default is 5 minutes.</param>
    /// <param name="key">The cache key. Default is the caller member name.</param>
    /// <returns>The cached or fetched data.</returns>
    /// <exception cref="InvalidOperationException">Thrown if the cached data cannot be cast or deserialized.</exception>
    public async Task<T> GetOrFetch<T>(
        Func<Task<T>> fetchTask, 
        int customCacheDuration = CacheDuration, 
        [CallerMemberName] string key = "") 
        where T : class?
    {
        if (cache.TryGetValue(key, out var cached))
        {
            return cached as T ?? throw new InvalidOperationException($"Failed to deserialize cached items for {key}");
        }

        var items = await fetchTask();
        if (items is null)
        {
            return null!;
        }

        cache.Set(key, items, TimeSpan.FromMinutes(customCacheDuration));
        return items;
    }

    /// <summary>
    /// Invalidates the cached data for the specified key.
    /// </summary>
    /// <param name="key">The cache key.</param>
    /// <void>Invalidates the cache entry.</void>
    public void Invalidate(string key) => cache.Remove(key);
}

Key Highlights & Features

  1. Modern C# Primary Constructor Syntax: By defining public class CacheService(IMemoryCache cache), the IMemoryCache dependency is injected directly into the class header—eliminating private field declarations and boilerplate constructor assignments.

  2. Implicit Cache Keys via [CallerMemberName]: Instead of manually defining string keys like "GetUserProfile", the [CallerMemberName] attribute automatically captures the name of the calling method or property. If called inside GetUserInfo(), the key automatically becomes "GetUserInfo".

  3. Get-or-Fetch Pattern: The GetOrFetch<T> method handles the standard checking pattern:

    • Cache Hit: Checks TryGetValue. If found, safely casts and returns the cached object.

    • Cache Miss: Runs the fetchTask delegate, saves the result in IMemoryCache using TimeSpan.FromMinutes(), and returns the fresh data.

  4. Invalidation: Includes a clean Invalidate(string key) helper for manual cache purging when underlying data is updated or deleted.

How to Use It in Your Code

After registering IMemoryCache and CacheService in your DI container (builder.Services.AddMemoryCache()), you can inject CacheService anywhere:

public class UserService(CacheService cacheService, IUserDbContext dbContext)
{
    public async Task<List<User>?> GetActiveUsers()
    {
        // Cache key automatically defaults to "GetActiveUsers" via [CallerMemberName]
        return await cacheService.GetOrFetch(async () => 
        {
            return await dbContext.Users.Where(u => u.IsActive).ToListAsync();
        }, customCacheDuration: 10);
    }

    public void PurgeActiveUserCache()
    {
        cacheService.Invalidate(nameof(GetActiveUsers));
    }
}

Sunday, 1 September 2019

Working with AWS Parameter Store in .NET

Using from code

Before starting, I installed the AWS CLI. When trying to figure out how to pass credentials into the AWS SDK, I stumbled upon the SDK's local credential discovery chain.

Among other options, the SDK automatically checks for local configuration files created in your user directory when you run aws configure via the CLI. It handles authentication seamlessly behind the scenes—a handy trick for local development!

Setting Up the Project & Package

I created a new ASP.NET Core MVC project and added the official AWS configuration package via NuGet:


Then I created a simple “Settings” class that holds the configuration values we’ll get back from AWS.
public class Settings
{
   public string awsvalue { get; set; }
   public string awsvalue2 { get; set; }
}
In the appsettings.json file, I told my app which AWS region to use.
{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "AllowedHosts": "*",
  "AWS": {
    "Profile": "default",
    "Region": "us-west-2"
  }
}
In Program.cs, I updated the web host builder to hook into Systems Manager as a configuration provider. Here, I'm pulling all settings stored under the /seroterdemo path:
public class Program
{
  public static void Main(string[] args)
  {
     CreateWebHostBuilder(args).Build().Run();
  }

  public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
     WebHost.CreateDefaultBuilder(args)
       .ConfigureAppConfiguration(builder =>
       {
          builder.AddSystemsManager("/seroterdemo");
       })
       .UseStartup<Startup>();
    }

Binding and Accessing Options

To make these values available to the application, I bound the configuration section to my Settings POCO in Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<Settings>(Configuration.GetSection("properties"));

    services.Configure<CookiePolicyOptions>(options =>
    {
       options.CheckConsentNeeded = context => true;
       options.MinimumSameSitePolicy = SameSiteMode.None;
     });
}
Finally, I injected IOptionsSnapshot<Settings> into the controller constructor. Using IOptionsSnapshot ensures that when the background reload timer fetches updated parameters from AWS, the controller automatically picks up the latest values on the next request!
        private readonly Settings _settings;

        public HomeController(IOptions<Settings> settings)
        {
            _settings = settings.Value;
        }

        public IActionResult Index()
        {
            ViewData["configval"] = _settings.awsvalue;
            ViewData["configval2"] = _settings.awsvalue2;

            return View();
        }
After updating my View to render the two properties, I launched the application—and both values stored in Parameter Store populated as expected.

What I Like About Parameter Store

  • Unbeatable Price: Standard parameters are free (up to 10,000 per account), which makes it a no-brainer for basic configuration and string parameters.

  • Full Audit Trail: Every update increments the version number and records an explicit change history showing who changed what and when.

  • Native .NET Core Integration: AWS’s Amazon.Extensions.Configuration.SystemsManager package integrates seamlessly into ASP.NET Core’s native IConfigurationBuilder, complete with automatic background reloads using TimeSpan.

The AWS team built this extension for .NET Core, and they added capabilities for reloading parameters automatically. Nice touch!

Friday, 30 August 2019

Quick Look: AWS Parameter store

AWS offers a parameter store as part of the AWS Systems Manager service. Parameter Store serves a more fundamental purpose: a central, secure key-value store for application configuration and secrets.
Getting started is simple. Navigate to the AWS Console, open AWS Systems Manager, and select Parameter Store from the left navigation panel. The interface allows you to view, edit, delete, and create parameters—supporting three data types: String, StringList, and encrypted SecureString (backed by AWS Key Management Service).
Each parameter gets a name and value. For the name, I used a “/” to define a hierarchy. The parameter type can be a string, list of strings, or encrypted string.
The UI was smart enough that when I went to go add a second parameter (/seroterdemo/properties/awsvalue2), it detected my existing hierarchy.
And that's it!! Now you are ready to use it in a .NET Core web app.


And lastly , some Real-World Usage Patterns

While it remains a lightweight way to store static variables, modern cloud architectures rely on Parameter Store for critical integrations:

  • Infrastructure as Code (IaC): AWS CloudFormation, Terraform, and AWS CDK directly resolve Parameter Store values at deployment time using dynamic references.

  • Container & Serverless Injection: ECS task definitions and AWS Lambda functions natively fetch Parameter Store values as runtime environment variables, separating application logic from secrets and config.

Wednesday, 21 August 2019

Adding an app.debug.config app.release.config transformations in .NET applications

Most applications need to run on multiple environments and with multiple configurations. At a minimum we usually have at least a local and production environment, but more often we have a local environment, a system test, UAT as well as our production and pre-production environments. Transformations allow you can have different settings for different configurations (debug or release). 

For example, the most common transformation is our connection string. For different databases , we will always have different connection strings That means a lot of different configurations are needed for each environment. Currently for ASP.NET, MVC and Web API projects, we have this functionality supported with our Web.config transformations. However for our app.config, i.e. for console or class libraries this is still not supported, out of the box. So we have to manually create our own transformations.

Previously, I have achieved these transformations manually, as you can see from this previous article I wrote: Adding an app.debug.config app.release.config transformations in .NET applications   , but now there are many different extension and Nuget packages for Visual studio, like Slow Cheetah and Configuration Transform which help you to achieve this much easily. 

Slow Cheetah 

Lately I have been using Slow Cheetah a lot and I really like it, this package allows you to automatically transform your app.config in Visual studio. You can either download it as a Visual Studio extension from the website or just add it to your project from Nuget.







Once installed ,you get a right click option to 
Add Transform, when you right click on your app.config in your project solution . 


Clicking this option will automatically create your new debug and release configs for you. 
And as you can see they are correctly nested underneath the app.config. This is because Slow Cheetah has automatically updated our project file to contain the necessary settings to tell the release and debug config that they are dependent upon the app.config. Before we had to manually make these adjustments to the project file ourselves.


If we open our project file and check the settings added, we can now see something like this, this is how we would normally have to edit our project file to look like, this basically defines the dependency between the app.config and the release and debug config.


Also notice the IsTransformFile element, this is the important setting for Slow cheetah. Instructing it to automatically transform the App.config file depending on the chosen configuration.

And that's it, a lot of manual work saved already. Now you can can add whatever customization's you like inside the transform files, for example if you want to tweak app settings and connection string.

E.G here I have just added an app setting to the app.config and the app.release.config , setting the value to true in the release config and false in the app.config. 




Note, the use of the replace transform key to prompt the replacement of this value and the locator key. 

So if we walk-through the example above, the replace transform key, sends the instruction to replace the true value from the isProd key located beneath the appSetting element. This is done using the xdt:Transform attribute. 

Also note , that all XDT documents, i.e. your app.config, and each environment config, need a namespace declaration in the root element, otherwise your transformations will not happen.





When you build your application the files are transformed and dropped into the output directory. If you are transforming the app.config then when the file is transformed it will be renamed in the output directory as usual to ensure that your application picks it up at runtime.

Preview Transform 


You can also quickly preview your transform using the Preview Transform context menu on the transform file. By selecting the release config and right clicking in the solution explorer, we are presented with the differences, which in our case, is the app setting differences we added between the app and release configs. However as you know, environment configs can become very long and detailed, so this Preview Transform tool can be a god send when you are trying to check settings or connection strings to debug an issue. 


Wednesday, 10 January 2018

How to fix: HTTP could not register URL error

Often when running a service , you might get the following annoying error :

HTTP could not register URL http://+:8000/MyWCF/. Your process does not have access rights to this namespace (see http://go.microsoft.com/fwlink/?LinkId=70353 for details).

To get around this you need to run up a command window and run the following for your service and domain :

netsh http add urlacl url=http://+:80/MyUri user=DOMAIN\user


Thanks to Brian via stackoverflow for this command and the explanation of the invalid exception text and how to find what's really going wrong.