Tuesday, 10 November 2026

Before Upgrading an ASP.NET Core Application to.NET 11, What Should Developers Look Out For?

Leave a Comment

.NET 11 will include improvements to the runtime, libraries, SDK, networking, ASP.NET Core, and developer tooling. NET 11 is presently in the release-candidate stage, with general availability anticipated in November 2026. For teams maintaining existing applications, the crucial question is not just what is new but also what has to be reviewed before transferring a production application to the new runtime. As part of.NET Conf 2026, Microsoft has also announced the release of.NET 11.

 

An upgrade should therefore be treated as a compatibility and validation exercise rather than simply changing the target framework from net10.0 to net11.0.

This article walks through a practical checklist for upgrading an ASP.NET Core application to .NET 11.

Start With the Current Application

Before changing the project file, understand what the application currently depends on.

Check:

  • Current .NET version

  • ASP.NET Core version

  • C# language version

  • NuGet packages

  • Database providers

  • Authentication libraries

  • Logging libraries

  • OpenTelemetry packages

  • Background services

  • Native dependencies

  • Docker base images

  • CI/CD build images

  • Cloud hosting environment

For a typical ASP.NET Core application, the project file may currently contain:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

</Project>

The first upgrade step is straightforward:

<TargetFramework>net11.0</TargetFramework>

But changing this one line does not prove that the application is ready for production.

Check the .NET 11 SDK First

Install the .NET 11 SDK in a development or isolated environment before changing production infrastructure.

Verify the SDK:

dotnet --version

Then check the installed SDKs:

dotnet --list-sdks

For a team environment, consider adding a global.json file so developers and CI systems use the intended SDK version.

For example:

{
  "sdk": {
    "version": "11.0.100"
  }
}

The exact SDK version should match the version your team has standardized on after .NET 11 reaches the required release state.

Update the Target Framework

Once the SDK is available, update the application:

<PropertyGroup>
  <TargetFramework>net11.0</TargetFramework>
</PropertyGroup>

Then restore and build:

dotnet restore
dotnet build

Do this before making additional application changes.

The first build result is useful because it identifies compatibility issues early.

A successful build does not mean the migration is complete. Runtime behavior, integration tests, deployment configuration, and third-party packages still need to be checked.

Review NuGet Dependencies

NuGet dependencies are one of the first places to look when an application moves to a new .NET version.

Run:

dotnet list package --outdated

Also inspect direct and transitive dependencies:

dotnet list package

Look for packages that:

  • Do not support net11.0

  • Depend on older framework versions

  • Use deprecated APIs

  • Include native components

  • Have newer versions specifically supporting .NET 11

Do not automatically upgrade every package at the same time.

A better approach is to separate the framework migration from unrelated dependency upgrades.

For example:

.NET 10
  |
  +-- Upgrade application to .NET 11
  |
  +-- Fix compatibility issues
  |
  +-- Run tests
  |
  +-- Upgrade selected dependencies
  |
  +-- Run tests again

This makes failures easier to diagnose.

Review ASP.NET Core Dependencies

ASP.NET Core applications often depend on more than the main Microsoft packages.

Check packages related to:

  • Authentication

  • Authorization

  • OpenAPI

  • JSON serialization

  • Entity Framework Core

  • Health checks

  • Background jobs

  • Messaging

  • Caching

  • Distributed tracing

  • Rate limiting

  • API documentation

If a package depends on a specific ASP.NET Core version, verify that its supported version aligns with the .NET 11 application.

Avoid ignoring package compatibility warnings simply because the application compiles.

Review Runtime Changes

.NET 11 includes runtime changes that can affect application performance and behavior.

Microsoft documents changes to the JIT, async execution, NativeAOT, hardware intrinsics, and other runtime areas.

One area receiving significant attention is runtime-native async.

The runtime can now manage async suspension and resumption differently from previous compiler-generated approaches, with associated performance and debugging improvements.

For most application teams, this does not mean rewriting existing async code.

Instead, existing asynchronous code should be tested under the new runtime.

Pay particular attention to:

public async Task<IActionResult> GetOrders()
{
    var orders = await service.GetOrdersAsync();

    return Ok(orders);
}

Do not replace working async code simply because the runtime has improved its async implementation.

Measure the application first.

Test HTTP and Networking Behavior

ASP.NET Core applications often spend a significant amount of time communicating with other services.

.NET 11 introduces networking changes including configurable HTTP connection eviction, HTTP request-body compression, improved HTTP/2 handling, DNS APIs, and other networking improvements.

Review applications that use:

HttpClient

especially when they maintain long-lived connections to external services.

A typical production configuration might use IHttpClientFactory:

builder.Services.AddHttpClient("OrdersApi", client =>
{
    client.BaseAddress =
        new Uri("https://orders.example.internal");
});

Then:

var client = httpClientFactory.CreateClient("OrdersApi");

var response = await client.GetAsync("api/orders");

The upgrade is a good opportunity to verify connection lifetime, timeout, retry, and DNS behavior.

Test JSON Serialization

System.Text.Json continues to receive improvements in .NET 11, including additional support around unions, JSON Lines, polymorphism, source generation, naming, and collection contracts.

Applications that use custom JSON converters should receive particular attention.

For example:

builder.Services.ConfigureHttpJsonOptions(options =>
{
    options.SerializerOptions.PropertyNamingPolicy =
        JsonNamingPolicy.CamelCase;
});

Run API integration tests against real payloads rather than relying only on unit tests.

Verify:

  • Property names

  • Null handling

  • Enum serialization

  • Custom converters

  • Polymorphic types

  • Date/time formatting

  • Request validation

  • Response compatibility

This is especially important for APIs consumed by older mobile or desktop clients.

Review API Compatibility

A runtime upgrade should not accidentally change your public API contract.

Before deployment, compare representative API responses from the old and new versions.

For example:

.NET 10 Application
       |
       v
GET /api/customers/123
       |
       v
Response Snapshot

.NET 11 Application
       |
       v
GET /api/customers/123
       |
       v
Response Snapshot

Compare:

  • HTTP status codes

  • Headers

  • JSON structure

  • Property names

  • Null values

  • Validation errors

  • Error responses

An application can compile successfully while still introducing an API behavior change.

Test Authentication and Authorization

Authentication middleware should be tested independently.

Check:

  • JWT validation

  • Cookie authentication

  • OpenID Connect

  • OAuth integrations

  • Policy-based authorization

  • Role claims

  • Custom claims

  • Token expiration

  • Refresh tokens

For example:

app.UseAuthentication();
app.UseAuthorization();

Run tests for both authenticated and unauthenticated requests.

Also verify that failed authentication produces the expected response rather than exposing internal exception details.

Test Entity Framework Core

If the application uses Entity Framework Core, treat the EF Core version as a separate dependency to validate.

Test:

  • Database connection

  • Migrations

  • Query translation

  • Transactions

  • Concurrency

  • Lazy/eager loading

  • Stored procedure calls

  • Database-specific behavior

A migration should not be considered successful simply because the application starts.

Run representative production queries against a test database.

For example:

var customers = await dbContext.Customers
    .Where(c => c.IsActive)
    .OrderBy(c => c.Name)
    .Take(100)
    .ToListAsync();

Measure query execution time before and after the upgrade.

Review Docker Images

If the application is containerized, update the base image as part of the migration.

For example:

FROM mcr.microsoft.com/dotnet/aspnet:11.0 AS runtime

WORKDIR /app

COPY . .

ENTRYPOINT ["dotnet", "MyApplication.dll"]

The exact image tag should match the supported .NET 11 release you are deploying.

Also update the SDK image used for building:

FROM mcr.microsoft.com/dotnet/sdk:11.0 AS build

Do not update only the runtime image while leaving the build environment on an incompatible SDK.

Check CI/CD Pipelines

The development machine may use .NET 11 while CI still uses .NET 10.

That can create confusing build failures.

Review:

Developer SDK
      |
      v
Source Control
      |
      v
CI Build Image
      |
      v
Automated Tests
      |
      v
Container Build
      |
      v
Production Runtime

Every stage should use a deliberate SDK/runtime version.

Update:

  • GitHub Actions

  • Azure Pipelines

  • GitLab CI

  • Jenkins

  • Docker build images

  • Deployment agents

  • Test environments

Then run the complete pipeline before production deployment.

Test Performance Before and After

Do not assume .NET 11 will automatically make your application faster.

Measure it.

Useful metrics include:

  • Requests per second

  • Average latency

  • p95 latency

  • p99 latency

  • CPU utilization

  • Memory usage

  • GC activity

  • Database latency

  • HTTP client latency

  • Startup time

For example:

Metric             Before       After

Requests/sec       2,100        2,150
p95 latency        180 ms       165 ms
CPU                62%          58%
Memory              420 MB       405 MB

These numbers are illustrative. Use actual production-like benchmark data when making upgrade decisions.

Pay Attention to Hardware Requirements

.NET 11 updates the minimum hardware requirements for some x86/x64 and Arm64 scenarios. Microsoft documents a move from x86-64-v1 to x86-64-v2 for supported x86/x64 environments, along with updated Arm64 requirements for some platforms.

This matters when applications run on older virtual machines or specialized infrastructure.

Before upgrading, check:

Production VM
    |
    +-- CPU architecture
    +-- Instruction-set support
    +-- Operating system
    +-- Container runtime
    +-- Hosting platform

