When upgrading an ASP.NET Core application, one of the biggest concerns is whether existing controllers and business logic will continue working without modification.
Consider an application that has been running on .NET 10 for several months. It contains multiple API controllers, authentication middleware, dependency injection registrations, database services, and third-party integrations. The application is stable, and the team wants to move to .NET 11 to benefit from the latest framework improvements.
The obvious question is whether the team needs to rewrite its controllers or change the existing API architecture.
In most cases, the answer is no.
ASP.NET Core continues to support the familiar MVC controller model in .NET 11. Existing attributes such as [ApiController], [HttpGet], [HttpPost], [Route], and [Authorize] remain part of the development experience.
The migration mainly involves updating the target framework, reviewing dependencies, checking framework changes, and validating the application's existing behavior.
In this article, we will upgrade a small ASP.NET Core Web API from .NET 10 to .NET 11 while keeping the existing controllers unchanged.
Understanding the ASP.NET Core Migration
ASP.NET Core applications generally follow a familiar architecture.
The controller handles HTTP requests, services contain business logic, and repositories or database contexts handle data access.
For example:
Client Application
|
v
ASP.NET Core Middleware
|
v
API Controller
|
v
Business Service
|
v
Database Repository
|
v
Database
Moving to .NET 11 does not require changing this architecture.
The framework continues supporting MVC controllers, dependency injection, routing, model binding, filters, authentication, and authorization.
However, some framework-level behaviors and dependencies have changed. These should be reviewed before upgrading a production application.
Step 1: Review the Existing .NET 10 Project
Let's start with a typical ASP.NET Core Web API project.
The existing project file might look like this:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
The application uses the standard ASP.NET Core Web SDK.
There is no requirement to replace this SDK or move to Minimal APIs.
The first step is to identify the current target framework, package references, SDK configuration, and any custom middleware.
If your solution contains multiple projects, review each project individually. Updating the API project alone may not be sufficient if shared libraries still target older frameworks.
Step 2: Update the Target Framework to .NET 11
The first code change is usually the project configuration.
Change:
<TargetFramework>net10.0</TargetFramework>
To:
<TargetFramework>net11.0</TargetFramework>
The updated project file becomes:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net11.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
If your solution uses a global.json file, check its SDK version as well.
For example:
{
"sdk": {
"version": "11.0.100"
}
}
Use an SDK version that is actually installed in your development or build environment.
This prevents the CLI from selecting an older SDK when building the solution.
Step 3: Keep Your Existing Controllers
Now let's look at the most important part of this migration.
Suppose your .NET 10 application contains the following controller:
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet]
public IActionResult GetProducts()
{
var products = new[]
{
new { Id = 1, Name = "Laptop", Price = 65000 },
new { Id = 2, Name = "Keyboard", Price = 2500 }
};
return Ok(products);
}
[HttpGet("{id:int}")]
public IActionResult GetProduct(int id)
{
return Ok(new
{
Id = id,
Name = "Laptop",
Price = 65000
});
}
[HttpPost]
public IActionResult CreateProduct(
[FromBody] ProductRequest request)
{
return CreatedAtAction(
nameof(GetProduct),
new { id = 3 },
request);
}
}
public record ProductRequest(
string Name,
decimal Price);
This controller uses standard ASP.NET Core MVC features.
After changing the project target framework to .NET 11, you do not need to rewrite these actions.
The routing attributes, action methods, model binding, and response types continue working.
This is important for applications containing dozens or hundreds of controllers. A framework upgrade should not automatically become a controller refactoring project.
Step 4: Review Program.cs
The next place to check is the application's startup configuration.
A typical .NET 10 application might contain:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddScoped<IProductService, ProductService>();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
This configuration remains a valid starting point in .NET 11.
The controller registration, dependency injection, authentication, authorization, and endpoint mapping do not need to be replaced with a different programming model.
However, review custom middleware and framework integrations before deployment.
For example, .NET 11 introduces automatic CSRF protection for endpoints that require antiforgery validation. MVC actions using [ValidateAntiForgeryToken] or [AutoValidateAntiforgeryToken] participate in this behavior.
A normal JSON-based Web API action using [HttpPost] does not automatically become subject to form antiforgery validation simply because the application targets .NET 11.
This distinction is important when troubleshooting unexpected HTTP 400 responses after an upgrade.
Step 5: Update Framework-Related NuGet Packages
Updating the target framework does not automatically update every package referenced by your project.
For example, an existing project might contain:
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.JsonPatch"
Version="10.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer"
Version="10.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools"
Version="10.0.0" />
</ItemGroup>
Review these dependencies and update compatible framework-related packages to their appropriate .NET 11 versions.
For example:
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.JsonPatch"
Version="11.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer"
Version="11.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools"
Version="11.0.0" />
</ItemGroup>
The versions shown are examples. Verify the actual package versions available for your migration and check compatibility with your database provider.
You can inspect the current dependencies using:
dotnet list package
dotnet list package --include-transitive
Pay particular attention to packages that integrate with MVC, authentication, OpenAPI, and Entity Framework Core.
Step 6: Review the ASP.NET Core 11 Changes That May Affect Existing Applications
Although most controller code can remain unchanged, some framework changes deserve attention.
Automatic CSRF protection
.NET 11 introduces automatic CSRF protection that checks request origin information for endpoints requiring antiforgery validation.
This affects MVC actions using antiforgery attributes and form-based scenarios.
If a legitimate browser request starts returning HTTP 400, check the request's Origin and Sec-Fetch-Site headers and review the configured CORS policy.
Do not disable antiforgery protection simply to make the request succeed. First establish whether the endpoint is actually vulnerable to cross-site request forgery.
OpenAPI dependency changes
ASP.NET Core 11 upgrades its Microsoft.OpenApi dependency to version 3.x.
If your application uses custom OpenAPI document, operation, or schema transformers, review those implementations.
The transformer interfaces remain familiar, but the underlying OpenAPI object model has changed.
Applications that only use standard OpenAPI generation may require little or no code modification.
Middleware and compatibility changes
Some older framework components and compatibility options have been removed.
For example, the ConcurrencyLimiter middleware has been removed. Applications using that middleware should review their request-limiting implementation and move to the supported rate-limiting approach where appropriate.
Do not assume that every middleware registration from an older application will remain available indefinitely.
Step 7: Build and Test the Existing Controllers
Once the project configuration and dependencies are updated, restore the packages and build the solution.
dotnet restore
dotnet build -c Release
Next, run the existing unit tests:
dotnet test -c Release
For a controller-based API, I would specifically test:
Existing route URLs and HTTP methods.
Request model binding and validation.
Authentication and authorization.
Dependency injection.
Database operations.
Exception handling and response codes.
JSON serialization and deserialization.
Swagger or OpenAPI document generation.
If your application uses integration tests with WebApplicationFactory, run those against the upgraded application as well.
The objective is to verify that existing API contracts remain stable.
A successful compilation is not enough. Existing mobile applications, web clients, and third-party integrations may depend on exact response formats and status codes.
Common Migration Problems
Problem | Possible reason | What to check |
|---|
Controller namespace or API reference errors | Incompatible package references | Review ASP.NET Core package versions |
HTTP 400 responses | Antiforgery validation or request configuration | Inspect request headers and endpoint metadata |
OpenAPI transformer compilation errors | Microsoft.OpenApi 3.x changes | Update custom transformers |
Middleware registration fails | Removed framework component | Replace with a supported implementation |
Dependency injection errors | Incompatible service registration or package | Review service registrations and dependencies |
Build works but runtime fails | Behavioral changes or missing dependencies | Run integration tests |
Best Practices for Upgrading Existing Controllers
For production applications, I recommend keeping the migration focused.
Do not rename controllers, change routes, or redesign services just because you are moving to a newer framework.
Keep API contracts stable and separate framework migration changes from business feature changes.
Update dependencies in a controlled manner and review breaking changes before deployment.
Run integration tests against the actual application and verify authentication, authorization, validation, and database operations.
Finally, deploy to staging before production. Monitor HTTP errors, request latency, application startup, and dependency failures.
Advantages and Disadvantages
Advantages
Existing controllers can continue working without rewriting action methods.
The existing MVC architecture can be retained.
Framework improvements can be adopted with limited application changes.
Existing API contracts can remain stable.
Teams can focus migration efforts on configuration and compatibility.
Disadvantages
Some dependencies may require upgrades.
Custom middleware may need replacement.
Security behavior changes require testing.
Custom OpenAPI integrations may need code changes.
Large applications still require comprehensive regression testing.
Summary
Moving an existing ASP.NET Core application from .NET 10 to .NET 11 does not mean replacing its controllers or changing its application architecture.
The existing MVC controller model remains supported, including routing attributes, model binding, dependency injection, and action methods.
Most migration work involves updating the target framework, reviewing NuGet dependencies, checking startup configuration, and validating framework-level changes.
Pay particular attention to automatic CSRF protection, OpenAPI dependency changes, and removed middleware components.
My recommendation is to keep your existing controllers and business logic intact unless a specific compatibility issue requires a change. Upgrade the framework first, test the existing API contracts, and introduce refactoring separately.
This approach keeps the migration manageable and reduces the risk of introducing unrelated bugs into an application that is already working in production.
Join the conversation! Your thoughts help the community grow.