Choorai
Lv.3 Production C# ASP.NET Core

Build a Production Backend with ASP.NET Core

ASP.NET Core is Microsoft's cross-platform web framework, widely used in enterprise, finance, and gaming sectors. With Minimal API style, it provides a concise yet powerful type system and high performance (Kestrel).

Why .NET?

  • Industry standard for enterprise/finance/gaming
  • Strong static type system (C#)
  • High performance (Kestrel web server)
  • Cross-platform (Windows, Linux, macOS)
  • Rich real-world references and Microsoft support

1. Create Project

Terminal
# Check .NET SDK installation
dotnet --version

# Create project
dotnet new webapi -n my-api --no-https
cd my-api

dotnet new webapi creates an ASP.NET Core Web API project. The --no-https option disables HTTPS for local development.

2. Write Your First API

Program.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;

var builder = WebApplication.CreateBuilder(args);

// CORS configuration
builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(policy =>
    {
        policy.WithOrigins("http://localhost:5173")
              .AllowAnyHeader()
              .AllowAnyMethod()
              .AllowCredentials();
    });
});

var app = builder.Build();
app.UseCors();

app.MapGet("/health", () => Results.Ok(new { status = "healthy" }));

app.MapGet("/api/hello", (string? name) =>
    Results.Ok(new { message = $"Hello, {name ?? "World"}!" }));

app.Run();

Run

Terminal
dotnet run

# Test (in a new terminal)
curl http://localhost:5000/health
curl "http://localhost:5000/api/hello?name=Choorai"

3. Add CRUD API

Program.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;

// Model definition
record Project(Guid Id, string Name, string Description, DateTime CreatedAt);

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors();
var app = builder.Build();
app.UseCors();

// In-memory storage
var projects = new List<Project>();
var lockObj = new object();

var api = app.MapGroup("/api/v1/projects");

// List all
api.MapGet("/", () =>
{
    lock (lockObj)
    {
        return Results.Ok(new { items = projects, total = projects.Count });
    }
});

// Get by ID
api.MapGet("/{id:guid}", (Guid id) =>
{
    lock (lockObj)
    {
        var project = projects.FirstOrDefault(p => p.Id == id);
        return project is not null
            ? Results.Ok(project)
            : Results.NotFound(new { error = "not found" });
    }
});

// Create
api.MapPost("/", (CreateProjectRequest req) =>
{
    var project = new Project(
        Guid.NewGuid(),
        req.Name,
        req.Description,
        DateTime.UtcNow
    );

    lock (lockObj)
    {
        projects.Add(project);
    }

    return Results.Created($"/api/v1/projects/{project.Id}", project);
});

// Update
api.MapPut("/{id:guid}", (Guid id, UpdateProjectRequest req) =>
{
    lock (lockObj)
    {
        var index = projects.FindIndex(p => p.Id == id);
        if (index == -1)
            return Results.NotFound(new { error = "not found" });

        projects[index] = projects[index] with
        {
            Name = req.Name,
            Description = req.Description
        };

        return Results.Ok(projects[index]);
    }
});

// Delete
api.MapDelete("/{id:guid}", (Guid id) =>
{
    lock (lockObj)
    {
        var removed = projects.RemoveAll(p => p.Id == id);
        return removed > 0
            ? Results.NoContent()
            : Results.NotFound(new { error = "not found" });
    }
});

app.Run();

// Request DTOs
record CreateProjectRequest(string Name, string Description);
record UpdateProjectRequest(string Name, string Description);

Minimal API and Record Types

C#'s record type defines immutable data objects concisely. The with expression creates a new instance with only specific fields changed.

4. Deploy with Docker

Dockerfile
# Build stage
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY *.csproj .
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app

# Runtime stage
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
WORKDIR /app
COPY --from=build /app .
ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080
ENTRYPOINT ["dotnet", "my-api.dll"]

Multi-stage builds reduce from SDK (~700MB) to runtime (~100MB). Alpine-based images are used to minimize size.

Terminal
# Local test
docker build -t my-dotnet-api .
docker run -p 8080:8080 my-dotnet-api

# Check image size (~100MB)
docker images my-dotnet-api

# Cloud Run deployment
gcloud run deploy my-dotnet-api \
  --source . \
  --region asia-northeast3 \
  --allow-unauthenticated \
  --min-instances 0 \
  --max-instances 1

Framework Comparison

Category .NET (Minimal API) Go (Chi) FastAPI
Image Size ~100MB ~15MB ~200MB
Cold Start ~200ms ~100ms ~500ms
Memory Usage ~40MB ~20MB ~80MB
Learning Curve Medium Medium Easy
Type System Static Static Dynamic

When Should You Choose .NET?

.NET Recommended

  • -- Enterprise/financial system development
  • -- Strong type safety is required
  • -- Windows server environments or Azure infrastructure
  • -- Game servers (Unity integration)

Consider Other Options

  • Minimal image size: Go
  • Quick prototype: Hono
  • Enterprise structure: NestJS

Last updated: February 22, 2026 · Version: v0.0.1

Send Feedback

Opens a new issue page with your message.

Open GitHub Issue