Содержание
Зачем мигрировать и когда
.NET Framework 4.8 — финальная версия «старого» .NET. Microsoft поддерживает его, но новых фич не добавляет. Всё новое — в .NET 10.
.NET 10 вышел в ноябре 2025, это LTS-релиз с поддержкой до ноября 2028. После переезда откроются фичи C# 12–14 и современный EF Core. Причины мигрировать:
- Производительность: .NET 10 в 2–5 раз быстрее Framework на типичных API-нагрузках
- Кроссплатформенность: Linux-контейнеры, Docker, меньше затрат на хостинг
- C# 14 и современные паттерны
- Native AOT — маленькие бинарники, быстрый старт
Что принципиально изменилось
| .NET Framework | .NET 10 |
|---|---|
Global.asax |
Program.cs |
Web.config |
appsettings.json |
Startup.cs (OWIN) |
WebApplication.CreateBuilder() |
System.Web.HttpContext |
Microsoft.AspNetCore.Http.HttpContext |
HttpModule / HttpHandler |
Middleware |
WebForms |
Blazor / Razor Pages / MVC |
WCF |
gRPC / REST / SignalR |
| Synchronous controllers | Async по умолчанию |
Шаг 1 — Оцени масштаб
Перед началом запусти .NET Upgrade Assistant:
dotnet tool install -g upgrade-assistant
upgrade-assistant analyze ./YourSolution.sln
Покажет:
- Какие NuGet-пакеты несовместимы
- Какие API устарели или убраны
- Оценку сложности миграции по каждому проекту
Плюс проверь .NET Compatibility Analyzer — вставляешь код, получаешь список проблем.
Шаг 2 — Обнови project file
Старый формат .csproj заменяется на SDK-style:
<!-- Было (.NET Framework) -->
<Project ToolsVersion="15.0" xmlns="...">
<Import Project="$(MSBuildExtensionsPath)\..." />
<PropertyGroup>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="System.Web" />
...сотни строк References...
</ItemGroup>
</Project>
<!-- Стало (.NET 10) -->
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
Шаг 3 — Program.cs вместо Global.asax
// Было: Global.asax
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
// Стало: Program.cs (.NET 10)
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Регистрация своих сервисов
builder.Services.AddScoped<IProductService, ProductService>();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Шаг 4 — Конфигурация
// Было: Web.config + ConfigurationManager
var connStr = ConfigurationManager.ConnectionStrings["MyDb"].ConnectionString;
var apiKey = ConfigurationManager.AppSettings["ApiKey"];
// Стало: appsettings.json + IConfiguration
// appsettings.json:
// {
// "ConnectionStrings": { "MyDb": "Server=..." },
// "ApiKey": "secret"
// }
// В сервисе через DI:
public class MyService(IConfiguration config)
{
private readonly string _connStr = config.GetConnectionString("MyDb")!;
private readonly string _apiKey = config["ApiKey"]!;
}
Секреты для разработки — в dotnet user-secrets, для production — в переменные окружения или Vault.
Шаг 5 — HttpContext и Request
// Было
var userAgent = HttpContext.Current.Request.UserAgent;
var ip = HttpContext.Current.Request.UserHostAddress;
HttpContext.Current.Response.Redirect("/home");
// Стало — HttpContext инжектируется через IHttpContextAccessor
public class MyService(IHttpContextAccessor accessor)
{
public string GetUserAgent()
{
var ctx = accessor.HttpContext!;
return ctx.Request.Headers.UserAgent.ToString();
}
}
// Или прямо в контроллере (он уже имеет HttpContext)
[ApiController]
public class HomeController : ControllerBase
{
public IActionResult Index()
{
var ip = HttpContext.Connection.RemoteIpAddress;
return Redirect("/home");
}
}
Шаг 6 — Middleware вместо HttpModules
// Было: HttpModule
public class LoggingModule : IHttpModule
{
public void Init(HttpApplication app)
{
app.BeginRequest += (s, e) => Log("Request started");
}
}
// Стало: Middleware
public class LoggingMiddleware(RequestDelegate next)
{
public async Task InvokeAsync(HttpContext context)
{
Log($"Request: {context.Request.Path}");
await next(context);
Log($"Response: {context.Response.StatusCode}");
}
}
// Регистрация в Program.cs
app.UseMiddleware<LoggingMiddleware>();
Типичные проблемы при миграции
System.Web удалён полностью — всё что зависит от System.Web.dll нужно заменять. Ищи альтернативы в Microsoft.AspNetCore.*.
WCF не поддерживается — замени на:
- REST API (ASP.NET Core Controllers или Minimal API)
- gRPC (
Grpc.AspNetCore) — если нужен бинарный протокол - CoreWCF — open-source порт WCF, помогает при больших легаси-кодобазах
Синхронные операции — в .NET Core синхронный I/O в контроллерах по умолчанию запрещён. Делай async:
// Было
public ActionResult Get(int id)
{
var product = _db.Products.Find(id); // синхронный вызов
return View(product);
}
// Стало
public async Task<IActionResult> Get(int id)
{
var product = await _db.Products.FindAsync(id);
return Ok(product);
}
Thread.Sleep в тестах и коде — заменяй на await Task.Delay().
Стратегия: постепенно или сразу
Два подхода:
Big bang — переписываешь всё сразу. Подходит для небольших проектов (< 50K строк кода).
Strangler Fig — запускаешь новый .NET 10 API рядом со старым. Reverse proxy (nginx) роутит новые эндпоинты на новый сервис, старые — на Framework. Постепенно переносишь модуль за модулем.
# Nginx роутинг: новые эндпоинты → .NET 10, остальное → .NET Framework
location /api/v2/ {
proxy_pass http://dotnet10-service:5000;
}
location / {
proxy_pass http://framework-service:80;
}
Strangler Fig безопаснее для больших проектов — в любой момент можно откатиться.
💡 .NET Upgrade Assistant умеет автоматически конвертировать project files, web.config и часть кода. Запусти его первым — сэкономит несколько часов ручной работы. Ещё больше рутины снимает Claude Code со скиллом /dotnet-migrate.
Обсуждение
Пока нет комментариев. Будьте первым.