Do not assume that every old server configuration is automatically suitable for the new runtime.

Test Background Services

ASP.NET Core applications frequently host background workers.

For example:

public sealed class OrderWorker(
    IServiceScopeFactory scopeFactory)
    : BackgroundService
{
    protected override async Task ExecuteAsync(
        CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            using var scope = scopeFactory.CreateScope();

            var service =
                scope.ServiceProvider
                    .GetRequiredService<IOrderService>();

            await service.ProcessAsync(stoppingToken);

            await Task.Delay(
                TimeSpan.FromMinutes(1),
                stoppingToken);
        }
    }
}

Test:

  • Graceful shutdown

  • Cancellation tokens

  • Database connections

  • Queue processing

  • Retry behavior

  • Duplicate processing

  • Worker startup

  • Worker restart

A runtime upgrade can expose issues that are invisible during normal API testing.

Use Staged Deployment

Do not make the first .NET 11 deployment a full production rollout.

A safer approach is:

Development
    |
    v
Automated Tests
    |
    v
Staging
    |
    v
Production Canary
    |
    v
Small Traffic Percentage
    |
    v
Full Production

Monitor the canary deployment for errors, latency, CPU, memory, database load, and external dependency failures.

If the metrics remain healthy, continue the rollout.

Common Mistakes
Updating the target framework and stopping there

Changing net10.0 to net11.0 is only the beginning.

Upgrading every NuGet package simultaneously

This makes compatibility problems difficult to isolate.

Ignoring native dependencies

Native libraries may have architecture or runtime requirements that managed packages do not expose clearly.

Testing only successful API requests

Error handling, authentication failures, timeouts, and malformed input need testing too.

Forgetting CI/CD

A developer machine using .NET 11 does not mean the deployment pipeline is ready.

Skipping performance benchmarks

Runtime improvements should be validated against your actual workload.

Deploying directly to all production instances

Use staged rollout when the application is business-critical.

Recommended Upgrade Checklist

Before moving an ASP.NET Core application to .NET 11, verify:

[ ] .NET 11 SDK installed
[ ] Target framework updated
[ ] NuGet dependencies reviewed
[ ] ASP.NET Core dependencies verified
[ ] Authentication tested
[ ] Authorization tested
[ ] JSON serialization tested
[ ] API contracts compared
[ ] EF Core queries tested
[ ] Database migrations tested
[ ] HttpClient behavior tested
[ ] Background workers tested
[ ] Docker images updated
[ ] CI/CD updated
[ ] Production hardware checked
[ ] Performance benchmark completed
[ ] Integration tests passed
[ ] Staging deployment completed
[ ] Rollback plan prepared
[ ] Production monitoring configured
Advantages of Moving to .NET 11

The upgrade provides access to the latest .NET platform improvements, including runtime, library, networking, SDK, and language changes. Microsoft also highlights improvements in areas such as JSON handling, HTTP networking, diagnostics, compression, numeric APIs, and async execution.

For applications that regularly update their platform version, adopting .NET 11 can also reduce the amount of technical debt accumulated from staying several releases behind.

What Should Not Be Changed During the Upgrade

A useful migration rule is to avoid changing unrelated architecture at the same time.

For example, do not combine:

.NET 10 → .NET 11
+
Database migration
+
Authentication rewrite
+
Microservices conversion
+
New messaging platform

unless there is a strong reason to do so.

A smaller change set makes production failures easier to understand.

A better approach is:

.NET 10
   |
   v
.NET 11
   |
   v
Validate
   |
   v
Stabilize
   |
   v
Make Next Architecture Change
Final Thoughts

Upgrading an ASP.NET Core application to .NET 11 should be treated as a controlled engineering change rather than a project-file edit. Start with dependency compatibility, then test the runtime, networking, JSON serialization, database access, authentication, background services, containers, and CI/CD pipeline.

Performance should be measured rather than assumed, and production rollout should be gradual for applications where downtime or regressions are expensive.

.NET 11 brings meaningful changes across the platform, but the best upgrade is not the one that adopts every new feature immediately. It is the one that moves the existing application to the new runtime with predictable behavior, measurable performance, and a clear rollback path.

Best ASP.NET Core 11.0 Hosting Recommendation

One of the most important things when choosing a good ASP.NET Core 11.0 hosting is the feature and reliability. HostForLIFE is the leading provider of Windows hosting and affordable ASP.NET Core, their servers are optimized for PHP web applications. The performance and the uptime of the hosting service are excellent and the features of the web hosting plan are even greater than what many hosting providers ask you to pay for. 

At HostForLIFE, customers can also experience fast ASP.NET Core 11 hosting. The company invested a lot of money to ensure the best and fastest performance of the datacenters, servers, network and other facilities. Its datacenters are equipped with the top equipments like cooling system, fire detection, high speed Internet connection, and so on. That is why HostForLIFE guarantees 99.9% uptime for ASP.NET Core. And the engineers do regular maintenance and monitoring works to assure its Orchard hosting are security and always up.


Read More...

Tuesday, 3 November 2026

Keeping API Pagination Reliable When Database Records Change

Leave a Comment

One popular feature of ASP.NET Core apps is pagination. Instead of loading all of the information in a single request, it enables users to get enormous collections of records in smaller chunks.


An order management application, for instance, can have millions of records. When a user requests a different page, the API delivers the following 20 records after returning the first 20 instead of every order.

These changes can cause duplicate records, missing results, and inconsistent page counts.

This article explains how to build reliable API pagination in ASP.NET Core using Entity Framework Core, offset pagination, keyset pagination, and stable sorting.

Why Database Changes Break Pagination

Consider an API that returns orders using page numbers.

GET /api/orders?page=1&pageSize=3

Assume the database initially contains these records:

Order ID

Order Date

105

10:00 AM

104

9:45 AM

103

9:30 AM

102

9:15 AM

101

9:00 AM

The first request returns:

105, 104, 103

Before the user requests the second page, another order is inserted.

The database now contains:

Order ID

Order Date

106

10:15 AM

105

10:00 AM

104

9:45 AM

103

9:30 AM

102

9:15 AM

101

9:00 AM

The user requests page two using an offset of three records.

The result is:

103, 102, 101

Order 103 appears twice because the new record shifted the positions of the existing records.

A deletion can cause the opposite problem. A record that should appear on the second page may be skipped because the remaining records moved forward.

This is the main limitation of offset-based pagination when records change frequently.

Build Basic Offset Pagination

Offset pagination is still useful for many applications, especially when users need numbered pages and can navigate directly to a particular page.

Create a request model:

public class PaginationRequest
{
    public int Page { get; set; } = 1;

    public int PageSize { get; set; } = 20;
}

Validate the requested values before executing the database query.

public static PaginationRequest Validate(
    PaginationRequest request)
{
    request.Page = Math.Max(1, request.Page);

    request.PageSize = Math.Clamp(
        request.PageSize,
        1,
        100);

    return request;
}

Limiting the page size prevents clients from requesting unnecessarily large result sets.

Next, create a response model:

public class PagedResponse<T>
{
    public List<T> Items { get; set; } = new();

    public int Page { get; set; }

    public int PageSize { get; set; }

    public int TotalCount { get; set; }

    public int TotalPages { get; set; }
}

Implement the query using Entity Framework Core:

public async Task<PagedResponse<Order>> GetOrdersAsync(
    PaginationRequest request,
    CancellationToken cancellationToken)
{
    request = PaginationRequestValidator.Validate(
        request);

    var query = _context.Orders
        .AsNoTracking()
        .OrderByDescending(x => x.CreatedAtUtc)
        .ThenByDescending(x => x.Id);

    var totalCount = await query.CountAsync(
        cancellationToken);

    var items = await query
        .Skip((request.Page - 1) * request.PageSize)
        .Take(request.PageSize)
        .ToListAsync(cancellationToken);

    return new PagedResponse<Order>
    {
        Items = items,
        Page = request.Page,
        PageSize = request.PageSize,
        TotalCount = totalCount,
        TotalPages = (int)Math.Ceiling(
            totalCount / (double)request.PageSize)
    };
}

The example uses CreatedAtUtc and Id for sorting.

The unique ID is important because multiple orders may have the same creation timestamp. Without a unique secondary sort key, the database may return records in different relative orders across requests.

Stable sorting makes offset pagination more predictable, but it does not prevent records from shifting between pages when new records are inserted or deleted.

Understand the Importance of Stable Sorting

A database does not guarantee a consistent row order unless the query explicitly specifies one.

Consider this query:

var orders = await _context.Orders
    .Skip(20)
    .Take(20)
    .ToListAsync();

There is no explicit ordering.

The database can return records in different orders depending on the execution plan, indexes, or changes to the data.

Always specify a deterministic order:

var orders = await _context.Orders
    .OrderByDescending(x => x.CreatedAtUtc)
    .ThenByDescending(x => x.Id)
    .Skip(20)
    .Take(20)
    .ToListAsync();

The ordering should end with a unique value, such as a primary key.

This prevents ambiguous ordering when multiple records have the same timestamp or business value.

Use Keyset Pagination for Frequently Changing Data

Keyset pagination, also called cursor pagination, retrieves records based on the last item from the previous response.

Instead of saying "skip the first 20 records," the client says "return records after this last known position."

For example, if the last order returned has:

CreatedAtUtc: 2026-11-02 10:00:00
Id: 105

