Eine Full-Stack .NET und React-CRUD-App erstellen: Schritt-für-Schritt-Anleitung
CRUD-Apps wirken oft einfach. Sie erfordern jedoch von Entwicklern, dieselben Integrationsprobleme zu lösen wie reale Produktivsysteme. Dazu gehören API-Design, Datenpersistenz, State Management im Frontend und Cross Origin Kommunikation.
In dieser Anleitung lernen Sie, wie Sie eine CRUD App mit .NET und React erstellen, um einen einfachen Create Read Update Delete Ablauf umzusetzen. Zu diesem Zweck kombiniert dieses Full-Stack .NET React Tutorial ASP.NET Core Minimal APIs für das Backend mit einer leistungsstarken React- und Vite-Anwendung für das Frontend. Außerdem lernen Sie einige Architekturmuster und Best Practices kennen, die von den meisten Entwicklungsteams beim Aufbau einer Full-Stack-Anwendung verwendet werden.
Wenn Sie die folgenden Schritte befolgen, erhalten Sie ein funktionsfähiges ASP.NET Core React CRUD-Beispiel und ein klares Verständnis dafür, wie ein React-Frontend mit einem modernen .NET-Backend verbunden wird.
Was wir erstellen
Wir erstellen ein kleines Produktmodul, das die klassischen CRUD Funktionen unterstützt: Produkte auflisten, neue Produkte erstellen, bestehende Produkte aktualisieren und Produkte löschen. Obwohl der Funktionsumfang überschaubar ist, spiegelt die Struktur reale Produktionsanwendungen wider und ist daher gut als Full-Stack .NET React-Projekt für Einsteiger geeignet.
Wie bereits erwähnt, verwenden wir ASP.NET Core Web API mit Minimal-APIs, EF Core und SQLite für das Backend. Im Frontend wird die App mit React und Vite umgesetzt. Für die API-Anfragen nutzen wir Axios. Alternativ können Sie auch die Fetch API verwenden und erhalten dasselbe Ergebnis.
Unten finden Sie ein Schema der Architektur, die wir in diesem Beispiel verwenden.
Warum man Minimal-APIs statt Controllern verwendet
Minimal-APIs bieten eine moderne Möglichkeit, HTTP-Endpunkte in ASP.NET Core zu erstellen, mit deutlich weniger Boilerplate Code als bei klassischen Controllern. Anstatt Controller und Attribute zu verwenden, werden die Endpunkte direkt in der Program.cs Datei oder in gruppierten Erweiterungen definiert. Dadurch wird der Boilerplate Code reduziert, während das Verhalten klar und nachvollziehbar bleibt.
Der Ansatz mit Minimal-APIs eignet sich besonders für:
- CRUD Services
- Microservices
- Leichtgewichtige APIs zur Anbindung von Frontend-Anwendungen
Für eine Schritt-für-Schritt-Anleitung für .NET React CRUD sorgen Minimal-APIs dafür, dass das Backend übersichtlich bleibt, ohne an Klarheit zu verlieren.
ASP.NET Core Backend erstellen
Beginnen wir mit der Erstellung unserer API. Der erste Schritt besteht darin, ein neues .NET-Projekt zu initialisieren, indem wir den folgenden Befehl ausführen:
dotnet new webapi -n CrudApi
cd CrudApi
Jetzt können wir die Anwendung wie folgt ausführen:
dotnet run
Wenn Sie diese Schritte befolgen, sollte Swagger standardmäßig verfügbar sein. Damit können Sie Endpunkte testen, während wir das ASP.NET Core Web API-Beispiel erstellen.
Datenmodell und Datenbank hinzufügen
Nun wollen wir die EF Core Pakete installieren:
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Design
Anschließend erstellen wir das Produktmodell wie unten dargestellt. Es ist zwar einfach, aber realistisch für eine C# .NET-Backend-Anleitung
Models/Product.cs
using System.ComponentModel.DataAnnotations;
namespace CrudApi.Models;
public class Product
{
public int Id { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; } = string.Empty;
[Range(0, 1_000_000)]
public decimal Price { get; set; }
public bool IsActive { get; set; } = true;
}
Sobald das Modell fertig ist, ist es an der Zeit, den DbContext zu erstellen:
Data/AppDbContext.cs
using Microsoft.EntityFrameworkCore;
using CrudApi.Models;
namespace CrudApi.Data;
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<Product> Products => Set<Product>();
}
Minimal-APIs konfigurieren
Um die Grundlagen für unsere API einzurichten, öffnen Sie die Program.cs und ersetzen den Standardinhalt durch Folgendes:
using CrudApi.Data;
using CrudApi.Models;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddCors(options =>
{
options.AddPolicy("frontend", policy =>
policy.WithOrigins("http://localhost:5173")
.AllowAnyHeader()
.AllowAnyMethod());
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseCors("frontend");
// Each MapGet, MapPost, or MapDelete call defines an HTTP endpoint directly, mapping a route to a
// handler function without requiring a controller class.
app.MapGet("/api/v1/products", async (AppDbContext db) =>
await db.Products.AsNoTracking().OrderBy(p => p.Id).ToListAsync()
);
app.MapGet("/api/v1/products/{id:int}", async (int id, AppDbContext db) =>
{
var product = await db.Products.FindAsync(id);
return product is null ? Results.NotFound() : Results.Ok(product);
});
app.MapPost("/api/v1/products", async (Product input, AppDbContext db) =>
{
db.Products.Add(input);
await db.SaveChangesAsync();
return Results.Created($"/api/v1/products/{input.Id}", input);
});
app.MapPut("/api/v1/products/{id:int}", async (int id, Product input, AppDbContext db) =>
{
if (id != input.Id) return Results.BadRequest();
var exists = await db.Products.AnyAsync(p => p.Id == id);
if (!exists) return Results.NotFound();
db.Entry(input).State = EntityState.Modified;
await db.SaveChangesAsync();
return Results.NoContent();
});
app.MapDelete("/api/v1/products/{id:int}", async (int id, AppDbContext db) =>
{
var product = await db.Products.FindAsync(id);
if (product is null) return Results.NotFound();
db.Products.Remove(product);
await db.SaveChangesAsync();
return Results.NoContent();
});
app.Run();
Fügen Sie anschließend die Verbindungszeichenfolge hinzu:
appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Data Source=crud.db"
}
}
Hinweis: In Produktivsystemen verwenden viele Teams DTOs und bilden dabei explizit nur die Felder ab, die aktualisiert werden dürfen. Dadurch werden versehentliche Überschreibungen vermieden und die Kontrolle über Datenänderungen erhöht.
Datenbank erstellen
Führen Sie den folgenden Befehl aus, um die Einrichtung der Datenbank abzuschließen:
dotnet ef migrations add InitialCreate
dotnet ef database update
Bis hierhin kann das Backend alle CRUD-Operationen in .NET und React über Minimal-APIs ausführen
React-Frontend erstellen
Da unser Backend nun bereit ist, wenden wir uns dem Frontend zu. Wie oben erwähnt, werden wir React und Vite für die Erstellung der Benutzeroberfläche verwenden. Der erste Schritt besteht darin, die React App zu erstellen:
npm create vite@latest crud-ui -- --template react
cd crud-ui
npm install
npm run dev
Damit wird der Frontend-Teil der React-Frontend- .NET-Backend-Anleitung initialisiert.
Um die API-Anfragen zu bearbeiten, werden wir Axios verwenden, daher muss auch dieses Paket installiert werden:
npm install axios
API Integration React und .NET
Um ein React Axios CRUD-Beispiel zu erstellen, beginnen wir damit, einen kleinen API-Client zu erstellen, der alle Operationen unterstützt. So können wir ihn in den verschiedenen Teilen unserer App wiederverwenden:
src/api/productsApi.js
import axios from "axios";
const api = axios.create({
baseURL: "https://localhost:5001/api/v1", // adjust port if needed
});
export async function getProducts() {
const res = await api.get("/products");
return res.data;
}
export async function createProduct(payload) {
const res = await api.post("/products", payload);
return res.data;
}
export async function updateProduct(id, payload) {
await api.put(`/products/${id}`, payload);
}
export async function deleteProduct(id) {
await api.delete(`/products/${id}`);
}
Tipp: Prüfen Sie die Start URL der API in der Konsolenausgabe oder in der Datei Properties/launchSettings.json und übernehmen Sie diese entsprechend in Axios.
React Crud UI Erstellen
Nun richten wir den Fokus auf die Komponenten. Um das State Management für CRUD in React einfach und nachvollziehbar zu halten, verwenden wir eine einzige Komponente in der App.jsx. Dabei kombinieren wir useEffect für das initiale Laden der Daten mit mehreren useState Hooks, um die Produkte und die Benutzereingaben zu speichern. Dieser Ansatz eignet sich gut für kleine bis mittelgroße CRUD-Prozesse und bleibt auch für Einsteiger gut verständlich.
Für komplexere Beispiele oder Implementierungen können Sie zusätzliche Strategien in Betracht ziehen, um die Lesbarkeit und Wartbarkeit des Codes zu verbessern. Dazu gehören Code Splitting, Custom Hooks sowie ein globales State Management um Zustände komponentenübergreifend zu verwalten, zum Beispiel mit der Context API oder Redux.
src/App.jsx
import { useEffect, useState } from "react";
import {
getProducts,
createProduct,
deleteProduct
} from "./api/productsApi";
export default function App() {
const [products, setProducts] = useState([]);
const [name, setName] = useState("");
const [price, setPrice] = useState("");
useEffect(() => {
loadProducts();
}, []);
async function loadProducts() {
const data = await getProducts();
setProducts(data);
}
async function onCreate(e) {
e.preventDefault();
const payload = {
name,
price: Number(price),
isActive: true
};
const created = await createProduct(payload);
setProducts((prev) => [...prev, created]);
setName("");
setPrice("");
}
async function onDelete(id) {
await deleteProduct(id);
setProducts((prev) => prev.filter(p => p.id !== id));
}
return (
<div style={{ maxWidth: 720, margin: "40px auto", fontFamily: "system-ui" }}>
<h1>Products CRUD</h1>
<form onSubmit={onCreate} style={{ display: "flex", gap: 8, marginBottom: 20 }}>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Product name"
required
/>
<input
value={price}
onChange={(e) => setPrice(e.target.value)}
placeholder="Price"
required
/>
<button>Add</button>
</form>
<ul style={{ listStyle: "none", padding: 0 }}>
{products.map(p => (
<li key={p.id} style={{ display: "flex", justifyContent: "space-between", marginBottom: 10 }}>
<span>
<strong>{p.name}</strong> - ${p.price}
</span>
<button onClick={() => onDelete(p.id)}>Delete</button>
</li>
))}
</ul>
</div>
);
}
Um eine reibungslose Benutzererfahrung zu gewährleisten, werden in Produktanwendungen in der Regel gängige UI Strategien eingesetzt. Dazu zählen Ladezustände, eine einfache Fehlerbehandlung sowie optimistische UI-Updates.
Skalierung in realen Projekten
Auch wenn es sich um eine Schritt-für-Schritt Full-Stack .NET React CRUD-Anleitung handelt, kann man durch verschiedene Verbesserungen im API-Bereich und im Frontend problemlos skalieren:
- Eine übersichtliche Projektarchitektur beibehalten und große Codeabschnitte in separate Dateien auslagern, um die Wartbarkeit zu verbessern.
- Minimal-APIs können mit MapGroup gruppiert werden.
- DTOs können EF-Entitäten ersetzen
- Validierungen lassen sich über Filter oder zusätzliche Bibliotheken integrieren.
- Ein Authentifizierungssystem kann sauber ergänzt werden.
- Globale State Management Systeme wie Context API oder Redux verbessern den Austausch von Informationen zwischen Komponenten und vermeiden die Nachteile von Prop Drilling.
- Frontend Bibliotheken für Datenabfragen mit Caching, etwa RTK Query oder React Query, sind eine gute Alternative, um API-Aufrufe zu reduzieren, insbesondere wenn mehrere Teile der Anwendung dieselben Backend Services nutzen.
Bereitstellung einer .NET und React-App (Überblick)
Deployment-Strategien sehen häufig vor, das React Build in einem statischen Hosting zu betreiben, zum Beispiel in einem AWS S3 Bucket oder über Azure Static Web Apps. Das Backend kann hingegen in einem App Service, Container oder auf einer virtuellen Maschine bereitgestellt werden. Beispielsweise lässt sich die React-App über Azure Static Web Apps bereitstellen, während die ASP.NET Core API im Azure App Service gehostet wird.
Unabhängig von den eingesetzten Tools ist es wichtig, eine klare Trennung der Verantwortlichkeiten einzuhalten. Nur so lässt sich eine saubere API-Integration zwischen React und .NET in allen Umgebungen gewährleisten.
Außerdem sollten sowohl im Backend als auch im Frontend Umgebungsvariablen verwendet werden, um sensible Informationen wie API Keys oder Datenbank-Zugangsdaten zu speichern. Dies hilft, Sicherheitslücken zu vermeiden.
Häufige Probleme und Lösungen
Beim Erlernen der Entwicklung einer CRUD-App mit .NET und React können einige typische Probleme auftreten. Wenn diese frühzeitig erkannt und behoben werden, lassen sich unnötige Debugging-Schritte vermeiden und die Entwicklung beschleunigen, insbesondere bei der ersten Integration von Frontend und Backend.
CORS
Ein häufiger Fehler, der beim ersten Zugriff auf die API in der Konsole erscheint, ist eine durch CORS Richtlinien blockierte Anfrage. Um dieses Problem zu beheben, sollten Sie die Datei Program.cs prüfen und sicherstellen, dass eine passende CORS Policy definiert ist und der Ursprung des Frontends freigegeben wurde.
HTTPs-Inkonsistenz
In Cloud-Umgebungen wird Ihre Anwendung in der Regel über HTTPS betrieben. Für die lokale Entwicklung reicht jedoch häufig HTTP aus. In diesem Fall ist es wichtig, dass sowohl Backend als auch Frontend dasselbe Protokoll verwenden. Andernfalls können Anfragen nicht erfolgreich ausgeführt werden. Wenn beim Zugriff auf die lokal gehostete API Fehler auftreten, sollten Sie prüfen, ob das vom Server bereitgestellte Protokoll mit dem vom Frontend verwendeten übereinstimmt.
Falsche Port-Konfiguration
In manchen Fällen wird im API-Projekt ein bestimmter Port definiert, während im Frontend ein anderer Port für die API-Anfragen verwendet wird. Prüfen Sie daher die Konfiguration des Axios-Clients sorgfältig, um Konfigurationsfehler auszuschließen.
Wann Controller sinnvoll sind
Minimal-APIs eignen sich hervorragend für CRUD-Services. Controller sind jedoch weiterhin sinnvoll, wenn APIs sehr groß werden oder stark auf Filter und Attribute angewiesen sind. Auch in Teams, die eine strikte MVC Struktur bevorzugen, fügt sich der controllerbasierte Ansatz oft besser in den Entwicklungsprozess ein.
Für die meisten CRUD-orientierten Services, die eine React-Anwendung unterstützen, stellen Minimal-APIs jedoch eine sehr gute Standardlösung dar.
Fazit
Diese Anleitung hat gezeigt, wie sich eine Full-Stack-.NET-React-Webanwendung mithilfe moderner ASP.NET Core Minimal-APIs umsetzen lässt. Wenn Sie die beschriebenen Schritte befolgen, erhalten Sie ein übersichtliches und gut lesbares Backend in Kombination mit einem einfachen React-Frontend. Genau diese Struktur entspricht den Anforderungen vieler realer Anwendungen. Selbstverständlich lässt sich dieses Beispiel um Funktionen wie Authentifizierung, Pagination, Suche oder Deployment Pipelines erweitern.
Wenn ein Unternehmen von Prototypen zu produktionsreifen Systemen übergeht, ist die Zusammenarbeit mit spezialisierten React Entwicklung Teams empfehlenswert. Solche Services helfen Ihnen dabei, eine stabile Grundlage zu schaffen und sicherzustellen, dass die Anwendung mit steigenden Projektanforderungen sicher skaliert.