# ASP.NET Core 10.0.1 ASP.NET Core is a cross-platform, high-performance, open-source framework for building modern, cloud-based, internet-connected applications. It provides a unified programming model for building web UI and web APIs, supporting multiple architectural patterns including MVC, Razor Pages, Blazor components, minimal APIs, gRPC services, and SignalR real-time communication. The framework runs on .NET 10, offering exceptional performance, built-in dependency injection, comprehensive middleware pipeline, and first-class support for containerization and cloud deployment. ASP.NET Core emphasizes developer productivity through minimal ceremony APIs, convention-over-configuration defaults, and extensive tooling support. It includes modular components for authentication (JWT, OAuth, OpenID Connect, Cookies), authorization policies, data protection with automatic key management, distributed caching, health monitoring, rate limiting, and comprehensive HTTP client management. The framework's middleware pipeline enables flexible request processing, while built-in support for WebSockets, Server-Sent Events, and HTTP/2+ ensures modern protocol compatibility for building responsive, scalable applications. ## WebApplication and WebApplicationBuilder Modern hosting API providing simplified application configuration with reduced boilerplate. Replaces older host builder patterns with a streamlined approach that configures services, middleware, and endpoints in a single fluent API. Supports dependency injection, logging, configuration, and environment-based setup out of the box. ```csharp // Basic minimal API application var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.Logger.LogInformation($"Current process ID: {Environment.ProcessId}"); // Simple endpoints app.MapGet("/plaintext", () => "Hello, World!"); app.MapGet("/", () => $""" Operating System: {Environment.OSVersion} .NET version: {Environment.Version} Username: {Environment.UserName} Date and Time: {DateTime.Now} """); // JSON response app.MapGet("/json", () => new { message = "Hello, World!" }).WithTags("json"); // Route parameters app.MapGet("/hello/{name}", (string name) => $"Hello, {name}!"); // Typed results with union types for multiple possible responses app.MapGet("/todo/{id}", Results, NotFound, BadRequest> (int id) => id switch { <= 0 => TypedResults.BadRequest(), >= 1 and <= 10 => TypedResults.Ok(new Todo(id, "Walk the dog")), _ => TypedResults.NotFound() }); // POST with model binding app.MapPost("/todos", (TodoBindable todo) => todo); app.MapGet("/todos", () => new Todo[] { new Todo(1, "Walk the dog"), new Todo(2, "Come back home") }); app.Run(); internal record Todo(int Id, string Title); public class TodoBindable : IBindableFromHttpContext { public int Id { get; set; } public string Title { get; set; } = string.Empty; public bool IsComplete { get; set; } public static ValueTask BindAsync(HttpContext context, ParameterInfo parameter) { return ValueTask.FromResult(new TodoBindable { Id = 1, Title = "I was bound from IBindableFromHttpContext.BindAsync!" }); } } ``` ## Route Groups and Endpoint Filters Organize related endpoints into logical groups with shared route prefixes, filters, and metadata. Endpoint filters enable request/response interception with dependency injection support, providing middleware-like behavior at the endpoint level. ```csharp var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); var outer = app.MapGroup("/outer"); var inner = outer.MapGroup("/inner"); // Add filter factory that runs for all endpoints in this group inner.AddEndpointFilterFactory((routeContext, next) => { IReadOnlyList? tags = null; return async invocationContext => { var endpoint = invocationContext.HttpContext.GetEndpoint(); tags ??= endpoint?.Metadata.GetMetadata()?.Tags ?? Array.Empty(); Console.WriteLine("Running filter!"); var result = await next(invocationContext); return $"{result} | /inner filter! Tags: {(tags.Count == 0 ? "(null)" : string.Join(", ", tags))}"; }; }); outer.MapGet("/outerget", () => "I'm nested."); inner.MapGet("/innerget", () => "I'm more nested."); // Multiple nested groups with route parameters var superNested = inner.MapGroup("/group/{groupName}") .MapGroup("/nested/{nestedName}") .WithTags("nested", "more", "tags"); superNested.MapGet("/", (string groupName, string nestedName) => { return $"Hello from {groupName}:{nestedName}!"; }); // Null result handling app.MapGet("/null-result", IResult () => null!); // Problem details with extensions var extensions = new Dictionary() { { "traceId", "traceId123" } }; var errors = new Dictionary() { { "Title", new[] { "The Title field is required." } } }; app.MapGet("/problem/{problemType}", (string problemType) => problemType switch { "plain" => Results.Problem(statusCode: 500, extensions: extensions), "object" => Results.Problem(new ProblemDetails() { Status = 500, Extensions = { { "traceId", "traceId123" } } }), "validation" => Results.ValidationProblem(errors, statusCode: 400, extensions: extensions), "objectValidation" => Results.Problem(new HttpValidationProblemDetails(errors) { Status = 400, Extensions = { { "traceId", "traceId123" } } }), "validationTyped" => TypedResults.ValidationProblem(errors, extensions: extensions), _ => TypedResults.NotFound() }); app.Run(); ``` ## MVC Controllers and Views Traditional Model-View-Controller pattern with server-side rendering using Razor views. Provides structured application architecture with controller classes handling requests, returning views, and managing antiforgery protection. ```csharp // Program.cs var builder = WebApplication.CreateBuilder(args); // Add services to the container builder.Services.AddControllersWithViews(); var app = builder.Build(); // Configure HTTP request pipeline if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); app.UseAntiforgery(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run(); // Controllers/HomeController.cs public class HomeController : Controller { private readonly ILogger _logger; public HomeController(ILogger logger) { _logger = logger; } public IActionResult Index([FromQuery] bool antiforgery = true) { ViewBag.EnableAntiforgery = antiforgery; return View(); } [HttpPost] [ValidateAntiForgeryToken] public ActionResult Index(Todo todo) { if (!ModelState.IsValid) { return View(todo); } _logger.LogInformation("Todo created: {Title}", todo.Title); return RedirectToAction(nameof(Index)); } [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult Error() { return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } } public class Todo { [Required] public string Title { get; set; } public bool IsComplete { get; set; } } ``` ## Session Middleware Store user-specific data across requests using in-memory or distributed session storage. Sessions use cookies to track user identity and store server-side state with configurable expiration and storage backends. ```csharp var builder = WebApplication.CreateBuilder(args); // Configure session services builder.Services.AddDistributedMemoryCache(); builder.Services.AddSession(options => { options.IdleTimeout = TimeSpan.FromMinutes(30); options.Cookie.HttpOnly = true; options.Cookie.IsEssential = true; }); var app = builder.Build(); app.UseSession(); app.MapGet("/session", async (HttpContext context) => { // Get visit count from session int visits = context.Session.GetInt32("visits") ?? 0; context.Session.SetInt32("visits", ++visits); // Store complex objects as JSON var user = context.Session.GetString("user"); if (user == null) { context.Session.SetString("user", JsonSerializer.Serialize(new { Name = "John", LastVisit = DateTime.UtcNow })); } return Results.Ok(new { visits, user }); }); app.MapPost("/session/clear", (HttpContext context) => { context.Session.Clear(); return Results.Ok("Session cleared"); }); app.Run(); ``` ## Static Files and Directory Browsing Serve static content (HTML, CSS, JavaScript, images) from the wwwroot directory with configurable options for caching, content types, and directory browsing. Supports response compression and custom file providers. ```csharp var builder = WebApplication.CreateBuilder(args); builder.Services.AddDirectoryBrowser(); builder.Services.AddResponseCompression(options => { options.EnableForHttps = true; }); var app = builder.Build(); app.UseResponseCompression(); // Serve static files from wwwroot with default options app.UseStaticFiles(); // Serve files from custom directory with directory browsing app.UseFileServer(new FileServerOptions { FileProvider = new PhysicalFileProvider( Path.Combine(builder.Environment.ContentRootPath, "CustomFiles")), RequestPath = "/files", EnableDirectoryBrowsing = true }); // Serve files with custom cache headers app.UseStaticFiles(new StaticFileOptions { OnPrepareResponse = ctx => { ctx.Context.Response.Headers.Append("Cache-Control", "public,max-age=600"); } }); app.Run(); ``` ## Response Caching and Output Caching Response Caching provides HTTP-level caching based on cache headers, while Output Caching offers more flexible server-side caching with tag-based invalidation and policy configuration. ```csharp var builder = WebApplication.CreateBuilder(args); // Modern approach: Output Caching builder.Services.AddOutputCache(options => { // Base policy for all /js paths options.AddBasePolicy(builder => builder .With(c => c.HttpContext.Request.Path.StartsWithSegments("/js")) .Expire(TimeSpan.FromDays(1))); // Named policies options.AddPolicy("NoCache", b => b.NoCache()); options.AddPolicy("Expire20", b => b.Expire(TimeSpan.FromSeconds(20))); // Vary by query parameter options.AddPolicy("VaryByQuery", b => b.SetVaryByQuery("id", "page")); }); var app = builder.Build(); app.UseOutputCache(); // Apply caching to endpoints app.MapGet("/cached", () => new { message = "This response is cached", time = DateTime.UtcNow }).CacheOutput(); app.MapGet("/nocache", () => new { message = "Never cached", time = DateTime.UtcNow }).CacheOutput("NoCache"); app.MapGet("/expire20", () => new { time = DateTime.UtcNow }).CacheOutput("Expire20"); // Tag-based caching and invalidation var blog = app.MapGroup("blog").CacheOutput(x => x.Tag("blog")); blog.MapGet("/", () => new { posts = new[] { "Post 1", "Post 2" } }); blog.MapGet("/{id}", (int id) => new { id, title = $"Post {id}" }); // Invalidate all cached responses with "blog" tag app.MapPost("/blog/purge", async (IOutputCacheStore cache) => { await cache.EvictByTagAsync("blog", default); return Results.Ok("Blog cache purged"); }); // Legacy approach: Response Caching builder.Services.AddResponseCaching(); app.UseResponseCaching(); app.MapGet("/response-cached", (HttpContext context) => { context.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue() { Public = true, MaxAge = TimeSpan.FromSeconds(10) }; context.Response.Headers.Vary = new string[] { "Accept-Encoding" }; return Results.Ok(new { time = DateTime.UtcNow }); }); app.Run(); ``` ## HTTP Logging Log HTTP request and response details including headers, body, and timing information. Supports custom interceptors for filtering sensitive data and configuring per-request logging behavior. ```csharp var builder = WebApplication.CreateBuilder(args); builder.Services.AddHttpLogging(logging => { // Configure which parts to log logging.LoggingFields = HttpLoggingFields.RequestPropertiesAndHeaders | HttpLoggingFields.ResponsePropertiesAndHeaders | HttpLoggingFields.RequestBody | HttpLoggingFields.ResponseBody; // Limit body logging size logging.RequestBodyLogLimit = 4096; logging.ResponseBodyLogLimit = 4096; // Redact sensitive headers logging.RequestHeaders.Add("Authorization"); logging.RequestHeaders.Add("Cookie"); }); // Add custom interceptor for advanced scenarios builder.Services.AddHttpLoggingInterceptor(); var app = builder.Build(); app.UseHttpLogging(); app.MapGet("/", () => "Hello World!"); app.MapPost("/data", (DataModel data) => Results.Ok(data)); app.Run(); // Custom interceptor public class SampleHttpLoggingInterceptor : IHttpLoggingInterceptor { public ValueTask OnRequestAsync(HttpLoggingInterceptorContext logContext) { // Don't log requests to /health if (logContext.HttpContext.Request.Path.StartsWithSegments("/health")) { logContext.LoggingFields = HttpLoggingFields.None; } return ValueTask.CompletedTask; } public ValueTask OnResponseAsync(HttpLoggingInterceptorContext logContext) { // Add custom parameter to logs logContext.AddParameter("ResponseTime", logContext.HttpContext.Items["ResponseTime"]?.ToString() ?? "0"); return ValueTask.CompletedTask; } } public record DataModel(string Name, string Value); ``` ## WebSockets Real-time, full-duplex communication over TCP with support for text and binary messages. Includes compression options and graceful connection lifecycle management. ```csharp var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.UseWebSockets(new WebSocketOptions { KeepAliveInterval = TimeSpan.FromMinutes(2) }); app.Use(async (context, next) => { if (context.Request.Path == "/ws") { if (context.WebSockets.IsWebSocketRequest) { var webSocket = await context.WebSockets.AcceptWebSocketAsync( new WebSocketAcceptContext() { DangerousEnableCompression = true }); await Echo(context, webSocket); return; } else { context.Response.StatusCode = StatusCodes.Status400BadRequest; } } await next(context); }); app.MapGet("/", () => Results.Text(@" ", "text/html")); app.Run(); async Task Echo(HttpContext context, WebSocket webSocket) { var buffer = new byte[1024 * 4]; var cancellationToken = context.RequestAborted; try { var result = await webSocket.ReceiveAsync(buffer.AsMemory(), cancellationToken); while (result.MessageType != WebSocketMessageType.Close) { // Echo the message back await webSocket.SendAsync( new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, cancellationToken); result = await webSocket.ReceiveAsync(buffer.AsMemory(), cancellationToken); } await webSocket.CloseAsync( result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, result.CloseStatusDescription, cancellationToken); } catch (OperationCanceledException) { // Connection closed by client } } ``` ## CORS (Cross-Origin Resource Sharing) Configure cross-origin resource sharing to allow browsers to make requests from different domains. Supports policy-based configuration with granular control over origins, methods, headers, and credentials. ```csharp var builder = WebApplication.CreateBuilder(args); builder.Services.AddCors(options => { // Named policy with specific origins options.AddPolicy("AllowSpecificOrigin", policy => { policy.WithOrigins("http://example.com", "https://app.example.com") .WithMethods("GET", "POST", "PUT", "DELETE") .WithHeaders("Content-Type", "Authorization") .AllowCredentials(); }); // Open policy (development only) options.AddPolicy("AllowAll", policy => { policy.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader(); }); // Default policy options.AddDefaultPolicy(policy => { policy.WithOrigins("http://localhost:3000") .AllowAnyMethod() .AllowAnyHeader(); }); }); var app = builder.Build(); // Apply CORS globally app.UseCors(); // Or apply per endpoint app.MapGet("/public", () => "Public data") .RequireCors("AllowAll"); app.MapGet("/api/data", () => new { data = "sensitive" }) .RequireCors("AllowSpecificOrigin"); // Or inline policy app.MapGet("/custom", () => "Custom CORS") .RequireCors(policy => policy.WithOrigins("http://custom.com")); app.Run(); ``` ## JWT Bearer Authentication Authenticate requests using JSON Web Tokens with automatic token validation, claims extraction, and integration with ASP.NET Core's authentication pipeline. Supports OpenID Connect discovery and custom token validation. ```csharp var builder = WebApplication.CreateBuilder(args); builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { // OpenID Connect discovery options.Authority = "https://your-identity-server.com"; options.Audience = "your-api-resource"; // Or manual configuration options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = "https://your-identity-server.com", ValidateAudience = true, ValidAudience = "your-api-resource", ValidateLifetime = true, ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes("your-256-bit-secret-key")) }; // Custom events options.Events = new JwtBearerEvents { OnAuthenticationFailed = context => { Console.WriteLine($"Auth failed: {context.Exception.Message}"); return Task.CompletedTask; }, OnTokenValidated = context => { Console.WriteLine($"Token validated for {context.Principal.Identity.Name}"); return Task.CompletedTask; } }; }); builder.Services.AddAuthorization(); var app = builder.Build(); app.UseAuthentication(); app.UseAuthorization(); app.MapGet("/", () => "Hello World!"); app.MapGet("/secure", (ClaimsPrincipal user) => new { message = "Authenticated!", user = user.Identity?.Name, claims = user.Claims.Select(c => new { c.Type, c.Value }) }) .RequireAuthorization(); app.MapGet("/admin", () => "Admin only") .RequireAuthorization(policy => policy.RequireClaim("role", "admin")); app.Run(); ``` ## Cookie Authentication Session-based authentication using encrypted cookies to maintain user identity across requests. Supports sliding expiration, custom cookie policies, and integration with ASP.NET Core Identity. ```csharp var builder = WebApplication.CreateBuilder(args); builder.Services .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.ExpireTimeSpan = TimeSpan.FromMinutes(20); options.SlidingExpiration = true; options.LoginPath = "/login"; options.LogoutPath = "/logout"; options.AccessDeniedPath = "/access-denied"; options.Cookie.Name = "MyAuthCookie"; options.Cookie.HttpOnly = true; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; options.Cookie.SameSite = SameSiteMode.Strict; // Custom sliding expiration logic options.Events = new CookieAuthenticationEvents() { OnCheckSlidingExpiration = context => { // Renew if 25% of time has elapsed context.ShouldRenew = context.ElapsedTime > (context.Options.ExpireTimeSpan / 4); return Task.CompletedTask; } }; }); builder.Services.AddAuthorization(); var app = builder.Build(); app.UseAuthentication(); app.UseAuthorization(); app.MapGet("/", (HttpContext context) => { if (!context.User.Identity?.IsAuthenticated ?? true) { return Results.Redirect("/login"); } return Results.Text($"Hello {context.User.Identity.Name}! Visits: { context.User.FindFirst("visit_count")?.Value ?? "0"}"); }); app.MapGet("/login", async (HttpContext context, string? returnUrl) => { // Simulate login form if (context.Request.Query.ContainsKey("username")) { var username = context.Request.Query["username"].ToString(); var claims = new List { new Claim(ClaimTypes.Name, username), new Claim("visit_count", "1") }; var claimsIdentity = new ClaimsIdentity( claims, CookieAuthenticationDefaults.AuthenticationScheme); var authProperties = new AuthenticationProperties { IsPersistent = true, ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(20) }; await context.SignInAsync( CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProperties); return Results.Redirect(returnUrl ?? "/"); } return Results.Text($@"
", "text/html"); }); app.MapGet("/logout", async (HttpContext context) => { await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); return Results.Redirect("/"); }); app.Run(); ``` ## OpenID Connect Authentication Authenticate users via external identity providers using OpenID Connect protocol. Supports code flow, token storage, user info endpoint claims, and custom event handlers for authentication lifecycle. ```csharp var builder = WebApplication.CreateBuilder(args); builder.Services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }) .AddCookie() .AddOpenIdConnect(options => { options.ClientId = "your-client-id"; options.ClientSecret = "your-client-secret"; options.Authority = "https://your-identity-provider.com/"; options.ResponseType = OpenIdConnectResponseType.Code; // Save tokens for later use options.SaveTokens = true; options.GetClaimsFromUserInfoEndpoint = true; // Request additional scopes options.Scope.Clear(); options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("email"); options.Scope.Add("api"); // Map claims options.ClaimActions.MapJsonKey("role", "role", "string"); options.TokenValidationParameters = new TokenValidationParameters { NameClaimType = "name", RoleClaimType = "role" }; // Custom paths options.AccessDeniedPath = "/access-denied"; options.CallbackPath = "/signin-oidc"; options.SignedOutCallbackPath = "/signout-callback-oidc"; // Event handlers options.Events = new OpenIdConnectEvents { OnAuthenticationFailed = context => { context.HandleResponse(); context.Response.StatusCode = 500; context.Response.ContentType = "text/plain"; return context.Response.WriteAsync( "An error occurred processing your authentication."); }, OnTokenValidated = context => { // Add custom claims var identity = (ClaimsIdentity)context.Principal.Identity; identity.AddClaim(new Claim("login_time", DateTime.UtcNow.ToString())); return Task.CompletedTask; } }; }); builder.Services.AddAuthorization(); var app = builder.Build(); app.UseAuthentication(); app.UseAuthorization(); app.MapGet("/", (ClaimsPrincipal user) => { if (!user.Identity?.IsAuthenticated ?? true) { return Results.Challenge(); } return Results.Text($"Hello {user.Identity.Name}!"); }); app.MapGet("/claims", (ClaimsPrincipal user) => user.Claims.Select(c => new { c.Type, c.Value })) .RequireAuthorization(); app.MapGet("/token", async (HttpContext context) => { var accessToken = await context.GetTokenAsync("access_token"); var idToken = await context.GetTokenAsync("id_token"); var refreshToken = await context.GetTokenAsync("refresh_token"); return Results.Ok(new { accessToken, idToken, refreshToken }); }) .RequireAuthorization(); app.MapGet("/logout", () => Results.SignOut( new AuthenticationProperties { RedirectUri = "/" }, CookieAuthenticationDefaults.AuthenticationScheme, OpenIdConnectDefaults.AuthenticationScheme)); app.Run(); ``` ## Custom Authorization Policies Define reusable authorization requirements and policies with custom handlers. Supports policy-based authorization, resource-based authorization, and dynamic policy providers. ```csharp var builder = WebApplication.CreateBuilder(args); builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(); builder.Services.AddAuthorization(options => { // Simple claim-based policies options.AddPolicy("RequireAdminRole", policy => policy.RequireRole("Admin")); options.AddPolicy("RequireEmail", policy => policy.RequireClaim(ClaimTypes.Email)); // Complex policy with multiple requirements options.AddPolicy("AtLeast21", policy => policy.Requirements.Add(new MinimumAgeRequirement(21))); // Combine requirements options.AddPolicy("AdminOver21", policy => { policy.RequireRole("Admin"); policy.Requirements.Add(new MinimumAgeRequirement(21)); }); }); // Register custom requirement handler builder.Services.AddSingleton(); // Or replace default policy provider for dynamic policies builder.Services.AddSingleton(); var app = builder.Build(); app.UseAuthentication(); app.UseAuthorization(); app.MapGet("/admin", () => "Admin only") .RequireAuthorization("RequireAdminRole"); app.MapGet("/adult", () => "21+ only") .RequireAuthorization("AtLeast21"); app.Run(); // Custom requirement public class MinimumAgeRequirement : IAuthorizationRequirement { public int MinimumAge { get; } public MinimumAgeRequirement(int minimumAge) { MinimumAge = minimumAge; } } // Custom handler public class MinimumAgeAuthorizationHandler : AuthorizationHandler { protected override Task HandleRequirementAsync( AuthorizationHandlerContext context, MinimumAgeRequirement requirement) { var dateOfBirthClaim = context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth); if (dateOfBirthClaim == null) { return Task.CompletedTask; } if (!DateTime.TryParse(dateOfBirthClaim.Value, out var dateOfBirth)) { return Task.CompletedTask; } var age = DateTime.Today.Year - dateOfBirth.Year; if (dateOfBirth.Date > DateTime.Today.AddYears(-age)) { age--; } if (age >= requirement.MinimumAge) { context.Succeed(requirement); } return Task.CompletedTask; } } // Dynamic policy provider public class MinimumAgePolicyProvider : IAuthorizationPolicyProvider { private DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; } public MinimumAgePolicyProvider(IOptions options) { FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options); } public Task GetDefaultPolicyAsync() => FallbackPolicyProvider.GetDefaultPolicyAsync(); public Task GetFallbackPolicyAsync() => FallbackPolicyProvider.GetFallbackPolicyAsync(); public Task GetPolicyAsync(string policyName) { // Dynamic policy: "MinimumAge21", "MinimumAge18", etc. if (policyName.StartsWith("MinimumAge", StringComparison.OrdinalIgnoreCase) && int.TryParse(policyName.Substring("MinimumAge".Length), out var age)) { var policy = new AuthorizationPolicyBuilder(); policy.AddRequirements(new MinimumAgeRequirement(age)); return Task.FromResult(policy.Build()); } return FallbackPolicyProvider.GetPolicyAsync(policyName); } } ``` ## SignalR Real-Time Communication Bidirectional communication between server and client using WebSockets, Server-Sent Events, or Long Polling. Supports strongly-typed hubs, streaming, groups, and scale-out with Redis backplane. ```csharp // Program.cs var builder = WebApplication.CreateBuilder(args); builder.Services.AddSignalR() .AddMessagePackProtocol() .AddJsonProtocol(options => { options.PayloadSerializerOptions.PropertyNamingPolicy = null; }); // For scale-out across multiple servers // builder.Services.AddSignalR().AddStackExchangeRedis("localhost:6379"); var app = builder.Build(); app.MapHub("/chat"); app.MapHub("/streaming"); app.Run(); // Hubs/ChatHub.cs public class ChatHub : Hub { public override async Task OnConnectedAsync() { var name = Context.GetHttpContext()?.Request.Query["name"].ToString() ?? "Anonymous"; Context.Items["Name"] = name; await Clients.All.SendAsync("UserJoined", name); await base.OnConnectedAsync(); } public override async Task OnDisconnectedAsync(Exception? exception) { var name = Context.Items["Name"]?.ToString() ?? "Anonymous"; await Clients.All.SendAsync("UserLeft", name); await base.OnDisconnectedAsync(exception); } public async Task SendMessage(string message) { var name = Context.Items["Name"]?.ToString() ?? "Anonymous"; await Clients.All.SendAsync("ReceiveMessage", name, message); } public async Task SendToUser(string connectionId, string message) { var name = Context.Items["Name"]?.ToString() ?? "Anonymous"; await Clients.Client(connectionId).SendAsync("ReceiveMessage", name, message); } public async Task JoinGroup(string groupName) { await Groups.AddToGroupAsync(Context.ConnectionId, groupName); var name = Context.Items["Name"]?.ToString() ?? "Anonymous"; await Clients.Group(groupName).SendAsync("UserJoinedGroup", name, groupName); } public async Task LeaveGroup(string groupName) { var name = Context.Items["Name"]?.ToString() ?? "Anonymous"; await Clients.Group(groupName).SendAsync("UserLeftGroup", name, groupName); await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName); } public async Task SendToGroup(string groupName, string message) { var name = Context.Items["Name"]?.ToString() ?? "Anonymous"; await Clients.Group(groupName).SendAsync("ReceiveMessage", name, message); } public async Task SendToOthers(string message) { var name = Context.Items["Name"]?.ToString() ?? "Anonymous"; await Clients.Others.SendAsync("ReceiveMessage", name, message); } } // Hubs/StreamingHub.cs public class StreamingHub : Hub { // Server-to-client streaming with IAsyncEnumerable public async IAsyncEnumerable Counter( int count, int delay, [EnumeratorCancellation] CancellationToken cancellationToken) { for (var i = 0; i < count; i++) { cancellationToken.ThrowIfCancellationRequested(); yield return i; await Task.Delay(delay, cancellationToken); } } // Server-to-client streaming with ChannelReader public ChannelReader ChannelStream(int count, int delay) { var channel = Channel.CreateUnbounded(); _ = Task.Run(async () => { for (var i = 0; i < count; i++) { await channel.Writer.WriteAsync($"Message {i}"); await Task.Delay(delay); } channel.Writer.Complete(); }); return channel.Reader; } // Client-to-server streaming public async Task UploadStream(ChannelReader stream) { while (await stream.WaitToReadAsync()) { while (stream.TryRead(out var item)) { Console.WriteLine($"Received: {item}"); await Clients.Caller.SendAsync("ReceiveProgress", $"Processed: {item}"); } } } } // Client-side usage (JavaScript) /* const connection = new signalR.HubConnectionBuilder() .withUrl("/chat?name=John") .withAutomaticReconnect() .build(); connection.on("ReceiveMessage", (name, message) => { console.log(`${name}: ${message}`); }); await connection.start(); await connection.invoke("SendMessage", "Hello World!"); // Streaming connection.stream("Counter", 10, 100).subscribe({ next: (item) => console.log(item), complete: () => console.log("Done"), error: (err) => console.error(err) }); */ ``` ## gRPC Services High-performance RPC framework with code-first service definitions using Protocol Buffers. Supports unary, server streaming, client streaming, and bidirectional streaming with strong typing. ```csharp // Program.cs var builder = WebApplication.CreateBuilder(args); builder.Services.AddGrpc(options => { options.MaxReceiveMessageSize = 16 * 1024 * 1024; // 16 MB options.EnableDetailedErrors = builder.Environment.IsDevelopment(); }); // Add gRPC JSON transcoding for HTTP/REST API builder.Services.AddGrpcJsonTranscoding(); var app = builder.Build(); app.MapGrpcService(); app.MapGrpcService(); app.MapGet("/", () => "gRPC endpoints: /greet.Greeter, /counter.Counter"); app.Run(); // Services/GreeterService.cs public class GreeterService : Greeter.GreeterBase { private readonly ILogger _logger; public GreeterService(ILogger logger) { _logger = logger; } // Unary RPC public override Task SayHello(HelloRequest request, ServerCallContext context) { _logger.LogInformation("Saying hello to {Name}", request.Name); return Task.FromResult(new HelloReply { Message = $"Hello {request.Name}" }); } // Server streaming RPC public override async Task SayHellos( HelloRequest request, IServerStreamWriter responseStream, ServerCallContext context) { for (var i = 0; i < 5; i++) { if (context.CancellationToken.IsCancellationRequested) { break; } await responseStream.WriteAsync(new HelloReply { Message = $"Hello {request.Name} #{i}" }); await Task.Delay(1000, context.CancellationToken); } } } // Services/CounterService.cs public class CounterService : Counter.CounterBase { // Client streaming RPC public override async Task AccumulateCount( IAsyncStreamReader requestStream, ServerCallContext context) { var sum = 0; await foreach (var request in requestStream.ReadAllAsync()) { sum += request.Count; } return new CountResult { Sum = sum }; } // Bidirectional streaming RPC public override async Task Echo( IAsyncStreamReader requestStream, IServerStreamWriter responseStream, ServerCallContext context) { await foreach (var request in requestStream.ReadAllAsync()) { await responseStream.WriteAsync(new EchoReply { Message = $"Echo: {request.Message}" }); } } } // Protos/greet.proto /* syntax = "proto3"; option csharp_namespace = "GrpcService"; package greet; service Greeter { rpc SayHello (HelloRequest) returns (HelloReply); rpc SayHellos (HelloRequest) returns (stream HelloReply); } message HelloRequest { string name = 1; } message HelloReply { string message = 1; } */ // Client usage /* var channel = GrpcChannel.ForAddress("https://localhost:5001"); var client = new Greeter.GreeterClient(channel); // Unary call var reply = await client.SayHelloAsync(new HelloRequest { Name = "World" }); Console.WriteLine(reply.Message); // Server streaming var call = client.SayHellos(new HelloRequest { Name = "World" }); await foreach (var response in call.ResponseStream.ReadAllAsync()) { Console.WriteLine(response.Message); } */ ``` ## Rate Limiting Control request rates using various algorithms including fixed window, sliding window, token bucket, and concurrency limiters. Supports per-endpoint policies and partition keys for user/IP-based limiting. ```csharp var builder = WebApplication.CreateBuilder(args); builder.Services.AddRateLimiter(options => { // Fixed window limiter options.AddFixedWindowLimiter("fixed", options => { options.PermitLimit = 10; options.Window = TimeSpan.FromMinutes(1); options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; options.QueueLimit = 2; }); // Sliding window limiter options.AddSlidingWindowLimiter("sliding", options => { options.PermitLimit = 100; options.Window = TimeSpan.FromMinutes(1); options.SegmentsPerWindow = 6; // 10-second segments options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; options.QueueLimit = 10; }); // Token bucket limiter options.AddTokenBucketLimiter("token", options => { options.TokenLimit = 100; options.QueueLimit = 10; options.ReplenishmentPeriod = TimeSpan.FromSeconds(10); options.TokensPerPeriod = 20; options.AutoReplenishment = true; }); // Concurrency limiter options.AddConcurrencyLimiter("concurrent", options => { options.PermitLimit = 10; options.QueueProcessingOrder = QueueProcessingOrder.NewestFirst; options.QueueLimit = 5; }); // Partition by user ID options.AddPolicy("perUser", context => { var userId = context.User.FindFirst("sub")?.Value ?? "anonymous"; return RateLimitPartition.GetTokenBucketLimiter(userId, key => new TokenBucketRateLimiterOptions { TokenLimit = 100, TokensPerPeriod = 20, ReplenishmentPeriod = TimeSpan.FromMinutes(1) }); }); // Global limiter options.GlobalLimiter = PartitionedRateLimiter.Create(context => { return RateLimitPartition.GetConcurrencyLimiter("global", key => new ConcurrencyLimiterOptions { PermitLimit = 1000, QueueLimit = 100 }); }); // Rejection response options.OnRejected = async (context, cancellationToken) => { context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests; if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter)) { await context.HttpContext.Response.WriteAsync( $"Too many requests. Please try again after {retryAfter.TotalSeconds} seconds.", cancellationToken); } else { await context.HttpContext.Response.WriteAsync( "Too many requests. Please try again later.", cancellationToken); } }; }); var app = builder.Build(); app.UseRateLimiter(); app.MapGet("/", () => "Hello World!"); app.MapGet("/limited", () => new { time = DateTime.UtcNow }) .RequireRateLimiting("fixed"); app.MapGet("/api/data", () => new { data = "sensitive" }) .RequireRateLimiting("perUser"); // Disable rate limiting for specific endpoint app.MapGet("/health", () => "Healthy") .DisableRateLimiting(); app.Run(); ``` ## Data Protection Cryptographic API for protecting and unprotecting data with automatic key management, rotation, and encryption at rest. Supports persistence to various stores and key protection using DPAPI, Azure Key Vault, or X.509 certificates. ```csharp // Basic usage without DI var keysFolder = Path.Combine(Directory.GetCurrentDirectory(), "temp-keys"); var dataProtectionProvider = DataProtectionProvider.Create( new DirectoryInfo(keysFolder), configuration => { configuration.SetApplicationName("MyApp"); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { configuration.ProtectKeysWithDpapi(); } }); var protector = dataProtectionProvider.CreateProtector("MyApp.Purpose"); // Protect data var protectedPayload = protector.Protect("Hello World!"); Console.WriteLine($"Protected: {protectedPayload}"); // Unprotect data var unprotectedPayload = protector.Unprotect(protectedPayload); Console.WriteLine($"Unprotected: {unprotectedPayload}"); // Time-limited protection var timeLimitedProtector = protector.ToTimeLimitedDataProtector(); var protectedWithExpiry = timeLimitedProtector.Protect( "Sensitive data", TimeSpan.FromMinutes(5)); // Using DI in web application var builder = WebApplication.CreateBuilder(args); builder.Services.AddDataProtection() .SetApplicationName("MyApp") .PersistKeysToFileSystem(new DirectoryInfo(keysFolder)) .SetDefaultKeyLifetime(TimeSpan.FromDays(90)); // Redis persistence var redis = ConnectionMultiplexer.Connect("localhost:6379"); builder.Services.AddDataProtection() .PersistKeysToStackExchangeRedis(redis, "DataProtection-Keys"); // Azure Blob Storage builder.Services.AddDataProtection() .PersistKeysToAzureBlobStorage(new Uri("https://...")); // Entity Framework Core builder.Services.AddDataProtection() .PersistKeysToDbContext(); // Key management var app = builder.Build(); // Use data protection in services app.MapGet("/protect", (IDataProtectionProvider provider, string data) => { var protector = provider.CreateProtector("MyApp.API"); return protector.Protect(data); }); app.MapGet("/unprotect", (IDataProtectionProvider provider, string protectedData) => { try { var protector = provider.CreateProtector("MyApp.API"); return protector.Unprotect(protectedData); } catch (CryptographicException) { return Results.BadRequest("Invalid protected payload"); } }); // Key management operations var serviceProvider = app.Services; var keyManager = serviceProvider.GetRequiredService(); // List all keys var allKeys = keyManager.GetAllKeys(); foreach (var key in allKeys) { Console.WriteLine($"Key {key.KeyId}: " + $"Created={key.CreationDate:u}, " + $"Expires={key.ExpirationDate:u}, " + $"IsRevoked={key.IsRevoked}"); } // Create new key var newKey = keyManager.CreateNewKey( activationDate: DateTimeOffset.Now, expirationDate: DateTimeOffset.Now.AddMonths(3)); // Revoke all keys keyManager.RevokeAllKeys( revocationDate: DateTimeOffset.Now, reason: "Security incident"); app.Run(); ``` ## HTTP Client Factory Manage HttpClient instances with proper lifetime handling, named and typed clients, middleware-style handlers, and Polly integration for resilience patterns. ```csharp var builder = WebApplication.CreateBuilder(args); // Basic named client builder.Services.AddHttpClient("api", client => { client.BaseAddress = new Uri("https://api.example.com/"); client.DefaultRequestHeaders.Add("Accept", "application/json"); client.Timeout = TimeSpan.FromSeconds(30); }); // Named client with handlers builder.Services.AddHttpClient("github", client => { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "MyApp"); }) .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { AllowAutoRedirect = false, UseCookies = false }) .AddHttpMessageHandler(() => new LoggingHandler()); // Typed client builder.Services.AddHttpClient() .ConfigureHttpClient(client => { client.BaseAddress = new Uri("https://api.github.com/"); }); // Header propagation builder.Services.AddHeaderPropagation(options => { options.Headers.Add("X-Correlation-Id"); options.Headers.Add("Authorization"); }); builder.Services.AddHttpClient("downstream") .AddHeaderPropagation(); // Polly integration for resilience builder.Services.AddHttpClient("resilient") .AddTransientHttpErrorPolicy(policy => policy.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)))) .AddTransientHttpErrorPolicy(policy => policy.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30))); var app = builder.Build(); app.UseHeaderPropagation(); // Use named client app.MapGet("/fetch", async (IHttpClientFactory clientFactory) => { var client = clientFactory.CreateClient("api"); var response = await client.GetStringAsync("data"); return Results.Ok(response); }); // Use typed client app.MapGet("/github/{user}", async (string user, GitHubService github) => { var profile = await github.GetUserAsync(user); return Results.Ok(profile); }); app.Run(); // Typed client service public class GitHubService { private readonly HttpClient _client; public GitHubService(HttpClient client) { _client = client; } public async Task GetUserAsync(string username) { return await _client.GetFromJsonAsync($"users/{username}"); } } public record GitHubUser(string Login, string Name, string Bio, int PublicRepos); // Custom message handler public class LoggingHandler : DelegatingHandler { protected override async Task SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { Console.WriteLine($"Request: {request.Method} {request.RequestUri}"); var response = await base.SendAsync(request, cancellationToken); Console.WriteLine($"Response: {response.StatusCode}"); return response; } } ``` ## Health Checks Expose application health status for monitoring systems, load balancers, and orchestrators. Supports custom health checks, liveness/readiness probes, and JSON response formatting. ```csharp var builder = WebApplication.CreateBuilder(args); builder.Services.AddHealthChecks() // Built-in checks .AddCheck("self", () => HealthCheckResult.Healthy()) // Database check .AddSqlServer( connectionString: builder.Configuration.GetConnectionString("DefaultConnection"), name: "sql", failureStatus: HealthStatus.Degraded, tags: new[] { "db", "sql", "ready" }) // Redis check .AddRedis( redisConnectionString: "localhost:6379", name: "redis", tags: new[] { "cache", "redis", "ready" }) // HTTP check for dependencies .AddUrlGroup( uri: new Uri("https://api.example.com/health"), name: "external-api", tags: new[] { "external", "ready" }) // Custom check .AddCheck("custom", tags: new[] { "ready" }) // Memory check .AddCheck("memory", () => { var allocated = GC.GetTotalMemory(forceFullCollection: false); var threshold = 1024L * 1024L * 1024L; // 1 GB return allocated < threshold ? HealthCheckResult.Healthy($"Memory usage: {allocated / 1024 / 1024} MB") : HealthCheckResult.Unhealthy($"Memory usage too high: {allocated / 1024 / 1024} MB"); }); var app = builder.Build(); // Basic health endpoint app.MapHealthChecks("/health"); // Liveness probe (excludes all checks, just returns 200) app.MapHealthChecks("/health/live", new HealthCheckOptions { Predicate = _ => false }); // Readiness probe (includes all checks with 'ready' tag) app.MapHealthChecks("/health/ready", new HealthCheckOptions { Predicate = check => check.Tags.Contains("ready") }); // Detailed JSON response app.MapHealthChecks("/health/detail", new HealthCheckOptions { ResponseWriter = async (context, report) => { context.Response.ContentType = "application/json"; var result = JsonSerializer.Serialize(new { status = report.Status.ToString(), checks = report.Entries.Select(e => new { name = e.Key, status = e.Value.Status.ToString(), description = e.Value.Description, duration = e.Value.Duration.TotalMilliseconds, exception = e.Value.Exception?.Message, data = e.Value.Data }), totalDuration = report.TotalDuration.TotalMilliseconds }); await context.Response.WriteAsync(result); } }); // UI endpoint (requires AspNetCore.HealthChecks.UI package) // app.MapHealthChecksUI(); app.Run(); // Custom health check public class CustomHealthCheck : IHealthCheck { private readonly ILogger _logger; public CustomHealthCheck(ILogger logger) { _logger = logger; } public async Task CheckHealthAsync( HealthCheckContext context, CancellationToken cancellationToken = default) { try { // Perform custom health check logic await Task.Delay(100, cancellationToken); var isHealthy = CheckCustomLogic(); if (isHealthy) { return HealthCheckResult.Healthy("Custom check passed", new Dictionary { { "timestamp", DateTime.UtcNow }, { "custom_metric", 42 } }); } return HealthCheckResult.Degraded("Custom check degraded"); } catch (Exception ex) { _logger.LogError(ex, "Health check failed"); return HealthCheckResult.Unhealthy("Custom check failed", ex); } } private bool CheckCustomLogic() { return true; } } ``` ## Antiforgery (CSRF Protection) Protect against Cross-Site Request Forgery attacks using anti-forgery tokens. Automatically validates tokens for state-changing operations and integrates with forms and AJAX requests. ```csharp var builder = WebApplication.CreateBuilder(args); builder.Services.AddAntiforgery(options => { options.HeaderName = "X-CSRF-TOKEN"; options.Cookie.Name = ".AspNetCore.Antiforgery"; options.Cookie.SameSite = SameSiteMode.Strict; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; }); var app = builder.Build(); app.UseAntiforgery(); // Serve form with anti-forgery token app.MapGet("/form", (HttpContext context, IAntiforgery antiforgery) => { var tokens = antiforgery.GetAndStoreTokens(context); var html = $@"