The next request retrieves records older than that position.

The database query looks like this:

var orders = await _context.Orders
    .Where(x =>
        x.CreatedAtUtc < lastCreatedAt ||
        (x.CreatedAtUtc == lastCreatedAt &&
         x.Id < lastOrderId))
    .OrderByDescending(x => x.CreatedAtUtc)
    .ThenByDescending(x => x.Id)
    .Take(pageSize)
    .ToListAsync(cancellationToken);

This query uses both the timestamp and unique ID to identify the next position.

Newer records inserted at the beginning of the list do not shift the cursor position.

That makes keyset pagination more reliable for frequently changing datasets.

Create a Cursor-Based Response

A cursor-based API does not need a page number.

Instead, return a continuation token representing the last record in the current result.

Create a response model:

public class CursorPagedResponse<T>
{
    public List<T> Items { get; set; } = new();

    public string? NextCursor { get; set; }

    public bool HasMore { get; set; }
}

Create a cursor model:

public class OrderCursor
{
    public DateTime CreatedAtUtc { get; set; }

    public long Id { get; set; }
}

The cursor should be encoded before returning it to the client.

public static class CursorHelper
{
    public static string Encode(OrderCursor cursor)
    {
        var json = JsonSerializer.Serialize(cursor);

        return Convert.ToBase64String(
            Encoding.UTF8.GetBytes(json));
    }

    public static OrderCursor Decode(string value)
    {
        var json = Encoding.UTF8.GetString(
            Convert.FromBase64String(value));

        return JsonSerializer.Deserialize<OrderCursor>(
            json) ?? throw new ArgumentException(
                "Invalid cursor.");
    }
}

Base64 encoding makes the cursor easier to transport in a URL. It does not provide security or prevent clients from modifying its contents.

For production APIs, consider signing the cursor or protecting it with ASP.NET Core Data Protection. Validate the decoded values and bind the cursor to the relevant filters and sort order.

Implement the Cursor Pagination Query

Now implement the complete query:

public async Task<CursorPagedResponse<Order>> GetOrdersAsync(
    string? cursor,
    int pageSize,
    CancellationToken cancellationToken)
{
    pageSize = Math.Clamp(pageSize, 1, 100);

    var query = _context.Orders
        .AsNoTracking()
        .AsQueryable();

    if (!string.IsNullOrWhiteSpace(cursor))
    {
        var last = CursorHelper.Decode(cursor);

        query = query.Where(x =>
            x.CreatedAtUtc < last.CreatedAtUtc ||
            (x.CreatedAtUtc == last.CreatedAtUtc &&
             x.Id < last.Id));
    }

    var results = await query
        .OrderByDescending(x => x.CreatedAtUtc)
        .ThenByDescending(x => x.Id)
        .Take(pageSize + 1)
        .ToListAsync(cancellationToken);

    var hasMore = results.Count > pageSize;

    var items = results
        .Take(pageSize)
        .ToList();

    string? nextCursor = null;

    if (hasMore && items.Count > 0)
    {
        var lastItem = items[^1];

        nextCursor = CursorHelper.Encode(
            new OrderCursor
            {
                CreatedAtUtc = lastItem.CreatedAtUtc,
                Id = lastItem.Id
            });
    }

    return new CursorPagedResponse<Order>
    {
        Items = items,
        HasMore = hasMore,
        NextCursor = nextCursor
    };
}

The query retrieves one additional record to determine whether another page exists.

For example, if the requested page size is 20, the database retrieves up to 21 records.

If 21 records are returned, the API knows that another page is available. It returns the first 20 and creates the next cursor from the twentieth record.

This avoids running a separate count query for every request.

Understand the Limitations of Keyset Pagination

Keyset pagination is not a complete snapshot of the database.

It prevents newly inserted records before the current cursor from shifting the next page, but it does not guarantee that every record remains unchanged between requests.

For example:

  • A record can be deleted before the client reaches it.

  • An existing record can change its sort value and move across the cursor.

  • A record inserted with an older timestamp may appear in a later page.

  • Changes to filtering fields can affect which records qualify.

If the application needs a consistent snapshot across multiple page requests, use a suitable snapshot strategy.

One option is to capture a fixed upper boundary on the first request and include it in every subsequent cursor. Another is to use database snapshot isolation within a transaction, although keeping a transaction open across multiple HTTP requests is usually not a good design.

The right approach depends on the application's consistency requirements.

Add Database Indexes for Pagination

Pagination performance depends heavily on database indexes.

If the API frequently sorts by creation time and ID, create a composite index.

For SQL Server, the index could look like this:

CREATE INDEX IX_Orders_CreatedAtUtc_Id
ON Orders (CreatedAtUtc DESC, Id DESC);

The exact index design depends on the database engine and query workload.

For EF Core, configure the index in the model:

modelBuilder.Entity<Order>()
    .HasIndex(x => new
    {
        x.CreatedAtUtc,
        x.Id
    });

If the API also filters by fields such as customer ID or status, evaluate whether those columns should be included in a composite index.

Do not create indexes without checking actual query plans. Additional indexes increase storage requirements and make inserts and updates more expensive.

Choose the Right Pagination Strategy

Offset and keyset pagination solve different problems.

Requirement

Offset pagination

Keyset pagination

Numbered pages

Supported

Not naturally supported

Jump to a specific page

Supported

Difficult

Frequent inserts

Can shift results

More stable

Large offsets

Can become expensive

Avoids large skips

Next-page navigation

Supported

Well suited

Total record count

Easy to include

Usually separate

Stable traversal

Limited with changing data

Better with a stable cursor

Use offset pagination for administrative screens where users need numbered pages and the dataset is relatively stable.

Use keyset pagination for activity feeds, order histories, transaction lists, and other datasets where new records arrive frequently.

Common Mistakes to Avoid

One common mistake is using Skip and Take without an explicit sort order. This can produce inconsistent results even when the database has not changed.

Another mistake is sorting only by a timestamp. Multiple records may share the same timestamp, so use a unique secondary key.

Developers also sometimes assume Base64-encoded cursors are secure. They are only encoded, not protected against modification.

Finally, avoid running an expensive CountAsync query on every request when the interface does not need an exact total count. A HasMore value is often enough for next-page navigation.

Summary

Reliable API pagination requires more than limiting the number of records returned by a query.

Offset pagination is useful for numbered pages, but inserts and deletions can shift records between requests. Keyset pagination uses a stable cursor to make next-page navigation more predictable when the database changes frequently.

Use deterministic sorting with a unique secondary key, choose the pagination strategy based on the application's requirements, and create indexes that support the actual query pattern.

For production applications, also validate cursor values, consider snapshot requirements, and test pagination while records are being inserted, updated, and deleted.

Best ASP.NET Core 11.0 Hosting Recommendation

One of the most important things when choosing a good ASP.NET Core 11.0 hosting is the feature and reliability. HostForLIFE is the leading provider of Windows hosting and affordable ASP.NET Core, their servers are optimized for PHP web applications. The performance and the uptime of the hosting service are excellent and the features of the web hosting plan are even greater than what many hosting providers ask you to pay for. 

At HostForLIFE, customers can also experience fast ASP.NET Core 11 hosting. The company invested a lot of money to ensure the best and fastest performance of the datacenters, servers, network and other facilities. Its datacenters are equipped with the top equipments like cooling system, fire detection, high speed Internet connection, and so on. That is why HostForLIFE guarantees 99.9% uptime for ASP.NET Core. And the engineers do regular maintenance and monitoring works to assure its Orchard hosting are security and always up.

Read More...

Tuesday, 27 October 2026

ASP.NET Core Tutorial: Content Security Policy Nonces: How to Prevent Your Website from Using Unsafe Scripts

Leave a Comment

Even with secure cookies, input validation, and authentication, a website may still be susceptible to cross-site scripting (XSS). One explanation is that JavaScript that an attacker is able to insert into a website may be executed by browsers.

Think about a program that shows comments left by users. An attacker may insert a script that runs in the browser of another user if the program improperly handles untrusted input. This might reveal private data or act as the impacted user, depending on the program and its security measures.

A Content Security Policy (CSP) helps reduce this risk by telling the browser which scripts and other resources it is allowed to load or execute. A nonce-based CSP takes this further by allowing specific inline scripts through a unique value generated for each HTML response.

This approach is useful for applications that need inline JavaScript but want to avoid allowing every inline script to run.

In this article, you will learn how CSP nonces work, how to configure them, how to use them in an ASP.NET Core application, and how to test the policy without breaking your website.

What Is a Content Security Policy?

Content Security Policy is a browser security mechanism that restricts the sources and types of content a page can load or execute.

A server normally sends CSP through the Content-Security-Policy HTTP response header. The browser reads the policy and applies its rules to supported resources.

For example:

Content-Security-Policy: default-src 'self'; script-src 'self'

This policy uses the current origin as the default permitted source and allows scripts from that origin. It does not permit arbitrary inline scripts through the script-src directive.

That distinction matters because inline JavaScript can be a common route for XSS attacks.

Without a suitable policy, a page might contain:

<script>
    alert("Inline script executed");
</script>

A restrictive CSP blocks this inline script unless the policy explicitly permits it through a mechanism such as a nonce or hash, or through another applicable exception.

The exact behavior depends on the directives and browser support. CSP is a defense-in-depth control, not a replacement for safe output encoding, input handling, or secure application design.

