C# and .NET 6 for modern backend APIs

C# and .NET 6 for modern backend APIs
By Marco Lopes · September 3, 2021 · Web Development
At Kordspace, we build across software, AI, Web3, hardware, and design. Those disciplines frequently meet at the backend: an API connects a web interface to business rules, coordinates background work, or turns device messages into something a product can use. We want that boundary to be clear, dependable, and easy for a remote team to maintain.
C# and ASP.NET Core give us a strong foundation for that work. With .NET 6 approaching release, we also have an opportunity to simplify application setup and reconsider how much infrastructure an API really needs.
This is our practical view of the platform as it stands today: promising, worth evaluating, and still subject to the constraints of preview software.
Where .NET 6 stands today
As of September 3, .NET 6 is in preview, with Preview 7 available. Its general release is planned for November, and it is intended to be a long-term support release. We should distinguish that roadmap from something already supported for ordinary production use.
For a new production system launching immediately, we would evaluate a currently supported release, such as .NET 5 or .NET Core 3.1, against the project's maintenance schedule. For prototypes, internal experiments, and projects with a later launch window, evaluating .NET 6 now can make sense.
We keep preview SDKs isolated, pin versions, and read compatibility notes before upgrading. We also verify third-party packages individually. A framework compiling successfully does not prove that its database provider, authentication integration, or deployment tooling is ready.
Why we reach for C# on the backend
We value C# because it makes contracts visible. Types, interfaces, nullable reference analysis, and compiler feedback help us catch mistakes before a request reaches a running service. That matters particularly in remote collaboration, where another developer may pick up a change hours after its author signs off.
ASP.NET Core adds a coherent set of building blocks: dependency injection, configuration, logging, middleware, authentication, and routing. We can assemble an API without choosing a separate library for every foundational concern.
We also get cross-platform development and deployment. Our team can work across different operating systems while targeting Linux containers in the cloud. The choice is not about insisting on one vendor's entire stack; it is about selecting a productive runtime with a deployment model we can operate confidently.
Use smaller startup code without shrinking the architecture
One of the most interesting .NET 6 additions is the minimal API model. It reduces the ceremony required to expose HTTP endpoints and works naturally with top-level statements.
For a small preview experiment, our project file can make the target and language settings explicit:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
With the .NET 6 preview SDK installed, a small Program.cs looks like this:
using Microsoft.AspNetCore.Builder;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/health", () => new { status = "ok" });
app.Run();
This endpoint tells us that the process can answer a request. It does not establish that the database or another dependency is available. We treat it as a starting point, not a complete operational health strategy.
Minimal setup is useful for a narrow service or prototype. It is not an invitation to put persistence, authorization decisions, and business rules into one growing file. We still separate HTTP handling from application behavior when that separation makes the code easier to test and change.
Choose minimal APIs or controllers deliberately
We do not need to declare controllers obsolete to appreciate minimal APIs. They solve overlapping problems with different trade-offs.
For a small service with a handful of focused routes, minimal APIs can keep the request flow easy to follow. For a larger public API, controllers may provide a more familiar structure, especially when we rely on filters, conventions, and the validation behavior associated with [ApiController].
We check feature differences rather than assuming complete equivalence. In particular, we should not expect minimal endpoints to reproduce controller-style automatic model validation merely because a request object has validation attributes.
Our decision follows the API's complexity and the team's working habits. Consistency across a project is usually more valuable than saving a few lines in one endpoint.
Make the API contract a product decision
Before writing persistence code, we define what consumers need to send and receive. We use dedicated request and response models rather than returning database entities directly.
That boundary prevents accidental exposure of internal fields and lets us change storage without silently changing the public contract. It also gives designers and frontend developers something concrete to discuss before implementation is complete.
We establish a few conventions early:
- Resource-oriented routes with predictable HTTP methods.
- Consistent success and error responses.
- Bounded pagination for collection endpoints.
- Explicit rules for timestamps, identifiers, and nullable fields.
- A compatibility policy for changes to existing responses.
We use OpenAPI tooling to document and inspect those decisions. An interactive documentation page is useful, but the underlying schema is the durable artifact: it supports reviews, integration work, and generated clients where appropriate.
For errors, we prefer a consistent problem-details format and avoid leaking exception messages or internal implementation details.
Put validation and authorization at the boundary
Type safety does not make incoming data trustworthy. We validate required fields, lengths, ranges, and allowed values before passing requests into application logic. We enforce business invariants there too, because background jobs and other entry points may bypass HTTP validation.
Authentication establishes who is calling. Authorization determines what that caller can do. We keep those responsibilities distinct and use established identity protocols and providers rather than inventing our own token scheme.
For JWT-based access, we validate signatures, issuer, audience, and expiration. For cookie-based browser flows, we account for cross-site request forgery. We also configure CORS narrowly; it is a browser policy, not a substitute for access control.
Most importantly, we check ownership and tenant boundaries when accessing resources. A valid token must not make every record accessible. Our logs should support investigation without collecting credentials, tokens, or unnecessary personal information.
Keep data access predictable
Entity Framework Core can be a productive default when its model fits the application. If we evaluate its version 6 preview alongside .NET 6, we confirm that the selected database provider supports the combination.
We keep database contexts scoped appropriately and avoid sharing a context across concurrent operations. For read-heavy endpoints, we project only the fields the response needs and consider no-tracking queries when updates are unnecessary.
We also inspect generated SQL for important request paths. An elegant LINQ expression can still produce an expensive query, and asynchronous execution does not make inefficient database access cheap.
Schema changes deserve their own release plan. We prefer reviewed migrations executed through a controlled deployment step, rather than allowing every application instance to race to modify the database at startup. Where availability matters, schema changes should remain compatible with the application versions that may overlap during rollout.
Design for waiting, failure, and repeated requests
Modern APIs spend considerable time waiting on databases, external services, and storage. We use asynchronous I/O through the request path and avoid blocking calls such as .Result or .Wait().
We propagate cancellation where supported, including the request cancellation token. That helps stop unnecessary work after a caller disconnects, although cancellation alone does not reverse an already committed transaction.
For outbound HTTP, IHttpClientFactory gives us a practical foundation for client configuration and connection management. We set timeouts deliberately and add retries only where repeating an operation is safe.
Long-running AI tasks, device processing, or blockchain interactions usually should not hold an HTTP request open indefinitely. We can accept a job, return an identifier, and expose its status separately. When duplicate submissions could cause harm, we design idempotency and durable processing explicitly rather than assuming each request arrives exactly once.
Deploy on Google Cloud without making the application cloud-shaped
Google Cloud is our preferred starting point, and Cloud Run is an attractive option for stateless HTTP services packaged as containers. We can evaluate a .NET 6 preview container there while keeping the preview's support limitations separate from the hosting decision.
We configure the service to listen on the port supplied through PORT and bind to an interface reachable outside the container. We keep session state, uploaded files, and durable work outside the application instance.
We use Secret Manager for sensitive configuration and a narrowly permissioned service identity for access to other resources. Our deployment process should distinguish configuration from the container image so the same build can move through environments.
Autoscaling also changes database planning. We consider maximum instance counts and connection pools together so application growth does not overwhelm the database.
When project requirements favor AWS or Azure, the same stateless design and container discipline remain useful. Portability does not eliminate provider-specific work, but it limits how much business logic depends on hosting details.
Test what crosses the boundaries
We want fast unit tests around business rules, but an API also needs tests that exercise routing, serialization, validation, authentication, and middleware together. ASP.NET Core's testing infrastructure lets us host the application for integration tests without treating every check as a deployed-environment exercise.
For important database behavior, we prefer tests against the actual database engine rather than assuming an in-memory substitute behaves identically. Constraints, transactions, and query translation are part of correctness.
Our continuous integration pipeline restores dependencies, builds, runs tests, and produces a versioned artifact. With preview software, we pin the SDK through global.json and upgrade intentionally.
For remote collaboration, we document local setup and provide configuration examples without secrets. A reproducible first run is more useful than instructions that depend on somebody remembering an undocumented workstation setting.
Make operation part of the first release
We add structured logging and request correlation early. Useful telemetry should help us connect an incoming request to downstream calls and failures without turning logs into a copy of customer data.
We monitor request latency, error rates, and dependency health, then establish performance expectations from measurements of our own workloads. We do not choose a framework based solely on an unrelated benchmark.
Before a production launch, we run this short checklist:
- Confirm runtime support status and dependency compatibility.
- Review validation, authorization, and secret handling.
- Test migrations, backups, and the rollback procedure.
- Verify container ports, resource limits, and health checks.
- Exercise critical endpoints under realistic load.
- Document alerts, ownership, and deployment steps.
Build for the team that maintains it
Our interest in .NET 6 is not simply that we can write a shorter startup file. We see an opportunity to combine C#'s explicit contracts with a lighter development experience, while retaining the operational practices that make an API dependable. Today, that means experimenting carefully with the preview and making production decisions against actual support requirements.
If we can help turn an idea into a maintainable backend, let's start a project. For more about how we connect software, AI, Web3, hardware, and design, explore our related services.
Conversation (0)
No comments yet. Start the conversation.
Join the conversation
Create a free member account to comment.