Submit Todo

AJAX Submit

"; return Results.Content(html, "text/html"); }); // Validate anti-forgery token on POST app.MapPost("/todo", [ValidateAntiForgeryToken] ([FromForm] string? title) => { if (string.IsNullOrWhiteSpace(title)) { return Results.BadRequest("Title is required"); } return Results.Ok(new { id = Guid.NewGuid(), title, created = DateTime.UtcNow }); }); // JSON endpoint with header-based token app.MapPost("/api/todo", [ValidateAntiForgeryToken] ([FromBody] TodoRequest todo) => { return Results.Ok(new { id = Guid.NewGuid(), todo.Title, created = DateTime.UtcNow }); }); // Get token for client-side AJAX app.MapGet("/antiforgery/token", (HttpContext context, IAntiforgery antiforgery) => { var tokens = antiforgery.GetAndStoreTokens(context); return Results.Ok(new { token = tokens.RequestToken }); }); app.Run(); public record TodoRequest(string Title); ``` ## Identity API Endpoints Complete authentication system with user registration, login, token refresh, email confirmation, and password reset. Integrates with Entity Framework Core and supports JWT bearer tokens. ```csharp var builder = WebApplication.CreateBuilder(args); var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); builder.Services.AddDbContext(options => options.UseSqlite(connectionString)); builder.Services .AddIdentityApiEndpoints(options => { options.SignIn.RequireConfirmedAccount = false; options.Password.RequireDigit = true; options.Password.RequiredLength = 8; options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5); options.Lockout.MaxFailedAccessAttempts = 5; }) .AddEntityFrameworkStores(); builder.Services.AddAuthorization(); var app = builder.Build(); // Ensure database is created using (var scope = app.Services.CreateScope()) { var db = scope.ServiceProvider.GetRequiredService(); db.Database.EnsureCreated(); } app.MapGet("/", () => "Hello World!"); // Protected endpoint requiring authentication app.MapGet("/protected", (ClaimsPrincipal user) => new { message = "You are authenticated!", userId = user.FindFirst(ClaimTypes.NameIdentifier)?.Value, email = user.FindFirst(ClaimTypes.Email)?.Value }) .RequireAuthorization(); // Map all Identity API endpoints: // POST /register - Register new user // POST /login - Login with username/password // POST /refresh - Refresh access token // GET /confirmEmail - Confirm email address // POST /resendConfirmationEmail - Resend confirmation email // POST /forgotPassword - Request password reset // POST /resetPassword - Reset password with token // POST /manage/2fa - Two-factor authentication endpoints // POST /manage/info - Get/update user information app.MapGroup("/identity").MapIdentityApi(); // Custom logout endpoint app.MapPost("/identity/logout", async (SignInManager signInManager) => { await signInManager.SignOutAsync(); return Results.Ok(); }) .RequireAuthorization(); app.Run(); public class ApplicationDbContext : IdentityDbContext { public ApplicationDbContext(DbContextOptions options) : base(options) { } } // Client usage examples: /* // Register POST /identity/register { "email": "user@example.com", "password": "Password123!" } // Login POST /identity/login { "email": "user@example.com", "password": "Password123!" } Response: { "tokenType": "Bearer", "accessToken": "eyJhbGc...", "expiresIn": 3600, "refreshToken": "CfDJ8..." } // Use access token GET /protected Authorization: Bearer eyJhbGc... // Refresh token POST /identity/refresh { "refreshToken": "CfDJ8..." } */ ``` ## OpenAPI Document Generation Built-in OpenAPI schema generation for minimal APIs and controllers with support for customization through transformers. Automatically generates OpenAPI v3.0 documents with schema definitions, operation metadata, and authentication requirements. ```csharp var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); // Add OpenAPI document generation builder.Services.AddOpenApi(); // Named OpenAPI documents for versioning builder.Services.AddOpenApi("v1", options => { // Add custom headers to all operations options.AddHeader("X-Version", "1.0"); // Add security scheme transformer options.AddDocumentTransformer(); }); builder.Services.AddOpenApi("v2", options => { // Add schema transformers options.AddSchemaTransformer(); // Add operation transformers options.AddOperationTransformer(); // Inline document transformer options.AddDocumentTransformer((document, context, token) => { document.Info.Title = "My API v2"; document.Info.Description = "Enhanced API with additional features"; document.Info.License = new OpenApiLicense { Name = "MIT" }; return Task.CompletedTask; }); // Filter endpoints to include options.ShouldInclude = (endpoint) => endpoint.Metadata.OfType() .Any(m => m.HttpMethods.Contains("GET")); }); var app = builder.Build(); // Map OpenAPI endpoints (JSON format) app.MapOpenApi(); // Map OpenAPI with custom path and YAML format app.MapOpenApi("/openapi/{documentName}.yaml"); // Swagger UI for development if (app.Environment.IsDevelopment()) { app.MapSwaggerUi(); } // Define API endpoints app.MapGet("/products", () => new[] { new Product(1, "Widget", 19.99m), new Product(2, "Gadget", 29.99m) }) .WithName("GetProducts") .WithSummary("Retrieve all products") .WithDescription("Returns a list of all available products in the catalog") .WithTags("Products") .Produces(StatusCodes.Status200OK) .WithOpenApi(); app.MapGet("/products/{id}", (int id) => { if (id <= 0) return Results.BadRequest("Invalid product ID"); return Results.Ok(new Product(id, "Widget", 19.99m)); }) .WithName("GetProduct") .WithSummary("Get product by ID") .WithTags("Products") .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) .WithOpenApi(operation => { // Customize OpenAPI operation operation.Summary = "Retrieve a specific product"; operation.Parameters[0].Description = "The unique product identifier"; return operation; }); app.MapPost("/products", (Product product) => { return Results.Created($"/products/{product.Id}", product); }) .WithName("CreateProduct") .WithTags("Products") .Produces(StatusCodes.Status201Created) .WithOpenApi(); // Protected endpoint with authentication app.MapGet("/admin/products", () => new { message = "Admin access" }) .RequireAuthorization() .WithTags("Admin") .WithOpenApi(); app.MapControllers(); app.Run(); record Product(int Id, string Name, decimal Price); // Custom document transformer public class BearerSecuritySchemeTransformer : IOpenApiDocumentTransformer { public Task TransformAsync( OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken) { document.Components ??= new OpenApiComponents(); document.Components.SecuritySchemes["Bearer"] = new OpenApiSecurityScheme { Type = SecuritySchemeType.Http, Scheme = "bearer", BearerFormat = "JWT", Description = "Enter JWT Bearer token" }; document.SecurityRequirements.Add(new OpenApiSecurityRequirement { [new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } }] = Array.Empty() }); return Task.CompletedTask; } } // Custom schema transformer public class AddExternalDocsTransformer : IOpenApiSchemaTransformer, IOpenApiOperationTransformer { public Task TransformAsync( OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken) { schema.ExternalDocs = new OpenApiExternalDocs { Description = "Learn more", Url = new Uri("https://docs.example.com/schemas") }; return Task.CompletedTask; } public Task TransformAsync( OpenApiOperation operation, OpenApiOperationTransformerContext context, CancellationToken cancellationToken) { operation.ExternalDocs = new OpenApiExternalDocs { Description = "API Documentation", Url = new Uri("https://docs.example.com/api") }; return Task.CompletedTask; } } // Access OpenAPI documents: // JSON: GET /openapi/v1.json // YAML: GET /openapi/v1.yaml // Swagger UI: GET /swagger ``` ## Summary ASP.NET Core provides a comprehensive, modular framework for building modern web applications and services. The platform excels in developer productivity through minimal ceremony APIs, strongly-typed programming models, and extensive middleware pipeline for cross-cutting concerns. Built-in dependency injection, configuration management, and logging create a consistent development experience across all application types. The framework's flexibility enables multiple architectural patterns: minimal APIs for lightweight microservices, MVC/Razor Pages for traditional web applications, Blazor for SPA experiences, SignalR for real-time features, and gRPC for high-performance service communication. Security features including authentication providers, authorization policies, data protection, and antiforgery protection integrate seamlessly. Performance-oriented features like output caching, rate limiting, response compression, and HTTP/2+ support ensure applications scale efficiently. Combined with cloud-native capabilities including health checks, distributed caching, and containerization support, ASP.NET Core delivers a complete platform for enterprise application development.