What Is a CSP Nonce?

A nonce is a cryptographically random value generated by the server and included in both the CSP header and the HTML elements that are allowed to execute.

The term nonce means “number used once.” For CSP, the important requirements are that the value be unpredictable and newly generated for each HTML response.

Suppose the server generates this nonce:

r4nd0mExampleValue

The response header might contain:

Content-Security-Policy: script-src 'self' 'nonce-r4nd0mExampleValue'

The HTML can then include an approved inline script:

<script nonce="r4nd0mExampleValue">
    initializeApplication();
</script>

The browser checks the script element against the policy. If the nonce matches the value in the policy, the script is permitted by that nonce source.

An inline script without the matching nonce is blocked by this policy unless another applicable rule allows it.

The example value above is illustrative only. A production nonce must be generated securely at runtime.

Why Nonces Are Better Than Allowing All Inline Scripts

A common way to make inline JavaScript work under CSP is to add 'unsafe-inline' to script-src.

For example:

Content-Security-Policy: script-src 'self' 'unsafe-inline'

This weakens the protection offered by the script policy because it allows inline scripts without requiring each one to carry an approved nonce or hash. In some older browser implementations, the interaction between nonce sources and 'unsafe-inline' also differs from modern CSP behavior.

A nonce-based policy provides a more controlled option.

Approach

How it works

Security consideration

'unsafe-inline'

Permits inline scripts under the applicable policy

Broadly permits inline script execution

Nonce

Permits scripts carrying a matching unpredictable value

Requires server-side generation and HTML integration

Hash

Permits inline script content matching a configured cryptographic hash

Requires updating the policy when the script content changes

External scripts only

Restricts scripts to approved sources

Requires careful source control and may not suit every application

A nonce is not automatically safer in every implementation. If an attacker can inject arbitrary HTML and also obtain a valid nonce from the response, the protection may be undermined. The server must keep the nonce unpredictable and ensure that trusted templates, rather than untrusted user input, determine which scripts receive it.

How to Generate a Secure Nonce in ASP.NET Core

ASP.NET Core applications can generate a nonce using the cryptographic random number generator provided by .NET.

The following example creates a middleware component that generates a new nonce for each HTTP request and makes it available to downstream components.

using System.Security.Cryptography;

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.Use(async (context, next) =>
{
    var nonceBytes = RandomNumberGenerator.GetBytes(32);
    var nonce = Convert.ToBase64String(nonceBytes);

    context.Items["CspNonce"] = nonce;

    await next();
});

app.Run();

This example generates 32 random bytes and encodes them as Base64. It stores the result in HttpContext.Items, allowing downstream middleware or application code to access it during the same request.

The example does not yet send a CSP header. It only generates and stores the nonce.

In a real application, ensure that the nonce is generated before any component that needs it, and generate it for every HTML response that uses the nonce-based policy.

Avoid placing a fixed nonce in application configuration or source code. A fixed value would defeat the purpose of generating a fresh, unpredictable nonce.

Add the Nonce to the CSP Response Header

Once the nonce is available, the application can include it in the response policy.

For example, the middleware can be extended as follows:

using System.Security.Cryptography;

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.Use(async (context, next) =>
{
    var nonce = Convert.ToBase64String(
        RandomNumberGenerator.GetBytes(32));

    context.Items["CspNonce"] = nonce;

    context.Response.OnStarting(() =>
    {
        context.Response.Headers["Content-Security-Policy"] =
            $"default-src 'self'; " +
            $"script-src 'self' 'nonce-{nonce}'; " +
            $"object-src 'none'; " +
            $"base-uri 'self'";

        return Task.CompletedTask;
    });

    await next();
});

app.MapGet("/", () => Results.Content(
    "<!doctype html><html><body><h1>Welcome</h1></body></html>",
    "text/html"));

app.Run();

This example demonstrates nonce generation and response-header construction. It does not include an inline script in the returned HTML, so there is no script element that uses the nonce yet.

The policy restricts scripts to the current origin and scripts carrying the generated nonce. It also blocks plugin-based objects and restricts the base URL used to resolve relative URLs.

The policy is intentionally small. A production website may need additional directives for stylesheets, images, fonts, API connections, frames, and other resources. Those directives should reflect the application's actual requirements rather than allowing every source by default.

Use the Nonce in Razor Views

An ASP.NET Core MVC or Razor Pages application can retrieve the nonce from HttpContext.Items and add it to trusted inline script elements.

For example, in a Razor view:

@{
    var nonce = Context.Items["CspNonce"] as string;
}

<!DOCTYPE html>
<html>
<head>
    <title>CSP Nonce Example</title>
</head>
<body>
    <h1>Welcome</h1>

    <script nonce="@nonce">
        console.log("Approved inline script executed.");
    </script>
</body>
</html>

The Razor view reads the nonce generated by the middleware and adds it to the script element.

The response header and the HTML element must use exactly the same nonce for that response. Razor's normal HTML encoding should remain enabled; do not disable encoding to insert the value.

Also, only trusted application templates should receive the nonce. Do not automatically attach it to every script element in user-submitted HTML or other untrusted content.

For applications with many views, consider creating a reusable helper, tag helper, or view component so developers do not need to handle nonce retrieval independently in every template.

Understand the Important CSP Directives

A nonce-based policy normally works alongside other CSP directives.

Directive

Purpose

Example

default-src

Provides a fallback for several resource types

default-src 'self'

script-src

Controls permitted JavaScript sources and execution

script-src 'self' 'nonce-...'

style-src

Controls stylesheet sources and inline styles

style-src 'self'

img-src

Controls image sources

img-src 'self' data:

connect-src

Controls script-initiated connections such as fetch and WebSocket connections

connect-src 'self'

object-src

Controls plugin-based content

object-src 'none'

base-uri

Restricts URLs used by the HTML base element

base-uri 'self'

frame-ancestors

Controls which sites may embed the page

frame-ancestors 'self'

These examples are starting points, not a complete policy for every application.

A directive such as default-src 'self' does not replace the need to define and test important resource-specific rules. Also, frame-ancestors must be delivered through an appropriate CSP response header; it is not supported through a CSP meta element.

Test the Policy Before Enforcing It

A strict CSP can block scripts that your application legitimately needs. Third-party analytics, payment widgets, embedded content, and frontend libraries may depend on resources that are not covered by the initial policy.

A safer rollout starts with report-only mode.

For example:

Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'nonce-example'; object-src 'none'; base-uri 'self'

This header reports policy violations without enforcing the policy. The example nonce is a placeholder and should not be copied into a production configuration.

Review browser developer tools and any configured CSP reporting destination for violations. Update the policy based on verified application requirements, then move to an enforcing Content-Security-Policy header.

Report-only mode is useful for discovering compatibility problems, but it does not protect users from the violations being reported.

Common Problems and Their Solutions

Problem

Likely cause

What to check

Inline script is blocked

Missing or mismatched nonce

Compare the response header and script element

Scripts stop working after deployment

Required source or script was omitted from the policy

Review browser CSP violation messages

Nonce appears unchanged across requests

The value is cached or generated outside the request flow

Generate a new nonce for each HTML response

Third-party widget fails

The widget needs additional permitted resources or scripts

Identify its actual dependencies before changing the policy

Page works in report-only mode but fails when enforced

Report-only mode does not block violations

Resolve the reported violations before enforcement

Cached HTML contains an old nonce

HTML and its CSP header are being cached or served independently

Ensure the cached response preserves a matching header and HTML pair, or avoid shared caching of nonce-bearing HTML

Common Mistakes to Avoid

Reusing a Nonce

A nonce should be generated securely for each HTML response. Reusing a value across requests makes it less useful as a response-specific authorization token.

Adding a Nonce to Untrusted Scripts

A nonce should identify scripts that the application trusts. Adding it automatically to attacker-controlled markup can allow injected scripts to run.

Keeping 'unsafe-inline' Without a Clear Reason

Allowing all inline scripts can undermine the intended protection. Prefer nonces, hashes, or external scripts when practical.

Applying a Policy Without Testing

A policy that is too restrictive can break login flows, payment integrations, or frontend features.

Start with report-only mode, examine the actual violations, and enforce the policy after validating the required resources.

Treating CSP as the Only XSS Defense

CSP reduces the impact of certain script-injection attacks, but it does not make unsafe HTML rendering acceptable.

Use context-aware output encoding, safe DOM APIs, appropriate input validation, and secure framework defaults as well.

Best Practices for Production Applications

  • Generate nonces with a cryptographically secure random number generator.

  • Use a fresh nonce for every HTML response that relies on it.

  • Construct the CSP header and HTML using the same request-specific value.

  • Keep nonce assignment limited to trusted scripts.

  • Avoid 'unsafe-inline' unless a documented compatibility requirement justifies it.

  • Use report-only mode to evaluate a policy before enforcement.

  • Review browser violations after deploying policy changes.

  • Be careful when caching HTML that contains nonces.

  • Define only the resource sources that the application actually needs.

  • Continue using output encoding and safe rendering practices to prevent XSS.

Advantages and Limitations of CSP Nonces

Advantages

Limitations

Allows selected inline scripts without permitting all inline scripts

Requires server-side nonce generation and template integration

Helps reduce the impact of certain XSS vulnerabilities

Does not replace safe output encoding or secure DOM handling

Works with server-rendered applications such as ASP.NET Core Razor

