[{"content":" TL;DR\n.NET 8 reaches end of support on November 10, 2026; upgrading directly to .NET 10 LTS avoids an intermediate migration. Watch out for breaking changes in IPNetwork (forwarded headers behind proxies) and exception handling middleware. Pin your SDK in global.json and verify telemetry alerts aren\u0026rsquo;t swallowed silently. Level: Advanced · Reading time: 15 min · Category: Case Studies\nThis is an illustrative case study. It combines situations that are common in production .NET systems. The company, people, and details are invented, and no numbers are presented as real measurements unless they come from the demo code in this article.\nThe ticket looked harmless: \u0026ldquo;Move Store.Api to .NET 10.\u0026rdquo; Elif, the team lead, attached one line from Microsoft\u0026rsquo;s support policy page. .NET 8 and .NET 9 both reach end of support on November 10, 2026. After that date there are no more security patches, and the security team would start flagging the API in every audit.\nKerem opened the first pull request on a Monday. He changed net8.0 to net10.0, pushed, and watched CI turn red. By Thursday the build was green again, the staging deploy looked healthy, and the error-rate alert had gone completely quiet. That silence turned out to be the most dangerous break of all.\nThe situation Store.Api is the backend for a mid-size Turkish e-commerce company: an ASP.NET Core Web API with controllers, Entity Framework Core (EF Core) on SQL Server, and JSON Web Tokens (JWT) for the storefront. A small back office reports area uses a separate cookie scheme named BackOffice. The API runs in Docker behind a load balancer, so it relies on forwarded headers. Swagger UI comes from Swashbuckle 6 with a JWT security definition and one operation filter. CI builds with TreatWarningsAsErrors.\nThe support dates, from the official .NET support policy page:\nVersion Type End of support .NET 8 LTS November 10, 2026 .NET 9 STS November 10, 2026 .NET 10 LTS November 14, 2028 Skipping .NET 9 was an easy call. The plan had five mechanical steps before any code changes.\nPin the SDK in global.json at the repository root:\n1 2 3 4 5 6 { \u0026#34;sdk\u0026#34;: { \u0026#34;version\u0026#34;: \u0026#34;10.0.100\u0026#34;, \u0026#34;rollForward\u0026#34;: \u0026#34;latestFeature\u0026#34; } } Change the target framework in one place, Directory.Build.props:\n1 2 3 4 5 6 7 8 \u0026lt;Project\u0026gt; \u0026lt;PropertyGroup\u0026gt; \u0026lt;TargetFramework\u0026gt;net10.0\u0026lt;/TargetFramework\u0026gt; \u0026lt;Nullable\u0026gt;enable\u0026lt;/Nullable\u0026gt; \u0026lt;ImplicitUsings\u0026gt;enable\u0026lt;/ImplicitUsings\u0026gt; \u0026lt;TreatWarningsAsErrors\u0026gt;true\u0026lt;/TreatWarningsAsErrors\u0026gt; \u0026lt;/PropertyGroup\u0026gt; \u0026lt;/Project\u0026gt; List what is outdated, then align every Microsoft.* package on the same major version:\n1 2 3 dotnet list package --outdated # .NET 10 SDK also accepts the noun-first form: dotnet package list --outdated --format json \u0026gt; outdated.json Keep the versions in Directory.Packages.props so no project can drift:\n1 2 3 4 5 6 7 8 9 10 11 \u0026lt;Project\u0026gt; \u0026lt;PropertyGroup\u0026gt; \u0026lt;ManagePackageVersionsCentrally\u0026gt;true\u0026lt;/ManagePackageVersionsCentrally\u0026gt; \u0026lt;/PropertyGroup\u0026gt; \u0026lt;ItemGroup\u0026gt; \u0026lt;PackageVersion Include=\u0026#34;Microsoft.AspNetCore.Authentication.JwtBearer\u0026#34; Version=\u0026#34;10.0.12\u0026#34; /\u0026gt; \u0026lt;PackageVersion Include=\u0026#34;Microsoft.EntityFrameworkCore.SqlServer\u0026#34; Version=\u0026#34;10.0.12\u0026#34; /\u0026gt; \u0026lt;PackageVersion Include=\u0026#34;Microsoft.EntityFrameworkCore.Design\u0026#34; Version=\u0026#34;10.0.12\u0026#34; /\u0026gt; \u0026lt;PackageVersion Include=\u0026#34;Swashbuckle.AspNetCore\u0026#34; Version=\u0026#34;10.1.4\u0026#34; /\u0026gt; \u0026lt;/ItemGroup\u0026gt; \u0026lt;/Project\u0026gt; Finally, change the container base image tags from 8.0-bookworm-slim to the .NET 10 images. That step broke too.\nSymptoms The breaks arrived in three waves.\nAt build time:\nerror ASPDEPR005: 'ForwardedHeadersOptions.KnownNetworks' is obsolete in Program.cs. error NU1510 on a direct System.Text.Json package reference nobody remembered adding. NU1903 vulnerability warnings, promoted to errors, for a package the team never referenced directly. Every Swashbuckle file failed with error CS0234: The type or namespace name 'Models' does not exist in the namespace 'Microsoft.OpenApi'. The nightly job that cancels stale orders no longer compiled at its ExecuteUpdateAsync call. At image build time:\ndocker build failed because the image tag mcr.microsoft.com/dotnet/aspnet:10.0-bookworm-slim does not exist. On staging:\nAn integration test that expected 302 Found for an anonymous back office request got 401 Unauthorized. The \u0026ldquo;5xx errors with exceptions\u0026rdquo; alert went silent, while the dashboard still showed 500 responses. Database CPU rose briefly right after the deploy, then settled. Investigation The team grouped build errors by code first, so they could fix one category at a time:\n1 dotnet build -c Release 2\u0026gt;\u0026amp;1 | grep -oE \u0026#34;(error|warning) [A-Z]+[0-9]+\u0026#34; | sort | uniq -c For the vulnerability warnings, they listed vulnerable transitive packages and asked NuGet which top-level package pulled each one in:\n1 2 3 (dotnet package list --vulnerable --include-transitive --format json | ConvertFrom-Json).projects.frameworks.transitivePackages.id | Sort-Object -Unique | ForEach-Object { dotnet nuget why src/Store.Api/Store.Api.csproj $_ } The silent alert took longer. The alert query counted log events from one category, Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware. On .NET 8, every exception that reached UseExceptionHandler produced this line, even when Store.Api\u0026rsquo;s IExceptionHandler turned it into a ProblemDetails response:\n1 2 3 fail: Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware[1] An unhandled exception has occurred while executing the request. System.InvalidOperationException: Pricing service returned null On .NET 10 the 500 response was identical, but that log line never appeared. The handler returned true, and that now means \u0026ldquo;handled, record nothing\u0026rdquo;.\nThe 401 was reproducible from a terminal:\n1 curl -i https://localhost:7001/api/admin/reports/daily-sales 1 2 HTTP/1.1 401 Unauthorized Location: https://localhost:7001/admin/login?ReturnUrl=%2Fapi%2Fadmin%2Freports%2Fdaily-sales The Location header was still there. Only the status code had changed.\nFor the database CPU bump, the team compared the SQL that EF Core logged on both versions. EF Core 10 names parameters after the variable (@status instead of @__status_0) and translates ids.Contains(o.Id) into IN (@ids1, @ids2, @ids3) instead of an OPENJSON parameter. New SQL text means new cached plans, so every query compiled once after the deploy.\nRoot cause Every symptom mapped to a documented breaking change in .NET 10 or in a package upgraded with it:\nSymptom Breaking change Type ASPDEPR005 IPNetwork and ForwardedHeadersOptions.KnownNetworks are obsolete Source incompatible Silent alert Exception diagnostics suppressed when TryHandleAsync returns true Behavioral 401 instead of 302 Cookie login redirects disabled for known API endpoints Behavioral Swashbuckle errors Swashbuckle 10 moved to Microsoft.OpenApi 2.x (namespace and model changes) Source incompatible ExecuteUpdateAsync error EF Core 10 setters are a plain lambda, not an expression tree Source incompatible SQL text and plans Parameterized collections use multiple parameters; parameter names simplified Behavioral NU1510 NuGet raises NU1510 for direct references pruned by the framework Source incompatible NU1903 dotnet restore audits transitive packages for net10.0 projects Behavioral Missing image tag Default .NET 10 images use Ubuntu, and no Debian images ship Behavioral Two more changes did not break anything but needed attention. dotnet new sln now creates an .slnx file, and the EF Core tools require --framework on multi-targeted projects.\nThe fix Forwarded headers Before (.NET 8):\n1 2 3 4 5 builder.Services.Configure\u0026lt;ForwardedHeadersOptions\u0026gt;(options =\u0026gt; { options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; options.KnownNetworks.Add(new Microsoft.AspNetCore.HttpOverrides.IPNetwork(IPAddress.Parse(\u0026#34;10.20.0.0\u0026#34;), 16)); }); After (.NET 10), using System.Net.IPNetwork:\n1 2 3 4 5 6 // Program.cs, before builder.Build(). Needs: using Microsoft.AspNetCore.HttpOverrides; builder.Services.Configure\u0026lt;ForwardedHeadersOptions\u0026gt;(options =\u0026gt; { options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; options.KnownIPNetworks.Add(System.Net.IPNetwork.Parse(\u0026#34;10.20.0.0/16\u0026#34;)); }); The fully qualified name avoids a clash with the obsolete Microsoft.AspNetCore.HttpOverrides.IPNetwork.\nException logging The team decided the handler should own its logging, so the alert no longer depends on middleware internals:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // File: Infrastructure/GlobalExceptionHandler.cs using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Mvc; namespace Store.Api.Infrastructure; public sealed class GlobalExceptionHandler( IProblemDetailsService problemDetails, ILogger\u0026lt;GlobalExceptionHandler\u0026gt; logger) : IExceptionHandler { public async ValueTask\u0026lt;bool\u0026gt; TryHandleAsync( HttpContext httpContext, Exception exception, CancellationToken cancellationToken) { // .NET 10 no longer logs exceptions that a handler reports as handled. logger.LogError(exception, \u0026#34;Unhandled exception for {Method} {Path}\u0026#34;, httpContext.Request.Method, httpContext.Request.Path); httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError; return await problemDetails.TryWriteAsync(new ProblemDetailsContext { HttpContext = httpContext, Exception = exception, ProblemDetails = new ProblemDetails { Status = StatusCodes.Status500InternalServerError, Title = \u0026#34;An unexpected error occurred.\u0026#34; } }); } } They updated the alert to the new category, Store.Api.Infrastructure.GlobalExceptionHandler. A team that wants the old behavior instead, including the error.type tag on the http.server.request.duration metric, can opt out of suppression:\n1 2 3 4 5 // Program.cs, in the pipeline after builder.Build() app.UseExceptionHandler(new ExceptionHandlerOptions { SuppressDiagnosticsCallback = _ =\u0026gt; false }); Cookie redirects For [ApiController] endpoints, 401 and 403 are the right answers, so the team fixed the test, not the code:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // File: tests/Store.Api.Tests/BackOfficeAuthTests.cs using System.Net; using Microsoft.AspNetCore.Mvc.Testing; using Xunit; namespace Store.Api.Tests; public sealed class BackOfficeAuthTests(WebApplicationFactory\u0026lt;Program\u0026gt; factory) : IClassFixture\u0026lt;WebApplicationFactory\u0026lt;Program\u0026gt;\u0026gt; { [Fact] public async Task Anonymous_report_request_returns_401_with_login_location() { var client = factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false }); var response = await client.GetAsync(\u0026#34;/api/admin/reports/daily-sales\u0026#34;); Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); Assert.StartsWith(\u0026#34;/admin/login\u0026#34;, response.Headers.Location?.AbsolutePath); } } The test project references Microsoft.AspNetCore.Mvc.Testing and xUnit, and Program must be visible to it (on .NET 10 a source generator makes it public; on .NET 8 add public partial class Program; at the end of Program.cs). One endpoint really was opened from links in emails, a CSV download, so it opts back in to redirects:\n1 2 3 4 [HttpGet(\u0026#34;daily-sales.csv\u0026#34;)] [AllowCookieRedirect] // Microsoft.AspNetCore.Http, new in .NET 10 public IActionResult DailySalesCsv() =\u0026gt; File(\u0026#34;date,total\\n\u0026#34;u8.ToArray(), \u0026#34;text/csv\u0026#34;, \u0026#34;daily-sales.csv\u0026#34;); Swashbuckle 10 and Microsoft.OpenApi 2.x The Models namespace is gone, Type is a JsonSchemaType flags enum, and security requirements reference schemes through the document. Before (Swashbuckle 6):\n1 2 3 4 5 6 7 8 9 10 11 12 using Microsoft.OpenApi.Models; options.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = \u0026#34;Bearer\u0026#34; } }, Array.Empty\u0026lt;string\u0026gt;() } }); After (Swashbuckle 10):\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // Program.cs, before builder.Build(). Needs: using Microsoft.OpenApi; builder.Services.AddSwaggerGen(options =\u0026gt; { options.SwaggerDoc(\u0026#34;v1\u0026#34;, new OpenApiInfo { Title = \u0026#34;Store.Api\u0026#34;, Version = \u0026#34;v1\u0026#34; }); options.OperationFilter\u0026lt;Store.Api.OpenApi.CorrelationIdHeaderFilter\u0026gt;(); options.AddSecurityDefinition(\u0026#34;Bearer\u0026#34;, new OpenApiSecurityScheme { Type = SecuritySchemeType.Http, Scheme = \u0026#34;bearer\u0026#34;, BearerFormat = \u0026#34;JWT\u0026#34;, Description = \u0026#34;Paste the access token only.\u0026#34; }); options.AddSecurityRequirement(document =\u0026gt; new OpenApiSecurityRequirement { [new OpenApiSecuritySchemeReference(\u0026#34;Bearer\u0026#34;, document)] = [] }); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // File: OpenApi/CorrelationIdHeaderFilter.cs using Microsoft.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; namespace Store.Api.OpenApi; public sealed class CorrelationIdHeaderFilter : IOperationFilter { public void Apply(OpenApiOperation operation, OperationFilterContext context) { operation.Parameters ??= []; operation.Parameters.Add(new OpenApiParameter { Name = \u0026#34;X-Correlation-Id\u0026#34;, In = ParameterLocation.Header, Required = false, Schema = new OpenApiSchema { Type = JsonSchemaType.String, Format = \u0026#34;uuid\u0026#34; } }); } } The old filter set Type = \u0026quot;string\u0026quot;, which no longer compiles. Swashbuckle 10 still writes OpenAPI 3.0 unless a project opts in to 3.1.\nEF Core 10 bulk updates The stale-order job built its setters as a reusable expression. Before (EF Core 8):\n1 2 3 4 5 6 private static Expression\u0026lt;Func\u0026lt;SetPropertyCalls\u0026lt;Order\u0026gt;, SetPropertyCalls\u0026lt;Order\u0026gt;\u0026gt;\u0026gt; CancelSetters() =\u0026gt; s =\u0026gt; s.SetProperty(o =\u0026gt; o.Status, OrderStatus.Cancelled); await db.Orders .Where(o =\u0026gt; o.Status == OrderStatus.Pending \u0026amp;\u0026amp; o.CreatedAtUtc \u0026lt; cutoffUtc) .ExecuteUpdateAsync(CancelSetters(), ct); EF Core 10 takes an Action\u0026lt;UpdateSettersBuilder\u0026lt;Order\u0026gt;\u0026gt;, so conditional setters become ordinary if statements. Store.Api\u0026rsquo;s real Order has a CancelReason column beyond the minimal shape:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // File: Orders/StaleOrderCanceller.cs using Microsoft.EntityFrameworkCore; using Store.Api.Data; namespace Store.Api.Orders; public sealed class StaleOrderCanceller(AppDbContext db, TimeProvider clock) { public Task\u0026lt;int\u0026gt; CancelAsync(TimeSpan maxAge, string? reason, CancellationToken ct) { var cutoffUtc = clock.GetUtcNow().UtcDateTime - maxAge; return db.Orders .Where(o =\u0026gt; o.Status == OrderStatus.Pending \u0026amp;\u0026amp; o.CreatedAtUtc \u0026lt; cutoffUtc) .ExecuteUpdateAsync(s =\u0026gt; { s.SetProperty(o =\u0026gt; o.Status, OrderStatus.Cancelled); if (reason is not null) { s.SetProperty(o =\u0026gt; o.CancelReason, reason); } }, ct); } } Inline chained lambdas such as s =\u0026gt; s.SetProperty(...).SetProperty(...) keep compiling. Only stored expression trees break.\nFor the new collection translation, the team kept the default and watched Query Store. If one query regresses, change that query only:\n1 2 3 var orders = await db.Orders .Where(o =\u0026gt; EF.Parameter(orderIds).Contains(o.Id)) // single JSON parameter, the EF Core 8 and 9 shape .ToListAsync(ct); The global switch is UseSqlServer(connectionString, o =\u0026gt; o.UseParameterizedCollectionMode(ParameterTranslationMode.Parameter)).\nNuGet warnings They deleted the System.Text.Json reference (the framework provides it), and upgraded the top-level package that dotnet nuget why named. To keep audit warnings visible without letting a new advisory block an urgent hotfix, they added this to Directory.Build.props:\n1 2 3 \u0026lt;PropertyGroup\u0026gt; \u0026lt;WarningsNotAsErrors\u0026gt;NU1901;NU1902;NU1903;NU1904;$(WarningsNotAsErrors)\u0026lt;/WarningsNotAsErrors\u0026gt; \u0026lt;/PropertyGroup\u0026gt; A nightly job still fails on any audit warning, so nobody can ignore them for long.\nSolution file and Dockerfile 1 2 dotnet sln Store.sln migrate # writes Store.slnx; update CI paths afterwards dotnet new sln --format sln # only if a tool in your pipeline cannot read .slnx yet 1 2 3 4 5 6 7 8 9 10 11 FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build WORKDIR /src COPY . . RUN dotnet publish src/Store.Api/Store.Api.csproj -c Release -o /app/publish # 10.0 is Ubuntu 24.04 \u0026#34;Noble\u0026#34;; there is no Debian variant for .NET 10 FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final WORKDIR /app COPY --from=build /app/publish . USER $APP_UID ENTRYPOINT [\u0026#34;dotnet\u0026#34;, \u0026#34;Store.Api.dll\u0026#34;] Port 8080 and the non-root app user are the same as on .NET 8. Re-check any apt-get install lines, because package names can differ between Debian and Ubuntu.\nA CI matrix for the transition For two weeks the team built both frameworks, so a hotfix could still ship on .NET 8. They temporarily replaced \u0026lt;TargetFramework\u0026gt; with \u0026lt;TargetFrameworks\u0026gt;net8.0;net10.0\u0026lt;/TargetFrameworks\u0026gt; and made versions conditional:\n1 2 3 4 5 6 \u0026lt;ItemGroup Condition=\u0026#34;\u0026#39;$(TargetFramework)\u0026#39; == \u0026#39;net8.0\u0026#39;\u0026#34;\u0026gt; \u0026lt;PackageVersion Include=\u0026#34;Microsoft.EntityFrameworkCore.SqlServer\u0026#34; Version=\u0026#34;8.0.31\u0026#34; /\u0026gt; \u0026lt;/ItemGroup\u0026gt; \u0026lt;ItemGroup Condition=\u0026#34;\u0026#39;$(TargetFramework)\u0026#39; == \u0026#39;net10.0\u0026#39;\u0026#34;\u0026gt; \u0026lt;PackageVersion Include=\u0026#34;Microsoft.EntityFrameworkCore.SqlServer\u0026#34; Version=\u0026#34;10.0.12\u0026#34; /\u0026gt; \u0026lt;/ItemGroup\u0026gt; Code that differs goes behind #if NET10_0_OR_GREATER. The workflow runs each framework as its own job:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 jobs: test: runs-on: ubuntu-latest strategy: matrix: tfm: [net8.0, net10.0] steps: - uses: actions/checkout@v4 - uses: actions/setup-dotnet@v4 with: dotnet-version: | 8.0.x 10.0.x - run: dotnet test Store.slnx -c Release -f ${{ matrix.tfm }} While multi-targeting, every EF Core tools command needs the framework: dotnet ef migrations add AddCancelReason --framework net10.0.\nResults Store.Api went to production on .NET 10 three weeks before the support deadline. The rollout itself was uneventful because every break had already surfaced in CI or staging. The error alert fired again in staging within minutes of a deliberately broken request, and the database CPU bump after deploy matched the one-time plan recompilation the EF Core documentation warns about. The team removed the net8.0 target, the conditional package versions, and the #if blocks in a follow-up pull request.\nWhat the team would do differently Read the breaking change lists first. Every surprise was documented. One hour with the ASP.NET Core, EF Core, SDK, and containers lists would have turned them into planned tasks. Upgrade cross-framework packages before the framework. Swashbuckle 10 also supports net8.0. Doing that migration on .NET 8 would have kept the framework pull request small. Test the alerts, not just the API. A green test suite said nothing about the log line an alert depended on. Treat behavioral changes as release notes for operations. Status codes, log categories, and SQL text are contracts with other teams. Checklist for your project Confirm the support dates for every runtime you deploy on the official .NET support policy page. Add global.json with the .NET 10 SDK and a rollForward policy. Move TargetFramework and package versions to Directory.Build.props and Directory.Packages.props. Run dotnet list package --outdated and align all Microsoft.* packages on 10.0.x. Search for KnownNetworks, WithOpenApi, WebHostBuilder, and Microsoft.OpenApi.Models. Search for stored SetPropertyCalls expressions used with ExecuteUpdate. Check every IExceptionHandler: does it log, and does any alert depend on middleware logs? Check tests and clients that expect cookie redirects from API endpoints. Run dotnet package list --vulnerable --include-transitive and decide how NU190x warnings should fail builds. Replace Debian image tags and re-test apt-get lines on Ubuntu. Decide between .sln and .slnx before someone runs dotnet new sln. Watch database CPU and Query Store for a day after the first deploy. Related reading Swashbuckle vs NSwag vs Built-in OpenAPI with Scalar dotnet CLI Cheat Sheet Migrating a .NET Framework 4.8 App to Modern .NET Step by Step Global Exception Handling with IExceptionHandler and ProblemDetails (in \u0026ldquo;The .NET Implementation Playbook\u0026rdquo;) Docker, Health Checks, and Deploying .NET Apps (in \u0026ldquo;Zero to Full-Stack .NET\u0026rdquo;) Tags: dotnet, aspnet-core, entity-framework-core, dotnet-10, migration\nPart of The .NET Field Guide. How this article was made: drafted with AI assistance, then reviewed by Softinbit Technology before publishing.\n","permalink":"https://www.softinbit.com/dotnet-field-guide/upgrading-dotnet-8-to-10/","summary":"How one team moved Store.Api to .NET 10 before .NET 8 support ended, and the exact fix for every break they hit on the way.","title":"Upgrading a Production API from .NET 8 to .NET 10: What Broke"},{"content":"About Softinbit Technology \u0026ldquo;Engineering software systems that cannot fail.\u0026rdquo;\nSoftinbit Technology is an engineering initiative and technical publication focused on building resilient, high-throughput .NET backend architectures, distributed cloud systems, and mission-critical software.\nBacked by more than 12 years of hands-on engineering experience spanning two demanding domains—real-time financial infrastructure and mission-critical industrial automation—our content is driven by a singular philosophy: obsessive fault tolerance, observable architectures, and resilient recovery.\n🏦 Mission-Critical Backend \u0026amp; Fintech Systems Our technical focus centers on architecting, maintaining, and scaling backend services that power high-availability financial trading and transaction systems:\nFault-Tolerant Microservices: Architecting scalable .NET Core backend services processing thousands of high-value concurrent transactions with 99.9%+ availability requirements. Ultra-Low Latency \u0026amp; High Performance: Implementing in-memory data grids (IMDG), efficient caching strategies, and distributed messaging (RabbitMQ, Hangfire) for sub-millisecond data paths. Resilient Integrations: Engineering zero-downtime REST integrations with financial market data feeds, payment gateways, and regulatory systems. AI-Augmented Engineering: Incorporating AI-assisted developer workflows and prompt engineering to accelerate delivery velocity while enforcing strict unit testing and clean architecture. 🏭 Industrial Roots: 8 Years in Automation \u0026amp; Control Systems Before enterprise backend engineering, our foundations were built through 8 years of programming, commissioning, and maintaining automated systems across 5 countries:\nMulti-facility Manufacturing Execution Systems (MES) and centralized real-time production dashboards across international manufacturing hubs. Massive-scale Automated Storage \u0026amp; Retrieval Systems (AS/RS), stacker cranes, and automated conveyors operating in strict, high-throughput environments. Safety-critical control loops, SCADA interfaces, and industrial communication pipelines (OPC, Modbus, TCP/IP). Why this matters for backend software:\nWhen you have resolved real-time system failures on physical factory floors, you approach software design differently. We design backend architectures with an intuitive respect for race conditions, timeouts, deadlocks, and graceful degradation.\n🛠️ Core Technical Arsenal Backend \u0026amp; Frameworks: C#, .NET 8 / 9 / 10, ASP.NET Core, Minimal APIs, Blazor, WCF, Clean Architecture, CQRS, DDD. Data \u0026amp; Caching: MS SQL Server, T-SQL, Entity Framework Core, Dapper, In-Memory Data Grids (IMDG), Redis. Messaging \u0026amp; Background Processing: RabbitMQ, Hangfire, Event-Driven Architecture, Outbox Pattern. Engineering Quality: xUnit, Testcontainers, OpenTelemetry, Structured Logging (Serilog), CI/CD (GitHub Actions). 📖 What We Publish Zero to Full-Stack .NET: A step-by-step roadmap from foundations to full-stack mastery. The .NET Implementation Playbook: Copy-paste, production-tested recipes for common backend challenges. The .NET Field Guide: In-depth comparisons, cheat sheets, and production war stories. The Modern .NET Toolkit: Modern cloud architectures, AI integration (Microsoft.Extensions.AI), and modern tooling. Transparency \u0026amp; AI Disclosure Articles and tutorials are drafted with the assistance of AI and meticulously tested, reviewed, and curated before publishing.\n📬 Connect \u0026amp; Follow LinkedIn: linkedin.com/in/softinbit X (Twitter): @softinbit GitHub: github.com/softinbit Substack Newsletter: softinbit.substack.com ","permalink":"https://www.softinbit.com/about/","summary":"High-performance .NET engineering, fault-tolerant backend architectures, and production-tested guides.","title":"About Softinbit"},{"content":" TL;DR\nPin your SDK version with global.json so teammates never break builds with previews. Run SQL Server locally via Docker or LocalDB, and trust ASP.NET Core developer certs with dotnet dev-certs https --trust. Use the command-line CLI shortcuts to build, run, and test projects without IDE friction. Level: Beginner · Prerequisites: Windows, macOS, or Linux machine · Reading time: 12 min\nPart 1 of the \u0026ldquo;Zero to Full-Stack .NET\u0026rdquo; series. Section: Getting Started.\nIt is your first Monday. You clone the project, open it, and press Build. The output says the project targets a .NET version you do not have. You install something, try again, and now the app starts but crashes because it cannot reach SQL Server. You fix that, open the browser, and get a red \u0026ldquo;Your connection is not private\u0026rdquo; warning on localhost.\nNone of this is a real bug. It is a machine that was never set up. The team has been working on their laptops for years and nobody remembers which tools they installed, so the onboarding document is three lines long and two of them are wrong.\nThis article is the setup I wish someone had handed me. It goes in the order you will hit the problems, and every step ends with a command you can run to prove it worked.\nWhat you will be able to do after this Install the current .NET SDK and pin the version a repository uses with global.json Run SQL Server locally in Docker or as LocalDB and connect to it from a query tool Trust the local HTTPS certificate so https://localhost stops warning Create, run, and test a console app and a Web API from the command line Use a dotnet CLI cheat sheet instead of clicking through menus Installing the SDK and pinning it per repository Download the SDK (not just the runtime) for the current LTS release from the .NET download page on dot.net. LTS means long-term support: it is the version most teams target because it receives fixes for three years. .NET 8 and .NET 10 are both LTS releases; pick the one your team uses, or .NET 10 if you are starting fresh (.NET 8 support ends in November 2026).\nCheck what you have:\n1 2 3 4 5 dotnet --list-sdks # 8.0.xxx [C:\\Program Files\\dotnet\\sdk] # 10.0.xxx [C:\\Program Files\\dotnet\\sdk] dotnet --version # prints the SDK that will be used in the current folder Having several SDKs installed is normal. The surprise is which one dotnet build picks: without any configuration, it uses the newest one on the machine. That is fine until a teammate installs a preview and a build starts behaving differently. The fix is a global.json at the root of the repository:\n1 2 3 4 5 6 { \u0026#34;sdk\u0026#34;: { \u0026#34;version\u0026#34;: \u0026#34;8.0.100\u0026#34;, \u0026#34;rollForward\u0026#34;: \u0026#34;latestFeature\u0026#34; } } rollForward: latestFeature means \u0026ldquo;use 8.0.100 or any newer 8.0.x feature band, but never jump to 9 or 10\u0026rdquo;. You can generate the file instead of typing it:\n1 dotnet new globaljson --sdk-version 8.0.100 --roll-forward latestFeature Running SQL Server locally Pick one of two paths:\nDocker (recommended if you have Docker Desktop): 1 docker run -e \u0026#34;ACCEPT_EULA=Y\u0026#34; -e \u0026#34;MSSQL_SA_PASSWORD=Dev_Passw0rd!\u0026#34; -p 1433:1433 --name sqldev -d mcr.microsoft.com/mssql/server:2022-latest LocalDB (lightweight, Windows-only, installed with Visual Studio):\nConnect string: Server=(localdb)\\\\mssqllocaldb;Database=StoreDb;Trusted_Connection=True; Trusting Local HTTPS Certificates When running ASP.NET Core APIs locally, avoid browser SSL security warnings with:\n1 2 dotnet dev-certs https --clean dotnet dev-certs https --trust Core CLI Cheat Sheet 1 2 3 4 dotnet new webapi -n Store.Api # Create new Web API dotnet build # Compile project dotnet run --project Store.Api # Run application dotnet test # Run all unit tests Part 1 of 59 in Zero to Full-Stack .NET. Next up: Thinking Like a Programmer: Turning a Vague Request Into Working Code.\nHow this article was made: drafted with AI assistance, then reviewed by Softinbit Technology before publishing.\n","permalink":"https://www.softinbit.com/zero-to-full-stack-dotnet/setting-up-a-dotnet-developer-machine/","summary":"Install the SDK, SQL Server, Git, and Node once, pin them per repo, and stop losing your first week to \u0026lsquo;it works on my machine\u0026rsquo;.","title":"Setting Up a .NET Developer Machine That Just Works"},{"content":"Welcome to SoftInBit If you are new here or looking to systematically level up your .NET and software engineering skills, follow our recommended tracks below:\n🟢 Track 1: Zero to Full-Stack .NET (Beginner to Intermediate) A progressive series designed to take you from foundational C# and modern developer workflows up to end-to-end full-stack applications.\nSetting Up a .NET Developer Machine That Just Works Thinking Like a Programmer: Turning a Vague Request Into Working Code Git for Daily Work: Branches, Pull Requests, and Fixing Mistakes C# Types in Practice: Value vs Reference, decimal vs double, and Nullable Control Flow That Reads Well: Guard Clauses, Switch Expressions, and Pattern Matching 🟡 Track 2: The .NET Implementation Playbook Copy-paste ready, production-tested implementations for real-world backend architectures.\nIssuing and Validating JWT Access Tokens in ASP.NET Core Global Exception Handling with IExceptionHandler and ProblemDetails EF Core Code-First Migrations: From First Migration to Production Deploy 🔴 Track 3: The .NET Field Guide In-depth technical comparisons, cheatsheets, and production war stories.\nUpgrading a Production API from .NET 8 to .NET 10: What Broke Modern C# Cheat Sheet: C# 10 to C# 14 EF Core vs Dapper: Which One for Which Query ","permalink":"https://www.softinbit.com/start-here/","summary":"Recommended reading order for developers starting their journey or leveling up.","title":"Start Here"},{"content":"The .NET Field Guide A comprehensive 80-part field manual featuring side-by-side library comparisons, printable cheat sheets, and illustrative postmortems.\nGuide Categories: Comparisons (01–20): Real benchmarks and trade-offs (EF Core vs Dapper, Hangfire vs Quartz, MassTransit vs Wolverine, Keycloak vs Duende, Docker Compose vs .NET Aspire). Cheat Sheets (21–40): Quick reference tables (LINQ, EF Core, dotnet CLI, Modern C#, System.Text.Json, SQL Server diagnostics). Case Studies (41–60): Production incidents, architectural migrations (.NET 8 to .NET 10 upgrade bugs, thread pool starvation, stale cache disasters). Tool Guides (61–80): Step-by-step masteries (Wolverine, YARP API Gateway, OpenTelemetry, Sentry, Elasticsearch, k6 load testing). ","permalink":"https://www.softinbit.com/series/dotnet-field-guide/","summary":"80 in-depth comparisons, cheat sheets, and production postmortems for .NET professionals.","title":"The .NET Field Guide"},{"content":"The .NET Implementation Playbook A collection of 43 battle-tested, focused recipes solving real-world backend engineering challenges in ASP.NET Core.\nCore Focus Areas: Authentication \u0026amp; Security (JWT, Refresh Tokens with rotation, Google OAuth, 2FA, API Keys, Resource-based authorization). Data \u0026amp; Entity Framework Core (Migrations in production, Soft Delete, Auditing Interceptors, RowVersion concurrency, Dapper integration). Resilience \u0026amp; Background Jobs (Quartz.NET, Hangfire, Transactional Outbox Pattern, RabbitMQ \u0026amp; MassTransit). APIs \u0026amp; Integration (Clean Architecture, Result pattern, File Uploads, iyzico payment gateway, HMAC webhook verification). ","permalink":"https://www.softinbit.com/series/dotnet-implementation-playbook/","summary":"Production-ready, copy-paste recipes for authentication, caching, architecture, and resilient backend design.","title":"The .NET Implementation Playbook"},{"content":"The Modern .NET Toolkit 28 hands-on guides navigating modern .NET developments, cloud native patterns, and AI application engineering.\nSignature Tracks: Blazor From Zero (Parts 01–05): A complete mini-series covering Render Modes (.NET 8/9/10), Component architecture, state management, auth, and JavaScript interop. AI in .NET (Parts 06–09): Calling LLMs via Microsoft.Extensions.AI, Tool Calling \u0026amp; Structured Outputs, building local RAG pipelines, and implementing Model Context Protocol (MCP) servers in C#. Modern DevOps \u0026amp; Cloud (Parts 10–28): GitHub Actions CI/CD, Linux + Nginx deployments, Kubernetes with Helm, Sentry error tracking, BenchmarkDotNet, and Hot Chocolate GraphQL. ","permalink":"https://www.softinbit.com/series/modern-dotnet-toolkit/","summary":"Modern developer workflows, Blazor, AI in .NET, GitHub Actions CI/CD, and Cloud Deployments.","title":"The Modern .NET Toolkit"},{"content":"📬 Stay Ahead in Modern .NET Engineering Join developers who read our weekly Friday dispatch. Every week, we cover practical patterns, battle-tested recipes, and lessons learned from real-world high-throughput production systems.\nWhat to Expect Every Friday: 💡 Tip of the Week: One focused, immediately applicable tip or pitfall extracted from our cheat sheets. 📖 The Weekly Breakdown: Direct, curated highlights from our ongoing series (Zero to Full-Stack, Implementation Playbook, Field Guide, Modern Toolkit). ⚙️ Production Insights: Real-world perspective on fault tolerance, high-availability fintech backends, and cloud patterns. 🚫 Zero Spam: Unsubscribe anytime with a single click. Subscribe to SoftInBit on Substack Enter your email below to read upcoming dispatches and access full archive issues. Or visit directly on Substack: softinbit.substack.com ","permalink":"https://www.softinbit.com/newsletter/","summary":"Get curated .NET tips, architecture insights, and cheat sheets directly to your inbox every Friday.","title":"The SoftInBit Newsletter"},{"content":"Zero to Full-Stack .NET This 59-part series is structured as a clear, step-by-step pathway. If you are starting out or filling in gaps in your software engineering journey, this is where you start.\nSeries Roadmap: Phase 1: Foundations \u0026amp; Setup (Parts 01–03) 01: Setting Up a .NET Developer Machine That Just Works 02: Thinking Like a Programmer: Turning a Vague Request Into Working Code 03: Git for Daily Work: Branches, Pull Requests, and Fixing Mistakes Phase 2: Core C# in Practice (Parts 04–18) Types, Collections, LINQ, OOP, async/await, Dependency Injection, and Unit Testing. Phase 3: Database \u0026amp; SQL Server Mastery (Parts 19–30) Schema design, T-SQL, Execution Plans, Transactions, and EF Core Code First. Phase 4: ASP.NET Core Backend Development (Parts 31–47) REST APIs, Swagger, JWT Auth, Middleware, Caching, SignalR, and Docker. Phase 5: Frontend \u0026amp; Integration (Parts 48–55) HTML, CSS, Modern JS, TypeScript, and Angular connected to .NET APIs. Phase 6: Professional Engineering (Parts 56–59) Requirements analysis, Agile/Scrum, Code Reviews, and Interview Preparation. 💡 New articles in this series are published every Monday and selected weekdays according to the SoftInBit Publication Schedule.\n","permalink":"https://www.softinbit.com/series/zero-to-full-stack-dotnet/","summary":"A complete 59-part roadmap from raw machine setup to production full-stack .NET apps.","title":"Zero to Full-Stack .NET"}]