Das Verständnis der Kernanforderungs- und Reaktionspipeline von ASP..-NET Teil MVC.,-Razor Seiten ,- und minimale APIs
Einleitung
Wir sind durch die Hosting-Layer gereist,. Middleware-Pipeline, und Routing-System.. Jetzt kommen wir zur Anwendung Layer, wo Ihre Geschäftslogik lebt. ASP...NET Core bietet drei primäre Programmiermodelle für den Aufbau von Web-Anwendungen:.
- MVC M(-Modell--Ansicht--Controller Ein strukturiertes Muster für den Aufbau komplexer Webanwendungen und APIs
- Razor-Seiten Eine Seite, die auf - basierte Modell, das Gebäude Web-UI vereinfacht
- Minimale APIs Ein leichter Ansatz zum Aufbau von HTTP-APIs mit minimaler Zeremonie
Jedes Modell sitzt auf der gleichen Routing- und Endpoint-Infrastruktur, die wir in Teil .4, , aber sie bieten verschiedene Entwicklererfahrungen und sind für unterschiedliche Szenarien optimiert . . . In diesem Teil erfahren Sie, wie jedes Modell Anfragen verarbeitet und Antworten erzeugt .
HINWEIS:-Dies ist Teil meiner Experimente mit KI-/-ein Weg, um zu verbringen $1000-Calude Code Web-credits..-I'-ve gefüttert dies ein BUNCH von Papieren...,-mein Verständnis Fragen Ich musste diesen Artikel generieren.-Es macht Spaß und füllt eine Lücke ich habe '-t überall sonst gefüllt gesehen
Das Anwendungsmodell Spectrum
graph LR
A[Minimal APIs] --> B[Razor Pages] --> C[MVC]
note1[Lightweight<br/>Simple APIs]
note2[Page-focused<br/>Web UI]
note3[Full-featured<br/>Complex apps]
A -.-> note1
B -.-> note2
C -.-> note3
Wann Sie jedes Mal verwenden müssen
- Minimale APIsEinfache APIs serverlose Funktionen
- Razor-Seiten:-Seite--fokussierte Web-Apps,-CRUD-Operationen,-Formulare--schwere Anwendungen
- MVCKomplexe Web-Apps – APIs mit komplexem Routing – Anwendungen, die eine klare Trennung der Bedenken erfordern
Alle drei teilen die gleiche Pipeline, unterscheiden sich aber in der Art und Weise, wie sie Code organisieren und Anfragen bearbeiten.
Minimale APIs - Der leichte Ansatz
Minimale APIs wurden in .-NET eingeführt, um die Entwicklung von APIs zu vereinfachen. .-Sie reduzieren Zeremonie und Boilerplate, damit es schneller ist, HTTP-APIs zu bauen.
Grundstruktur
var builder = WebApplication.CreateBuilder(args);
// Add services
builder.Services.AddSingleton<IDataService, DataService>();
var app = builder.Build();
// Define endpoints
app.MapGet("/", () => "Hello World!");
app.MapGet("/users/{id}", (int id) => new User(id, "John Doe"));
app.MapPost("/users", (User user) =>
{
// Save user
return Results.Created($"/users/{user.Id}", user);
});
app.Run();
record User(int Id, string Name);
Verarbeitungsfluss anfordern
sequenceDiagram
participant Client
participant Routing
participant Binding as Parameter Binding
participant Filter as Endpoint Filters
participant Handler as Route Handler
participant Result as Result Execution
Client->>Routing: HTTP Request
Routing->>Routing: Match route pattern
Routing->>Binding: Extract parameters
Note over Binding: Bind from:<br/>- Route values<br/>- Query string<br/>- Headers<br/>- Body<br/>- Services
Binding->>Filter: Execute filters (if any)
Filter->>Handler: Invoke handler delegate
Note over Handler: Execute your code
Handler->>Result: Return IResult or object
Result->>Result: Execute result<br/>(JSON, file, redirect, etc.)
Result->>Client: HTTP Response
Parameter-Bindung im Detail
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IEmailService, EmailService>();
var app = builder.Build();
// Simple types from route
app.MapGet("/users/{id}", (int id) =>
new { userId = id });
// Multiple sources
app.MapGet("/search",
(string q, // From query string
int page = 1, // From query string with default
[FromHeader(Name = "X-API-Key")] string apiKey, // From header
[FromServices] IEmailService emailService) // From DI container
=>
{
return new
{
Query = q,
Page = page,
ApiKey = apiKey,
EmailServiceType = emailService.GetType().Name
};
});
// Complex type from body
app.MapPost("/users",
([FromBody] CreateUserRequest request,
[FromServices] IUserService userService,
HttpContext context) =>
{
var user = userService.CreateUser(request);
return Results.Created($"/users/{user.Id}", user);
});
// Custom binding with AsParameters
app.MapGet("/products", ([AsParameters] ProductQuery query) =>
{
// All parameters bundled in one object
return new { query.Category, query.MinPrice, query.MaxPrice };
});
app.Run();
record CreateUserRequest(string Name, string Email);
// AsParameters allows grouping parameters
record ProductQuery(string? Category, decimal MinPrice = 0, decimal MaxPrice = 1000);
interface IEmailService { }
class EmailService : IEmailService { }
interface IUserService
{
User CreateUser(CreateUserRequest request);
}
class User
{
public int Id { get; set; }
}
Verbindliche Quellen-Visualisierung
graph TD
Request[HTTP Request] --> Route[Route Values<br/>users/123]
Request --> Query[Query String<br/>?page=1&size=10]
Request --> Headers[Headers<br/>X-API-Key: abc123]
Request --> Body[Request Body<br/>JSON/XML]
Request --> Services[DI Container<br/>Registered Services]
Route --> Handler[Handler Method]
Query --> Handler
Headers --> Handler
Body --> Handler
Services --> Handler
style Request stroke:#ef4444,stroke-width:3px
style Handler stroke:#10b981,stroke-width:3px
Ergebnisse und Reaktionen
Minimale APIs unterstützen mehrere Antworttypen
var app = WebApplication.CreateBuilder().Build();
// 1. Direct return - serialized as JSON
app.MapGet("/users/{id}", (int id) =>
new User(id, "John Doe", "[email protected]"));
// 2. IResult - typed results
app.MapGet("/ok", () => Results.Ok(new { status = "OK" }));
app.MapGet("/created", () => Results.Created("/users/1", new User(1, "Jane", "[email protected]")));
app.MapGet("/notfound", () => Results.NotFound());
app.MapGet("/redirect", () => Results.Redirect("/new-location"));
app.MapGet("/file", () => Results.File("file.pdf", "application/pdf"));
// 3. Typed results with Results<T1, T2, ...>
app.MapGet("/typed/{id}", Results<Ok<User>, NotFound> (int id) =>
{
var user = GetUser(id);
return user != null
? TypedResults.Ok(user)
: TypedResults.NotFound();
});
// 4. Custom response
app.MapGet("/custom", (HttpContext context) =>
{
context.Response.StatusCode = 200;
context.Response.Headers["X-Custom-Header"] = "Value";
return context.Response.WriteAsync("Custom response");
});
// 5. Stream response
app.MapGet("/stream", async (HttpContext context) =>
{
context.Response.ContentType = "text/plain";
await context.Response.StartAsync();
for (int i = 0; i < 10; i++)
{
await context.Response.WriteAsync($"Line {i}\n");
await context.Response.Body.FlushAsync();
await Task.Delay(100);
}
});
app.Run();
record User(int Id, string Name, string Email);
User? GetUser(int id) => id == 1 ? new User(1, "John", "[email protected]") : null;
Endpunktfilter
Mit Filtern können Sie Code vor und nach dem Handler ausführen.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Global filter using middleware
app.Use(async (context, next) =>
{
Console.WriteLine($"Before: {context.Request.Path}");
await next(context);
Console.WriteLine($"After: {context.Response.StatusCode}");
});
// Endpoint-specific filter
app.MapGet("/filtered", () => "Hello")
.AddEndpointFilter(async (context, next) =>
{
Console.WriteLine("Before handler");
var result = await next(context);
Console.WriteLine("After handler");
return result;
});
// Filter with logic
app.MapGet("/validated", (int age) => $"Age: {age}")
.AddEndpointFilter(async (context, next) =>
{
var age = context.GetArgument<int>(0);
if (age < 0)
{
return Results.BadRequest("Age cannot be negative");
}
return await next(context);
});
// Reusable filter
app.MapGet("/timed", () =>
{
Thread.Sleep(100);
return "Done";
})
.AddEndpointFilter<TimingFilter>();
app.Run();
// Reusable filter class
class TimingFilter : IEndpointFilter
{
private readonly ILogger<TimingFilter> _logger;
public TimingFilter(ILogger<TimingFilter> logger)
{
_logger = logger;
}
public async ValueTask<object?> InvokeAsync(
EndpointFilterInvocationContext context,
EndpointFilterDelegate next)
{
var sw = Stopwatch.StartNew();
var result = await next(context);
sw.Stop();
_logger.LogInformation(
"Endpoint {Endpoint} took {Elapsed}ms",
context.HttpContext.Request.Path,
sw.ElapsedMilliseconds);
return result;
}
}
Filter-Pipeline - :
graph LR
A[Request] --> B[Filter 1 Before]
B --> C[Filter 2 Before]
C --> D[Handler]
D --> E[Filter 2 After]
E --> F[Filter 1 After]
F --> G[Response]
Razor-Seiten:-Seite--basierte Web UI
Razor Pages stellt eine Seite zur Verfügung.--fokussiertes Modell, das Gebäude Web UI einfacher als MVC für viele Szenarien macht.
Struktur
Pages/
_ViewImports.cshtml
_ViewStart.cshtml
Index.cshtml
Index.cshtml.cs
Products/
List.cshtml
List.cshtml.cs
Details.cshtml
Details.cshtml.cs
Grundlegende Razor Seite
Seiten - /-Index -.-cshtml-:-
@page
@model IndexModel
<h1>Welcome</h1>
<p>Current time: @Model.CurrentTime</p>
<form method="post">
<input type="text" asp-for="Name" />
<button type="submit">Submit</button>
</form>
@if (!string.IsNullOrEmpty(Model.Message))
{
<p>@Model.Message</p>
}
Seiten /-Index-.-cshtml-.-CS:-
public class IndexModel : PageModel
{
[BindProperty]
public string Name { get; set; } = "";
public string CurrentTime { get; set; } = "";
public string Message { get; set; } = "";
public void OnGet()
{
CurrentTime = DateTime.Now.ToString("HH:mm:ss");
}
public IActionResult OnPost()
{
if (string.IsNullOrWhiteSpace(Name))
{
Message = "Please enter a name";
return Page();
}
Message = $"Hello, {Name}!";
return Page();
}
}
Verarbeitungsfluss anfordern
sequenceDiagram
participant Client
participant Routing
participant PageModel as Page Model
participant Binding as Model Binding
participant Handler as Handler Method
participant Filters as Page Filters
participant View as Razor View
Client->>Routing: GET /products/details?id=123
Routing->>PageModel: Create PageModel instance
PageModel->>Binding: Bind parameters
Note over Binding: Bind from:<br/>- Route data<br/>- Query string<br/>- Form data<br/>- Services
Binding->>Filters: Execute filters (OnPageHandlerExecuting)
Filters->>Handler: Call OnGet/OnPost
Handler->>Handler: Execute logic<br/>Load data
Handler->>Filters: Return IActionResult
Filters->>Filters: OnPageHandlerExecuted
Filters->>View: Render Razor view
View->>View: Execute Razor syntax<br/>Generate HTML
View->>Client: HTML Response
Routenparameter
// Pages/Products/Details.cshtml.cs
@page "{id:int}"
@model DetailsModel
public class DetailsModel : PageModel
{
private readonly IProductService _productService;
public DetailsModel(IProductService productService)
{
_productService = productService;
}
public Product? Product { get; set; }
public async Task<IActionResult> OnGetAsync(int id)
{
Product = await _productService.GetProductAsync(id);
if (Product == null)
{
return NotFound();
}
return Page();
}
}
URL.:. /products/details/123 → id = 123
Mehrere Handler
public class ContactModel : PageModel
{
[BindProperty]
public ContactForm Form { get; set; } = new();
public string Message { get; set; } = "";
// Default GET handler
public void OnGet()
{
}
// Default POST handler
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page();
}
// Send email
Message = "Message sent!";
return Page();
}
// Named handler: POST with ?handler=subscribe
public IActionResult OnPostSubscribe(string email)
{
// Subscribe to newsletter
Message = $"Subscribed: {email}";
return Page();
}
// Named handler: POST with ?handler=unsubscribe
public IActionResult OnPostUnsubscribe(string email)
{
// Unsubscribe
Message = $"Unsubscribed: {email}";
return Page();
}
}
public class ContactForm
{
[Required]
public string Name { get; set; } = "";
[Required, EmailAddress]
public string Email { get; set; } = "";
[Required]
public string Message { get; set; } = "";
}
Verwendung in Razor-:
@page
@model ContactModel
<!-- Default handler (OnPost) -->
<form method="post">
<input asp-for="Form.Name" />
<input asp-for="Form.Email" />
<textarea asp-for="Form.Message"></textarea>
<button type="submit">Send</button>
</form>
<!-- Named handler (OnPostSubscribe) -->
<form method="post" asp-page-handler="Subscribe">
<input type="email" name="email" />
<button type="submit">Subscribe</button>
</form>
<!-- Named handler (OnPostUnsubscribe) -->
<form method="post" asp-page-handler="Unsubscribe">
<input type="email" name="email" />
<button type="submit">Unsubscribe</button>
</form>
Seitenfilter
// Global page filter
public class LoggingPageFilter : IPageFilter
{
private readonly ILogger<LoggingPageFilter> _logger;
public LoggingPageFilter(ILogger<LoggingPageFilter> logger)
{
_logger = logger;
}
public void OnPageHandlerSelected(PageHandlerSelectedContext context)
{
_logger.LogInformation(
"Page handler selected: {HandlerMethod}",
context.HandlerMethod?.Name);
}
public void OnPageHandlerExecuting(PageHandlerExecutingContext context)
{
_logger.LogInformation("Page handler executing");
}
public void OnPageHandlerExecuted(PageHandlerExecutedContext context)
{
_logger.LogInformation(
"Page handler executed, result: {Result}",
context.Result?.GetType().Name);
}
}
// Register globally
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages(options =>
{
options.Conventions.ConfigureFilter(new LoggingPageFilter(
LoggerFactory.Create(b => b.AddConsole()).CreateLogger<LoggingPageFilter>()
));
});
var app = builder.Build();
MVC-:-Das vollwertige Rahmenwerk
MVC (-Modell--Ansicht--Controller)-System bietet ein strukturiertes Muster für den Aufbau komplexer Anwendungen mit klarer Trennung der Bedenken
Struktur
Controllers/
HomeController.cs
ProductsController.cs
ApiController.cs
Models/
Product.cs
CreateProductViewModel.cs
Views/
Home/
Index.cshtml
About.cshtml
Products/
List.cshtml
Details.cshtml
Shared/
_Layout.cshtml
_ValidationScriptsPartial.cshtml
MVC-Anforderung Verarbeitungsfluss
sequenceDiagram
participant Client
participant Routing
participant Controller as Controller Factory
participant Filters
participant ModelBinding as Model Binding
participant Action as Action Method
participant Result as Action Result
participant View
Client->>Routing: GET /products/details/123
Routing->>Controller: Create controller instance
Controller->>Filters: Authorization filters
alt Not authorized
Filters-->>Client: 401/403
end
Filters->>Filters: Resource filters
Filters->>ModelBinding: Bind parameters
Note over ModelBinding: Bind from:<br/>- Route values<br/>- Query string<br/>- Form data<br/>- Body<br/>- Services
ModelBinding->>Filters: Action filters (OnActionExecuting)
Filters->>Action: Invoke action method
Action->>Action: Execute business logic
Action->>Result: Return IActionResult
Result->>Filters: Action filters (OnActionExecuted)
Filters->>Filters: Result filters (OnResultExecuting)
Filters->>View: Execute result (render view)
View->>Filters: Result filters (OnResultExecuted)
Filters->>Filters: Resource filters (OnResourceExecuted)
Filters->>Client: HTML/JSON Response
Grundsteuerung
public class ProductsController : Controller
{
private readonly IProductService _productService;
private readonly ILogger<ProductsController> _logger;
public ProductsController(
IProductService productService,
ILogger<ProductsController> logger)
{
_productService = productService;
_logger = logger;
}
// GET: /products
public async Task<IActionResult> Index()
{
var products = await _productService.GetAllProductsAsync();
return View(products);
}
// GET: /products/details/5
public async Task<IActionResult> Details(int id)
{
var product = await _productService.GetProductAsync(id);
if (product == null)
{
return NotFound();
}
return View(product);
}
// GET: /products/create
public IActionResult Create()
{
return View();
}
// POST: /products/create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(CreateProductViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var product = await _productService.CreateProductAsync(model);
return RedirectToAction(nameof(Details), new { id = product.Id });
}
// DELETE: /products/delete/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Delete(int id)
{
await _productService.DeleteProductAsync(id);
return RedirectToAction(nameof(Index));
}
}
Ergebnisse der Maßnahmen
public class ResultsController : Controller
{
// View result
public IActionResult Index()
{
return View(); // Views/Results/Index.cshtml
}
// View with model
public IActionResult Details(int id)
{
var model = new Product { Id = id, Name = "Product" };
return View(model);
}
// Specific view
public IActionResult Custom()
{
return View("CustomViewName");
}
// JSON result
public IActionResult GetJson()
{
return Json(new { id = 1, name = "Product" });
}
// Redirect
public IActionResult RedirectSample()
{
return RedirectToAction("Index", "Home");
}
// Redirect to route
public IActionResult RedirectToRouteSample()
{
return RedirectToRoute("Default", new { controller = "Home", action = "Index" });
}
// Content result
public IActionResult GetText()
{
return Content("Plain text response", "text/plain");
}
// File result
public IActionResult DownloadFile()
{
var bytes = System.IO.File.ReadAllBytes("file.pdf");
return File(bytes, "application/pdf", "download.pdf");
}
// Status code result
public IActionResult StatusCodeSample()
{
return StatusCode(503); // Service Unavailable
}
// Not found
public IActionResult NotFoundSample()
{
return NotFound(); // 404
}
// Bad request
public IActionResult BadRequestSample()
{
return BadRequest("Invalid input"); // 400
}
// Unauthorized
public IActionResult UnauthorizedSample()
{
return Unauthorized(); // 401
}
}
Modellbindung
public class BindingController : Controller
{
// Simple parameter from route
// Route: products/{id}
public IActionResult Get(int id)
{
return Ok(new { id });
}
// Multiple parameters from route and query
// Route: products/{id}?includeDetails=true
public IActionResult GetWithDetails(int id, bool includeDetails = false)
{
return Ok(new { id, includeDetails });
}
// Complex type from form
[HttpPost]
public IActionResult CreateFromForm([FromForm] Product product)
{
return Ok(product);
}
// Complex type from body (JSON)
[HttpPost]
public IActionResult CreateFromBody([FromBody] Product product)
{
return Ok(product);
}
// From header
public IActionResult HeaderSample([FromHeader(Name = "X-API-Key")] string apiKey)
{
return Ok(new { apiKey });
}
// From services
public IActionResult ServiceSample([FromServices] IProductService productService)
{
var count = productService.GetCount();
return Ok(new { count });
}
// Custom binding
public IActionResult CustomBinding([ModelBinder(typeof(CustomBinder))] CustomModel model)
{
return Ok(model);
}
}
// Custom model binder
public class CustomBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue("custom").FirstValue;
var model = new CustomModel { Value = value?.ToUpper() ?? "" };
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
public class CustomModel
{
public string Value { get; set; } = "";
}
Aktionsfilter
Filter führen Code an verschiedenen Punkten in der Request-Pipeline aus
graph TD
A[Request] --> B[Authorization Filters]
B --> C{Authorized?}
C -->|No| D[Challenge Result]
C -->|Yes| E[Resource Filters<br/>OnResourceExecuting]
E --> F[Model Binding]
F --> G[Action Filters<br/>OnActionExecuting]
G --> H[Action Method]
H --> I[Action Filters<br/>OnActionExecuted]
I --> J[Exception Filters<br/>if exception]
J --> K[Result Filters<br/>OnResultExecuting]
K --> L[Action Result]
L --> M[Result Filters<br/>OnResultExecuted]
M --> N[Resource Filters<br/>OnResourceExecuted]
N --> O[Response]
D --> O
style H stroke:#ef4444,stroke-width:3px
style L stroke:#10b981,stroke-width:3px
Filtertypen
// 1. Authorization filter
public class CustomAuthorizeFilter : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
if (!context.HttpContext.User.Identity?.IsAuthenticated ?? true)
{
context.Result = new UnauthorizedResult();
}
}
}
// 2. Resource filter
public class CacheResourceFilter : IResourceFilter
{
private static readonly Dictionary<string, object> _cache = new();
public void OnResourceExecuting(ResourceExecutingContext context)
{
var cacheKey = context.HttpContext.Request.Path;
if (_cache.TryGetValue(cacheKey, out var cachedResult))
{
context.Result = (IActionResult)cachedResult;
// Short-circuit the pipeline
}
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
var cacheKey = context.HttpContext.Request.Path;
if (context.Result != null)
{
_cache[cacheKey] = context.Result;
}
}
}
// 3. Action filter
public class LogActionFilter : IActionFilter
{
private readonly ILogger<LogActionFilter> _logger;
public LogActionFilter(ILogger<LogActionFilter> logger)
{
_logger = logger;
}
public void OnActionExecuting(ActionExecutingContext context)
{
_logger.LogInformation(
"Executing action {Action} with arguments: {Arguments}",
context.ActionDescriptor.DisplayName,
string.Join(", ", context.ActionArguments.Select(kvp => $"{kvp.Key}={kvp.Value}")));
}
public void OnActionExecuted(ActionExecutedContext context)
{
_logger.LogInformation(
"Executed action {Action}, result: {Result}",
context.ActionDescriptor.DisplayName,
context.Result?.GetType().Name);
}
}
// 4. Exception filter
public class CustomExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
if (context.Exception is ArgumentException)
{
context.Result = new BadRequestObjectResult(new
{
error = context.Exception.Message
});
context.ExceptionHandled = true;
}
}
}
// 5. Result filter
public class HeaderResultFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
context.HttpContext.Response.Headers["X-Custom-Header"] = "Value";
}
public void OnResultExecuted(ResultExecutedContext context)
{
// After result execution
}
}
// Register filters
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews(options =>
{
// Global filters
options.Filters.Add<LogActionFilter>();
options.Filters.Add<CustomExceptionFilter>();
});
// Or apply to specific controllers/actions
[ServiceFilter(typeof(LogActionFilter))]
public class ProductsController : Controller
{
[TypeFilter(typeof(CacheResourceFilter))]
public IActionResult Index()
{
return View();
}
}
API-Controller
[ApiController]
[Route("api/[controller]")]
public class ProductsApiController : ControllerBase
{
private readonly IProductService _productService;
public ProductsApiController(IProductService productService)
{
_productService = productService;
}
// GET: api/products
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<Product>), 200)]
public async Task<ActionResult<IEnumerable<Product>>> GetProducts()
{
var products = await _productService.GetAllProductsAsync();
return Ok(products);
}
// GET: api/products/5
[HttpGet("{id}")]
[ProducesResponseType(typeof(Product), 200)]
[ProducesResponseType(404)]
public async Task<ActionResult<Product>> GetProduct(int id)
{
var product = await _productService.GetProductAsync(id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
// POST: api/products
[HttpPost]
[ProducesResponseType(typeof(Product), 201)]
[ProducesResponseType(400)]
public async Task<ActionResult<Product>> CreateProduct(CreateProductDto dto)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var product = await _productService.CreateProductAsync(dto);
return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, product);
}
// PUT: api/products/5
[HttpPut("{id}")]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
public async Task<IActionResult> UpdateProduct(int id, UpdateProductDto dto)
{
if (id != dto.Id)
{
return BadRequest();
}
var exists = await _productService.ExistsAsync(id);
if (!exists)
{
return NotFound();
}
await _productService.UpdateProductAsync(dto);
return NoContent();
}
// DELETE: api/products/5
[HttpDelete("{id}")]
[ProducesResponseType(204)]
[ProducesResponseType(404)]
public async Task<IActionResult> DeleteProduct(int id)
{
var exists = await _productService.ExistsAsync(id);
if (!exists)
{
return NotFound();
}
await _productService.DeleteProductAsync(id);
return NoContent();
}
}
Vergleich der drei Modelle
graph TB
subgraph "Minimal APIs"
MA1[Direct Route Registration]
MA2[Lambda Handlers]
MA3[Endpoint Filters]
MA4[Simple Binding]
MA1 --> MA2
MA2 --> MA3
MA3 --> MA4
end
subgraph "Razor Pages"
RP1[Page-Based Routing]
RP2[PageModel Handlers]
RP3[Page Filters]
RP4[Razor View Engine]
RP1 --> RP2
RP2 --> RP3
RP3 --> RP4
end
subgraph "MVC"
MVC1[Controller-Based Routing]
MVC2[Action Methods]
MVC3[Multiple Filter Types]
MVC4[View/JSON Results]
MVC1 --> MVC2
MVC2 --> MVC3
MVC3 --> MVC4
end
style MA1 stroke:#10b981,stroke-width:3px
style RP1 stroke:#ef4444,stroke-width:3px
style MVC1 stroke:#6366f1,stroke-width:3px
| Funktionen für minimale APIs von | ® Razor Seiten von MVC- | ® |
|---|---|---|
| Komplexität Mittel Mittel Hoch Hoch Hoch | ||
| Kesselplatte Minimales, mittelschweres, signifikantes und mittleres | ||
| Das Beste für | -APIs ,-Microservices Seite --fokussierte Web-Apps Komplexe Anwendungen | |
| Routing Inline-Convention-Konvention-Attribute-Konzession-Konkret-Konvent-Kontrakt-Kongreß-Konzentrate-Konzentrat-Konsortium-Kontingente-Inline-Kontinenz-Konfektion-Kontribute | ||
| Trennung der Bedenken Mittel Mittel Hoch Hoch Hoch | ||
| Prüfbarkeit Sehr gut, sehr gut. | ||
| Leistung Schnellstes, schnelles und schnelles | ||
| Lernkurve Leichtstes, leichtes, mittelschweres und leichtes. |
Schlüssel zum Mitnehmen
- Minimale APIs eine leichtgewichtige, low---Zeremonie zum Erstellen von HTTP-APIs bereitstellen
- Razor-Seiten Vereinfachung der Web-Entwicklung mit einem Page-Modell
- MVC bietet Full---Featured Rahmen mit klarer Trennung von Bedenken
- Alle drei Modelle verwenden die gleiche zugrunde liegende Routing- und Endpunktinfrastruktur
- Parameterbindung funktioniert ähnlich über alle Modelle hinweg
- Filter bieten Cross---Schneidbedenken in verschiedenen Pipeline-Phasen
- Wählen Sie das Modell basierend auf Ihrer Anwendung.'s Komplexität und Anforderungen
- Sie können Modelle in der gleichen Anwendung mischen
Jedes Programmiermodell bietet eine andere Entwickler-Erfahrung bei der Nutzung der gleichen leistungsstarken ASP .. ?NET Core Pipeline ?. ? Verstehen Sie alle drei hilft Ihnen, das richtige Werkzeug für jedes Szenario wählen ?