Shared caching can create mismatches between HTML and headers

Supports a more restrictive script policy

Third-party integrations may require additional configuration

Can be introduced through a staged report-only rollout

Requires ongoing testing and policy maintenance

Summary

Content Security Policy nonces let a website authorize selected inline scripts while restricting other script execution. The server generates an unpredictable value, includes it in the CSP response header, and places the same value on trusted script elements.

In ASP.NET Core, this can be implemented with cryptographic nonce generation, middleware, and Razor views. The important part is keeping the header and HTML synchronized while ensuring that untrusted content never receives an approved nonce.

Start with a policy that reflects your application's actual resource requirements, test it in report-only mode, and enforce it after reviewing violations. Combined with output encoding and safe DOM practices, a nonce-based CSP adds a useful layer of browser-side protection against XSS.

Best ASP.NET Core 11.0 Hosting Recommendation

One of the most important things when choosing a good ASP.NET Core 11.0 hosting is the feature and reliability. HostForLIFE is the leading provider of Windows hosting and affordable ASP.NET Core, their servers are optimized for PHP web applications. The performance and the uptime of the hosting service are excellent and the features of the web hosting plan are even greater than what many hosting providers ask you to pay for. 

At HostForLIFE, customers can also experience fast ASP.NET Core 11 hosting. The company invested a lot of money to ensure the best and fastest performance of the datacenters, servers, network and other facilities. Its datacenters are equipped with the top equipments like cooling system, fire detection, high speed Internet connection, and so on. That is why HostForLIFE guarantees 99.9% uptime for ASP.NET Core. And the engineers do regular maintenance and monitoring works to assure its Orchard hosting are security and always up.

Read More...

Tuesday, 20 October 2026

How to Use ASP.NET Core CancellationToken Without Stopping Work?

Leave a Comment

Let's say a user begins to download a sizable report from your ASP.NET Core API. They either terminate the request or close the browser after a few seconds. The server may continue be querying the database, contacting another API, or processing data even after the client is no longer waiting for the outcome.

 

If this occurs frequently, your program may waste network resources, database connections, and CPU time on tasks that are no longer necessary.

This is where CancellationToken becomes useful. It allows an operation to receive a cancellation request and stop when it is safe to do so.

However, adding a cancellation token to a method does not automatically stop every operation inside it. Your code must pass the token to supported APIs and handle cancellation correctly.

In this article, we will learn how to use CancellationToken in ASP.NET Core, propagate cancellation through database and HTTP calls, and avoid common mistakes in production applications.

What Is CancellationToken in C#?

CancellationToken is a .NET type that allows one part of an application to notify another part that an operation should stop.

It does not forcibly terminate a running method. Instead, it provides a cooperative cancellation mechanism. The operation must check the token or call an API that observes it.

Consider this example:

public async Task ProcessAsync(
    CancellationToken cancellationToken)
{
    cancellationToken.ThrowIfCancellationRequested();

    await Task.Delay(
        TimeSpan.FromSeconds(10),
        cancellationToken);

    Console.WriteLine("Processing completed.");
}

If cancellation is requested while Task.Delay is running, the delay can stop early by throwing an OperationCanceledException.

The important point is that the token must reach the operation that can actually respond to cancellation.

How ASP.NET Core Handles Request Cancellation

ASP.NET Core exposes HttpContext.RequestAborted, a cancellation token that indicates when the current HTTP request has been aborted.

In controller actions, ASP.NET Core can also bind a CancellationToken parameter to the request-aborted token.

For example:

[HttpGet("report")]
public async Task<IActionResult> GetReport(
    CancellationToken cancellationToken)
{
    var report = await GenerateReportAsync(
        cancellationToken);

    return Ok(report);
}

When the request is aborted, the token can signal that the operation should stop.

This is particularly useful for long-running database queries, external HTTP requests, file operations, and data processing.

However, cancellation is not guaranteed to happen immediately. Some operations may already have completed, and some dependencies may not support cancellation at every stage.

Step 1: Pass CancellationToken to Entity Framework Core

Database operations can consume resources even after a client stops waiting for the result.

Entity Framework Core provides asynchronous methods that accept a cancellation token, including ToListAsync, FirstOrDefaultAsync, and SaveChangesAsync.

Consider this example:

[HttpGet("orders")]
public async Task<IActionResult> GetOrders(
    CancellationToken cancellationToken)
{
    var orders = await _context.Orders
        .AsNoTracking()
        .OrderByDescending(o => o.CreatedAt)
        .Take(100)
        .Select(o => new
        {
            o.Id,
            o.CreatedAt,
            o.TotalAmount
        })
        .ToListAsync(cancellationToken);

    return Ok(orders);
}

The cancellation token is passed to ToListAsync. If the request is cancelled, EF Core can pass the cancellation request to the database provider.

Whether the underlying database operation stops immediately depends on the provider and database driver.

Without the token, the query may continue running even when the request is no longer useful. The exact behavior depends on the operation and provider, so test cancellation with your actual database setup.

Step 2: Cancel External HTTP Requests

ASP.NET Core applications often call external services to retrieve prices, customer details, payment status, or other information.

Use HttpClient methods that accept a cancellation token so an abandoned request does not unnecessarily wait for the external service.

public sealed class PricingService
{
    private readonly HttpClient _httpClient;

    public PricingService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<string> GetPricingAsync(
        CancellationToken cancellationToken)
    {
        using var response = await _httpClient.GetAsync(
            "api/pricing",
            cancellationToken);

        response.EnsureSuccessStatusCode();

        return await response.Content.ReadAsStringAsync(
            cancellationToken);
    }
}

Register the service with IHttpClientFactory:

builder.Services.AddHttpClient<PricingService>(client =>
{
    client.BaseAddress =
        new Uri("https://pricing.example.com/");

    client.Timeout = TimeSpan.FromSeconds(15);
});

The timeout is an additional safeguard. It does not replace request cancellation.

The external service may still have received and started processing the request before cancellation reaches it. Cancellation primarily helps your application stop waiting and release resources when the underlying operation supports it.

For critical operations, such as a payment request, do not assume that cancelling the HTTP request reverses a transaction that the external service has already completed.

Step 3: Pass Cancellation Through Every Layer

A common mistake is accepting a cancellation token in the controller but not passing it to the service or repository.

Consider a service method:

public async Task<List<Product>> GetProductsAsync(
    CancellationToken cancellationToken)
{
    return await _context.Products
        .AsNoTracking()
        .ToListAsync(cancellationToken);
}

The controller should pass its token to the service:

[HttpGet("products")]
public async Task<IActionResult> GetProducts(
    CancellationToken cancellationToken)
{
    var products = await _productService
        .GetProductsAsync(cancellationToken);

    return Ok(products);
}

This keeps cancellation flowing from the incoming request to the database operation.

The same principle applies to repository methods, file processing, HTTP calls, and other asynchronous operations.

If an intermediate method ignores the token, work inside that method may continue even after the request is cancelled.

Step 4: Handle Cancellation Without Hiding Errors

Cancellation is an expected outcome in many web applications. A user may navigate away, close a browser tab, or stop an operation.

You should handle cancellation differently from unexpected application errors.

For example:

[HttpGet("export")]
public async Task<IActionResult> Export(
    CancellationToken cancellationToken)
{
    try
    {
        var file = await _exportService.CreateAsync(
            cancellationToken);

        return File(
            file,
            "application/octet-stream",
            "export.bin");
    }
    catch (OperationCanceledException)
        when (cancellationToken.IsCancellationRequested)
    {
        // The request was cancelled.
        throw;
    }
}

This example rethrows cancellation rather than treating it as a successful export. In many controllers, no explicit catch is needed at all; allowing cancellation to propagate is simpler.

Avoid catching every exception and returning 200 OK. Doing so can hide genuine failures and make monitoring unreliable.

Also, do not assume every OperationCanceledException means the client disconnected. A linked token or an internal timeout may have caused the cancellation. Check the relevant token and handle each case according to the application's requirements.

Step 5: Combine Request Cancellation with an Operation Timeout

Sometimes an operation should stop not only when the client disconnects but also when it exceeds a time limit.

You can combine cancellation sources using a linked token.

public async Task<Result> RunOperationAsync(
    CancellationToken requestToken)
{
    using var timeoutSource =
        new CancellationTokenSource(
            TimeSpan.FromSeconds(20));

    using var linkedSource =
        CancellationTokenSource.CreateLinkedTokenSource(
            requestToken,
            timeoutSource.Token);

    return await PerformOperationAsync(
        linkedSource.Token);
}

The operation stops when either the request token or the timeout token is cancelled, provided the underlying work supports cancellation.

Keep the distinction between these cases. A client disconnect may be normal, while an operation timeout may indicate a slow dependency or an overloaded system.

CancellationToken vs. Timeout

Feature

CancellationToken

Timeout

Main purpose

Signals that an operation should stop

Limits how long an operation may run

Trigger

Cancellation requested by a caller or another source

A configured duration expires

Cooperation required

Yes, the operation must observe it

Depends on how the timeout is implemented

Common use

HTTP request cancellation

External API or database operation limits

Can be combined

Yes

Yes, using a cancellation source or supported API timeout

A timeout can be implemented through a cancellation token, but the concepts are not identical. Cancellation describes a request to stop; a timeout is one possible reason for that request.

Common Mistakes to Avoid

Accepting a Token but Never Using It

Adding a CancellationToken parameter has little value if the code never checks it or passes it to an operation that supports cancellation.

Using Task.Run for Every Async Operation

Wrapping database or HTTP calls in Task.Run does not automatically make them cancellable. Use the asynchronous APIs provided by the underlying libraries and pass the token directly.

Swallowing OperationCanceledException

Catching cancellation and returning a successful result can make the application report work as completed when it was actually interrupted.

Assuming Cancellation Rolls Back Completed Work

Cancellation does not automatically undo a database update, payment, or external side effect that has already completed. Use transactions, idempotency, or explicit compensation where appropriate.

Forgetting Background Jobs

An HTTP request token is intended to represent the lifetime of that request. If work must continue after the request ends, such as generating a report in the background, use a durable job queue or hosted background service with its own cancellation and shutdown strategy.

Best Practices for Production Applications

  1. Accept CancellationToken in asynchronous methods that perform cancellable work.

  2. Pass the token through controller, service, repository, and dependency layers.

  3. Use asynchronous database and HTTP APIs with cancellation support.

  4. Set realistic timeouts for external dependencies.

  5. Distinguish client cancellation from timeouts and unexpected exceptions.

  6. Avoid starting background work that depends on the lifetime of an HTTP request.

  7. Log cancellation carefully without treating every cancelled request as an application failure.

  8. Test cancellation with slow database queries, external HTTP calls, and long-running operations.

  9. Do not rely on cancellation to reverse completed transactions or external side effects.

  10. Ensure that application shutdown and background processing have their own cancellation policies.

Advantages and Disadvantages

Advantages

  • Reduces unnecessary work when requests are abandoned.

  • Can release database and network resources sooner.

  • Helps limit the duration of long-running operations.

  • Supports cleaner shutdown and cancellation flows.

  • Works across many asynchronous .NET APIs.

Disadvantages

  • Requires cooperation from the operations being cancelled.

  • Cancellation may not stop work immediately.

  • Can complicate error handling if different cancellation sources are mixed together.

  • Does not automatically undo completed side effects.

  • Some third-party libraries and external services may not fully support cancellation.

Summary

CancellationToken is an important tool for building responsive ASP.NET Core applications. It allows request cancellation to flow through database queries, HTTP calls, and other asynchronous operations so the application can stop unnecessary work when possible.

The most important practice is to propagate the token through every layer and pass it to APIs that support cancellation. Handle cancellation separately from unexpected errors, and remember that stopping an operation does not automatically undo work that has already completed.

Used correctly, cancellation helps your application use resources more responsibly, especially when handling slow requests and long-running operations.

Best ASP.NET Core 11.0 Hosting Recommendation

One of the most important things when choosing a good ASP.NET Core 11.0 hosting is the feature and reliability. HostForLIFE is the leading provider of Windows hosting and affordable ASP.NET Core, their servers are optimized for PHP web applications. The performance and the uptime of the hosting service are excellent and the features of the web hosting plan are even greater than what many hosting providers ask you to pay for. 

At HostForLIFE, customers can also experience fast ASP.NET Core 11 hosting. The company invested a lot of money to ensure the best and fastest performance of the datacenters, servers, network and other facilities. Its datacenters are equipped with the top equipments like cooling system, fire detection, high speed Internet connection, and so on. That is why HostForLIFE guarantees 99.9% uptime for ASP.NET Core. And the engineers do regular maintenance and monitoring works to assure its Orchard hosting are security and always up.

Read More...

Monday, 12 October 2026

Runtime Async in.NET 11: The Real Changes

Leave a Comment

async and await have looked the same since C# 5.

This concerns what happens in he background, you write a method, mark it async, call with await, and the compiler turns it into an action that can pause and resume.

That translation has always been the compiler's.

In .NET 11, the runtime takes it back.

 

Throw this article i will explain what the compiler does today, why it costs what it costs, and what runtime async replaces it with.

First, what await actually has to solve

Take a simple method:

public async Task<string> FetchAsync(string url)
{
    return await service.GetAsync(url);
}

When we reach the await, the network call might take 200 milliseconds.

During those 200 milliseconds, there is no work to do. Blocking a thread to sit and wait would be wasteful.

So the method pauses, gives the thread back, and resumes later when the response arrives.

That raises a problem. A normal method lives on the stack. It runs top to bottom.

If you pause in the middle and come back later, possibly on a completely different thread, where did all our variables go?

Something has to remember.

How it has worked since 2012

Since C# 5, the compiler solves this by rewriting your method.

It takes your method and turns it into a small object, a state machine. That hold the following information:

  • A position marker. Which await did we stop at?

  • Your variables.

  • A resume method. Your original method body, rewritten so it can be re-entered and jump back to where it stopped.

It works. It has worked well for thirteen years. But it comes costs.

What that design costs you

It allocates. When an await genuinely has to wait, that bookmark object moves to the heap. One allocation, every call.

It saves more than it needs to. The compiler has no way of knowing which variables will actually matter when execution resumes, so it keeps everything that might be needed.

What .NET 11 does differently

Runtime async moves the job from the compiler to the runtime.

The compiler stops generating the state machine object entirely. Instead, it leaves your method looking like you wrote it, with a marker that says "this method can pause here".

The runtime handle the pausing and resuming themselves.

This change a lot, because the runtime knows things the compiler never could:

  • It saves only what is genuinely needed.

  • It reuses objects instead of allocating fresh ones at every pause point.

What you actually notice

Cleaner stack traces

  • Live stack traces show your real call chain.

  • Debugging improves with it.

Less memory, a bit more speed

A community benchmark of a loop with ten awaits measured:


Time

Memory allocated

Old approach

192.4 ns

1.61 KB

Runtime async

128.0 ns

928 B

About a third faster.

But if your await is a database query that takes 30 milliseconds, the query dominates and you will not notice 60 nanoseconds.

Trying it out

One property in your project file:

<PropertyGroup>
  <TargetFramework>net11.0</TargetFramework>
  <Features>runtime-async=on</Features>
</PropertyGroup>

Best ASP.NET Core 11.0 Hosting Recommendation

One of the most important things when choosing a good ASP.NET Core 11.0 hosting is the feature and reliability. HostForLIFE is the leading provider of Windows hosting and affordable ASP.NET Core, their servers are optimized for PHP web applications. The performance and the uptime of the hosting service are excellent and the features of the web hosting plan are even greater than what many hosting providers ask you to pay for. 

At HostForLIFE, customers can also experience fast ASP.NET Core 11 hosting. The company invested a lot of money to ensure the best and fastest performance of the datacenters, servers, network and other facilities. Its datacenters are equipped with the top equipments like cooling system, fire detection, high speed Internet connection, and so on. That is why HostForLIFE guarantees 99.9% uptime for ASP.NET Core. And the engineers do regular maintenance and monitoring works to assure its Orchard hosting are security and always up.

 

Read More...

Tuesday, 6 October 2026

Strong API Verification: Creating ASP.NET Unit Tests Fundamental Webhook Controllers with FluentAssertions and xUnit

Leave a Comment

Making sure your endpoints can reliably handle incoming requests is crucial when developing automated incident response pipelines or custom webhook receivers in ASP.NET Core. A webhook controller serves as a conduit between the internal logic of your application and external cloud monitoring services, such as Azure Monitor. Your incident response pipeline quietly fails if it crashes on corrupted data, mishandles security tokens, or is unable to parse a payload.


You require thorough unit tests to ensure great dependability. We will use xUnit, Moq, and FluentAssertions to create reliable unit tests for an ASP.NET Core webhook controller in this thorough tutorial.

Why Unit Test Webhook Controllers?

Webhook endpoints have specific testing requirements that set them apart from standard CRUD controllers:

  • Security Verification: You must ensure unauthorized requests lacking valid secret tokens are rejected instantly with a 401 Unauthorized.

  • Payload Resilience: External services can occasionally send malformed or incomplete data; your controller must gracefully return a 400 Bad Request.

  • Behavior Verification: Beyond returning correct HTTP status codes, you need to verify that incoming alerts are correctly logged and passed down to downstream services.

Step 1: Install Required Test NuGet Packages

Before writing tests, ensure your testing project has the necessary packages installed. Navigate to your YourSolution.UnitTests project and run:

Bash

dotnet add package xunit
dotnet add package xunit.runner.visualstudio
dotnet add package Moq
dotnet add package FluentAssertions
dotnet add package Microsoft.AspNetCore.Mvc.Core
dotnet add package Microsoft.NET.Test.Sdk

Step 2: Setting Up the Test Class and Mocks

Because your webhook controller depends on ILogger<WebhookController> for logging alert events, you will use Moq to create a mock logger. We will use FluentAssertions for clean, expressive assertions.

Create a test class named WebhookControllerTests.cs:

using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
using YourSolution.API.Controllers;
using YourSolution.API.Models;

namespace YourSolution.UnitTests.Controllers
{
    public class WebhookControllerTests
    {
        private readonly Mock<ILogger<WebhookController>> _loggerMock;
        private readonly WebhookController _controller;
        private const string ValidToken = "YourSuperSecretToken123";

        public WebhookControllerTests()
        {
            _loggerMock = new Mock<ILogger<WebhookController>>();
            _controller = new WebhookController(_loggerMock.Object);
        }
    }
}

Step 3: Writing Unit Tests for Success, Security, and Edge Cases

A complete test suite for a webhook controller should cover three distinct scenarios: valid payloads with correct authorization, unauthorized token attempts, and malformed payloads.

1. Testing the Success Path (Valid Payload & Token)

Verify that when a well-formed Azure Common Alert Schema payload arrives with the correct secret token, the controller returns 200 OK and logs the alert.

[Fact]
public async Task ReceiveAzureAlert_ReturnsOk_WhenPayloadIsValidAndTokenIsCorrect()
{
    // Arrange
    var payload = new AzureAlertPayload
    {
        SchemaId = "azureMonitorCommonAlertSchema",
        Data = new AlertData
        {
            Essentials = new AlertEssentials
            {
                AlertRule = "API-High-Failure-Rate",
                Severity = "Sev2",
                MonitorCondition = "Fired",
                FiredDateTime = DateTime.UtcNow,
                Description = "Failure rate exceeded 5%."
            }
        }
    };

    // Act
    var result = _controller.ReceiveAzureAlert(ValidToken, payload);

    // Assert
    result.Should().BeOfType<OkObjectResult>();
    var okResult = result as OkObjectResult;
    okResult?.StatusCode.Should().Be(200);

    // Verify that the logger captured the alert event
    _loggerMock.Verify(
        x => x.Log(
            LogLevel.Warning,
            It.IsAny<EventId>(),
            It.Is<It.IsAnyType>((v, t) => v.ToString()!.Contains("Azure Alert Fired")),
            It.IsAny<Exception>(),
            It.Is<Func<It.IsAnyType, Exception?, string>>((v, t) => true)!),
        Times.Once);
}

2. Testing Security Constraints (Invalid Token)

Ensure that requests with incorrect or missing secret tokens are rejected immediately.

[Fact]
public async Task ReceiveAzureAlert_ReturnsUnauthorized_WhenTokenIsInvalid()
{
    // Arrange
    var payload = new AzureAlertPayload();
    const string invalidToken = "WrongToken999";

    // Act
    var result = _controller.ReceiveAzureAlert(invalidToken, payload);

    // Assert
    result.Should().BeOfType<UnauthorizedObjectResult>();
    var unauthorizedResult = result as UnauthorizedObjectResult;
    unauthorizedResult?.StatusCode.Should().Be(401);
}

3. Testing Data Validation (Malformed Payloads)

Verify that if Azure sends a payload where essential data is missing, the controller rejects it gracefully.

[Fact]
public async Task ReceiveAzureAlert_ReturnsBadRequest_WhenPayloadEssentialsAreNull()
{
    // Arrange
    var malformedPayload = new AzureAlertPayload
    {
        Data = new AlertData
        {
            Essentials = null! // Simulating missing essentials
        }
    };

    // Act
    var result = _controller.ReceiveAzureAlert(ValidToken, malformedPayload);

    // Assert
    result.Should().BeOfType<BadRequestObjectResult>();
    var badRequestResult = result as BadRequestObjectResult;
    badRequestResult?.StatusCode.Should().Be(400);
}

Summary

By unit testing your webhook controller with xUnit, Moq, and FluentAssertions, you ensure that your cloud monitoring integration is resilient, secure, and production-ready:

  • Payload Verification: Guarantees that Azure Common Alert Schema data is parsed correctly without unhandled null reference exceptions.

  • Security Enforcement: Validates that secret token checks prevent unauthorized third-party requests.

  • Behavioral Confirmation: Asserts that critical alerts are properly logged and processed.

Best ASP.NET Core 11.0 Hosting Recommendation

One of the most important things when choosing a good ASP.NET Core 11.0 hosting is the feature and reliability. HostForLIFE is the leading provider of Windows hosting and affordable ASP.NET Core, their servers are optimized for PHP web applications. The performance and the uptime of the hosting service are excellent and the features of the web hosting plan are even greater than what many hosting providers ask you to pay for. 

At HostForLIFE, customers can also experience fast ASP.NET Core 11 hosting. The company invested a lot of money to ensure the best and fastest performance of the datacenters, servers, network and other facilities. Its datacenters are equipped with the top equipments like cooling system, fire detection, high speed Internet connection, and so on. That is why HostForLIFE guarantees 99.9% uptime for ASP.NET Core. And the engineers do regular maintenance and monitoring works to assure its Orchard hosting are security and always up.

 

Read More...

Tuesday, 29 September 2026

How to Record Errors Before Your App Closes in.NET 11 Crash Reports?

Leave a Comment

It is possible for a production application to fail in a way that leaves very little valuable information.

A native failure might end the process, an exception could get past the application's regular error-handling pipeline, or the operating system could end the process before your regular logging code has a chance to complete generating a meaningful record.


Ordinary application logging is not necessarily sufficient in these situations. .NET has many levels for analyzing process failures, including as crash dumps, EventPipe diagnostics, unhandled-exception events, and runtime-generated JSON crash reports in.NET 11. When a process crashes, the crash-report feature—which is set up via runtime environment variables—provides thread and stack-frame information.

The important production principle is:

Do not depend on one error-handling mechanism to diagnose every type of application failure.

Instead, combine application-level logging with runtime crash diagnostics.

Why Normal Exception Handling Is Not Enough

Most application errors can be handled normally:

try
{
    await ProcessOrderAsync();
}
catch (Exception ex)
{
    logger.LogError(ex, "Order processing failed");
}

This works when the exception reaches the catch block.

But some failures happen outside that path.

For example:

Application Code
      |
      v
Exception
      |
      +---- try/catch
      |       |
      |       v
      |     Logged
      |
      +---- No handler
              |
              v
        Process termination

A process-level failure needs a different diagnostic strategy.

Microsoft's exception guidance also distinguishes handling expected application exceptions from failures that leave the process in an undefined state.

What Is a Crash Report?

A crash report is diagnostic information generated when the runtime encounters a process crash.

In .NET 11, the runtime can generate a JSON-formatted crash report when DOTNET_EnableCrashReport is enabled. The report contains information about threads and stack frames associated with the crashing application.

Conceptually:

Application
    |
    X
Process Crash
    |
    v
.NET Runtime
    |
    +---- Crash Report
    |
    +---- Crash Dump

These outputs serve different purposes.

A crash report is relatively focused diagnostic information.

A dump is a snapshot of the process state and can contain much more information, including application memory.

Enable Crash Reports in .NET 11

The runtime setting is:

DOTNET_EnableCrashReport=1

When enabled, .NET generates a JSON crash report when the runtime supports the crash-report path.

The generated filename is based on the dump path or dump name with:

.crashreport.json

appended.

For example:

export DOTNET_EnableCrashReport=1

Then start the application normally.

The setting can be configured through the deployment environment rather than hard-coded into application source code.

Combine Crash Reports With Crash Dumps

A crash report is useful, but a dump provides a much deeper diagnostic snapshot.

.NET supports automatic crash dump collection through environment variables such as:

DOTNET_DbgEnableMiniDump=1

You can also specify the dump type:

DOTNET_DbgMiniDumpType=2

The documented dump types include:

Value

Type

Purpose

1

Mini

Smaller diagnostic dump

2

Heap

More comprehensive process information

3

Triage

Smaller dump with reduced personal information

4

Full

Full process memory

The default dump type is Heap.

For many production incidents, a heap dump provides substantially more information than a small crash report.

However, larger dumps also have larger storage and security implications.

Configure the Dump Location

The dump path can be controlled with:

DOTNET_DbgMiniDumpName=/var/log/myapp/crash-%p-%t.dmp

.NET supports template values in dump filenames.

For example:

%p = Process ID
%e = Executable name
%h = Host name
%t = Timestamp

This is useful when several instances write diagnostic files to the same directory.

A containerized deployment might use a writable diagnostic directory:

/diagnostics/
    crash-1234-1720000000.dmp
    crash-1234-1720000000.dmp.crashreport.json

The directory must be writable by the process account.

A Practical Production Configuration

A Linux deployment could use:

DOTNET_EnableCrashReport=1
DOTNET_DbgEnableMiniDump=1
DOTNET_DbgMiniDumpType=2
DOTNET_DbgMiniDumpName=/diagnostics/crash-%p-%t.dmp

This provides two diagnostic artifacts:

Crash
  |
  +---- .crashreport.json
  |
  +---- .dmp

The JSON report gives quick information.

The dump can be used for deeper investigation.

Why You Should Not Rely Only on AppDomain.UnhandledException

.NET provides:

AppDomain.CurrentDomain.UnhandledException

It can be useful for logging an exception that has escaped normal exception handling.

For example:

AppDomain.CurrentDomain.UnhandledException += (_, args) =>
{
    if (args.ExceptionObject is Exception ex)
    {
        Console.Error.WriteLine(ex);
    }
};

However, this should not be treated as a universal crash-capture mechanism.

The event is a notification for uncaught exceptions. Microsoft also cautions that the handler runs during application termination and should avoid operations that can block or introduce additional failures.

A logging call that depends on a failing database, network connection, or another unhealthy subsystem may therefore be unreliable during a crash.

Keep Crash Handlers Simple

A crash-path handler should do as little work as possible.

Avoid:

AppDomain.CurrentDomain.UnhandledException += async (_, args) =>
{
    await SendToDatabaseAsync(args);
    await NotifyExternalServiceAsync(args);
};

This introduces additional dependencies into the failure path.

Prefer a simple local diagnostic operation:

AppDomain.CurrentDomain.UnhandledException += (_, args) =>
{
    Console.Error.WriteLine(
        $"Unhandled exception: {args.ExceptionObject}");
};

The runtime-level crash report or dump should provide the deeper diagnostic information.

ASP.NET Core Errors Are a Different Layer

ASP.NET Core applications have their own request-level error-handling pipeline.

For example:

HTTP Request
     |
     v
Middleware
     |
     +---- Exception
     |
     v
Error Handler
     |
     v
HTTP 500

A request exception does not necessarily mean that the entire process has crashed.

The ASP.NET Core developer exception page and exception-handling middleware operate at the HTTP pipeline level.

Therefore, an ASP.NET Core production application should typically have:

Request Errors
     |
     v
Application Logging


Process Crashes
     |
     +---- Crash Report
     |
     +---- Crash Dump

These mechanisms solve different problems.

What About Background Services?

Background workers are another important source of failures.

Consider:

public class Worker : BackgroundService
{
    protected override async Task ExecuteAsync(
        CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            await ProcessAsync(stoppingToken);
        }
    }
}

A failure inside the worker needs to be handled according to the intended lifecycle of that service.

For recoverable operation failures:

try
{
    await ProcessAsync(stoppingToken);
}
catch (Exception ex)
{
    logger.LogError(ex, "Worker operation failed");
}

For a failure that makes the process unsafe to continue, allowing the process to terminate may be preferable to leaving the application in an unknown state.

The crash diagnostics then provide evidence for investigating the failure.

Native Crashes Need Different Thinking

Not every process termination is a managed exception.

A native dependency can cause a process-level failure without passing through a normal C# catch block.

For example:

C# Application
      |
      v
Native Library
      |
      X
Native Crash
      |
      v
Process Terminates

This is where crash dumps become especially useful.

A dump contains a snapshot of process state that can be analyzed after the process has terminated.

Use dotnet-dump for Manual Investigation

Automatic crash dumps are useful for unexpected failures, but sometimes the application is still alive and unhealthy.

For example:

High CPU
Deadlock
Memory pressure
Thread starvation
Unresponsive application

In these situations, use dotnet-dump to collect a dump from the running process.

A basic command is:

dotnet-dump collect -p <process-id>

Microsoft documents dotnet-dump as a cross-platform diagnostic tool for collecting and analyzing .NET process dumps.

This is different from automatic crash collection.

Automatic crash
       |
       v
Runtime creates dump


Suspected unhealthy process
       |
       v
Engineer collects dump

Both approaches are useful.

Be Careful With Containers

Crash dumps can be large.

Microsoft notes that collecting full or heap dumps can cause substantial virtual memory to be paged into the process and, in a memory-constrained container, this additional memory pressure can cause the container to be terminated.

Therefore, test dump collection under realistic resource limits.

For example:

Container
  |
  +---- Application Memory
  |
  +---- Runtime
  |
  +---- Dump Collection
          |
          v
      Extra Memory

Do not discover this behavior for the first time during a production incident.

Protect Crash Dumps

A dump can contain sensitive information because it may include process memory.

That can include:

  • User data

  • Access tokens

  • Connection information

  • Request data

  • Configuration values

  • Secrets held in memory

Microsoft explicitly warns that dumps may contain sensitive information and should be handled according to the application's security requirements.

Treat crash dumps as sensitive production artifacts.

Use:

  • Restricted filesystem permissions

  • Encrypted storage

  • Controlled retention

  • Limited access

  • Secure transfer

  • Automatic cleanup

Do not upload production dumps to an unrestricted shared location.

Add Crash Correlation

A useful production design combines normal logs with crash artifacts.

For example:

Request
   |
   v
Trace ID
   |
   +---- Application Logs
   |
   +---- Metrics
   |
   +---- Crash Report
   |
   +---- Crash Dump

If the process crashes, the surrounding logs can provide the operational context while the crash artifacts provide process-level information.

Where possible, include application version and deployment information in your logs:

Version: 2026.09.28.4
Environment: Production
Instance: app-07

This helps determine whether the crash corresponds to a particular deployment.

Test the Crash Pipeline

Do not wait for a real crash to discover that the dump directory is not writable.

Create a controlled test environment and deliberately trigger a process failure.

For example:

Environment.FailFast(
    "Crash diagnostics test");

This should be done only in a controlled test environment.

Then verify:

[ ] Process terminates
[ ] Crash report appears
[ ] Dump appears
[ ] Filename is correct
[ ] File permissions are correct
[ ] Diagnostic artifact is readable
[ ] Logs identify the deployment
[ ] Artifact retention works

This validates the entire pipeline.

Common Mistakes

Depending Only on try/catch

A process-level failure may never reach the application's exception handler.

Depending Only on UnhandledException

The event provides notification, but it is not equivalent to a complete process dump.

Performing Network Operations During a Crash

The failing component may be the network, database, or another dependency.

Creating Full Dumps Automatically Without Capacity Planning

Large dumps can consume significant storage and resources.

Storing Dumps Indefinitely

Dumps can contain sensitive memory contents.

Exposing Diagnostic Endpoints Publicly

Diagnostic interfaces should be protected.

Never Testing Crash Collection

A configuration that has never been tested is not a reliable incident-response mechanism.

Best Practices

  1. Keep normal exception handling for recoverable application errors.

  2. Use runtime crash reports for process-level diagnostic information.

  3. Enable automatic crash dumps for services where post-crash investigation is important.

  4. Select the smallest dump type that provides the required diagnostic information.

  5. Protect crash artifacts as sensitive data.

  6. Use dotnet-dump for manual investigation of unhealthy but still-running processes.

  7. Test crash collection in the same deployment environment used in production.

  8. Verify container memory and storage limits before enabling automatic dumps.

  9. Keep crash-path logging simple and local.

  10. Correlate crash artifacts with application version and deployment information.

  11. Establish retention and deletion policies for dumps.

  12. Document who can access production diagnostic artifacts.

Crash Report vs Crash Dump

Capability

Crash Report

Crash Dump

JSON format

Yes

No

Thread information

Yes

Yes

Stack information

Yes

Yes

Process memory

No

Depending on dump type

Easy to inspect

Yes

Requires diagnostic tools

Storage size

Relatively small

Can be large

Deep investigation

Limited

Stronger

Sensitive data exposure

Lower

Potentially high

The appropriate choice depends on the incident.

A crash report may be enough to identify the failing thread or stack.

A dump is more useful when you need to inspect application state in depth.

Production Configuration Checklist

Crash Reporting
[ ] DOTNET_EnableCrashReport configured where supported
[ ] Crash report location is writable
[ ] Crash report retention is defined

Crash Dumps
[ ] DOTNET_DbgEnableMiniDump configured where required
[ ] Dump type selected intentionally
[ ] Dump location has sufficient storage
[ ] Container memory limits tested
[ ] Dump permissions are restricted

Application Logging
[ ] Unhandled exceptions are logged
[ ] Background worker failures are logged
[ ] Deployment/version information is available
[ ] Logs are correlated with instance information

Security
[ ] Crash artifacts are treated as sensitive
[ ] Access is restricted
[ ] Storage is encrypted
[ ] Retention policy exists
[ ] Automatic cleanup is configured

Testing
[ ] Controlled crash has been tested
[ ] Crash report was generated
[ ] Dump was generated
[ ] Dump can be analyzed
[ ] Recovery behavior was verified

Summary

.NET 11 provides another useful layer for diagnosing process failures through runtime-generated crash reports. Setting DOTNET_EnableCrashReport=1 enables a JSON-formatted crash report containing thread and stack-frame information for supported crash scenarios.

For deeper investigation, automatic crash dumps can capture substantially more process state. .NET supports configuring these through environment variables such as DOTNET_DbgEnableMiniDump, DOTNET_DbgMiniDumpType, and DOTNET_DbgMiniDumpName.

The practical architecture is:

Expected Application Error
        |
        v
try/catch + Application Logging


Unexpected Process Failure
        |
        +---- Crash Report
        |
        +---- Crash Dump
        |
        v
Post-Crash Investigation

The important part is not simply enabling a runtime variable. A production-ready crash-diagnostic system also needs secure storage, controlled retention, sufficient disk and memory capacity, and a tested investigation workflow.

When the application exits unexpectedly, the goal is to preserve enough evidence to answer three questions:

What failed?
Where did it fail?
What was the process doing when it failed?

With application logging, crash reports, and appropriately configured dumps working together, those questions become much easier to answer.

ASP.NET Core 11.0 Hosting Recommendation

One of the most important things when choosing a good ASP.NET Core 11 hosting is the feature and reliability. HostForLIFE is the leading provider of Windows hosting and affordable ASP.NET Core, their servers are optimized for PHP web applications. The performance and the uptime of the hosting service are excellent and the features of the web hosting plan are even greater than what many hosting providers ask you to pay for. 

At HostForLIFE.eu, customers can also experience fast ASP.NET Core hosting. The company invested a lot of money to ensure the best and fastest performance of the datacenters, servers, network and other facilities. Its datacenters are equipped with the top equipments like cooling system, fire detection, high speed Internet connection, and so on. That is why HostForLIFE.eu guarantees 99.9% uptime for ASP.NET Core. And the engineers do regular maintenance and monitoring works to assure its Orchard hosting are security and always up.

Read More...