Endpoint Mapping
Arc.Core provides extension methods for manually mapping HTTP endpoints to your application. This gives you full control over route patterns, handlers, and endpoint metadata.
Overview
Section titled “Overview”While Arc automatically maps commands and queries to endpoints, you may need to create custom endpoints for specific scenarios such as:
- Health checks
- Webhooks
- Custom API endpoints
- Static file serving
- Proxy endpoints
The MapGet() and MapPost() extension methods allow you to define these endpoints fluently.
Basic Usage
Section titled “Basic Usage”MapGet
Section titled “MapGet”Map a GET endpoint to handle HTTP GET requests:
using Cratis.Arc;using Cratis.Arc.Http;
var builder = ArcApplication.CreateBuilder(args);var app = builder.Build();
app.MapGet("/health", async context =>{ await context.Write("OK");});
await app.RunAsync();MapPost
Section titled “MapPost”Map a POST endpoint to handle HTTP POST requests:
app.MapPost("/webhook", async context =>{ var data = await context.ReadBodyAsJson(typeof(object)); // Process webhook data context.SetStatusCode(200);});Method Signatures
Section titled “Method Signatures”Both methods follow the same pattern:
ArcApplication MapGet( string pattern, Func<IHttpRequestContext, Task> handler, EndpointMetadata? metadata = null)
ArcApplication MapPost( string pattern, Func<IHttpRequestContext, Task> handler, EndpointMetadata? metadata = null)Parameters
Section titled “Parameters”- pattern - The route pattern (e.g.,
/api/users,/health) - handler - An async function that processes the HTTP request
- metadata - Optional endpoint metadata for documentation and configuration
Working with IHttpRequestContext
Section titled “Working with IHttpRequestContext”The handler function receives an IHttpRequestContext that provides access to the request and response:
Reading Request Data
Section titled “Reading Request Data”app.MapPost("/api/data", async context =>{ // Query parameters var id = context.Query["id"];
// Headers var authToken = context.Headers["Authorization"];
// Cookies var sessionId = context.Cookies["SessionId"];
// Request body as JSON var data = await context.ReadBodyAsJson(typeof(MyData));
// Path and method var path = context.Path; var method = context.Method;});Writing Response Data
Section titled “Writing Response Data”app.MapGet("/api/users", async context =>{ var users = new[] { new { Id = 1, Name = "Alice" } };
// Set status code context.SetStatusCode(200);
// Set content type context.ContentType = "application/json";
// Write JSON response await context.WriteResponseAsJson(users, users.GetType());
// Or write plain text // await context.Write("Hello World");});Setting Response Headers
Section titled “Setting Response Headers”app.MapGet("/api/data", async context =>{ context.SetResponseHeader("Cache-Control", "no-cache"); context.SetResponseHeader("X-Custom-Header", "value");
await context.Write("Data");});Endpoint Metadata
Section titled “Endpoint Metadata”Add metadata to provide documentation and configure endpoint behavior:
using Cratis.Arc.Http;
app.MapGet("/api/users", async context =>{ // Handler implementation},new EndpointMetadata( Name: "GetAllUsers", Summary: "Retrieves a list of all users", Tags: ["Users"], AllowAnonymous: false));Metadata Properties
Section titled “Metadata Properties”- Name - Unique identifier for the endpoint (used as operationId in OpenAPI)
- Summary - Human-readable description of what the endpoint does
- Tags - Categories for grouping related endpoints
- AllowAnonymous - Whether authentication is required (
false= authentication required)
Fluent API and Method Chaining
Section titled “Fluent API and Method Chaining”The extension methods return the ArcApplication instance, enabling fluent chaining:
app.MapGet("/health", async context => { await context.Write("OK"); }, new EndpointMetadata(Name: "Health", AllowAnonymous: true)) .MapGet("/version", async context => { await context.Write("1.0.0"); }, new EndpointMetadata(Name: "Version", AllowAnonymous: true)) .MapPost("/api/events", async context => { var evt = await context.ReadBodyAsJson(typeof(object)); context.SetStatusCode(202); }, new EndpointMetadata(Name: "ReceiveEvent", AllowAnonymous: false));
await app.RunAsync();Complete Examples
Section titled “Complete Examples”Health Check Endpoint
Section titled “Health Check Endpoint”app.MapGet("/health", async context =>{ var health = new { Status = "Healthy", Timestamp = DateTime.UtcNow, Version = "1.0.0" };
context.ContentType = "application/json"; await context.WriteResponseAsJson(health, health.GetType());},new EndpointMetadata( Name: "HealthCheck", Summary: "Returns the health status of the application", Tags: ["System"], AllowAnonymous: true));Webhook Handler
Section titled “Webhook Handler”app.MapPost("/webhooks/github", async context =>{ // Verify webhook signature var signature = context.Headers["X-Hub-Signature-256"];
// Read webhook payload var payload = await context.ReadBodyAsJson(typeof(object));
// Process webhook // ... your logic here ...
context.SetStatusCode(200); await context.Write("Webhook received");},new EndpointMetadata( Name: "GitHubWebhook", Summary: "Receives GitHub webhook notifications", Tags: ["Webhooks"], AllowAnonymous: true));RESTful API Endpoint
Section titled “RESTful API Endpoint”public record Product(int Id, string Name, decimal Price);
app.MapGet("/api/products", async context =>{ var products = new[] { new Product(1, "Product A", 29.99m), new Product(2, "Product B", 39.99m) };
await context.WriteResponseAsJson(products, products.GetType());},new EndpointMetadata( Name: "ListProducts", Summary: "Get all available products", Tags: ["Products"], AllowAnonymous: true));
app.MapPost("/api/products", async context =>{ var product = await context.ReadBodyAsJson(typeof(Product)) as Product;
if (product == null) { context.SetStatusCode(400); await context.Write("Invalid product data"); return; }
// Save product logic here
context.SetStatusCode(201); context.SetResponseHeader("Location", $"/api/products/{product.Id}"); await context.WriteResponseAsJson(product, typeof(Product));},new EndpointMetadata( Name: "CreateProduct", Summary: "Create a new product", Tags: ["Products"], AllowAnonymous: false));Custom Error Handler
Section titled “Custom Error Handler”app.MapGet("/api/data/{id}", async context =>{ var id = context.Query["id"];
if (string.IsNullOrEmpty(id)) { context.SetStatusCode(400); var error = new { Error = "ID parameter is required" }; await context.WriteResponseAsJson(error, error.GetType()); return; }
// Fetch data logic var data = FetchData(id);
if (data == null) { context.SetStatusCode(404); var error = new { Error = $"Data with ID {id} not found" }; await context.WriteResponseAsJson(error, error.GetType()); return; }
await context.WriteResponseAsJson(data, data.GetType());});Authentication and Authorization
Section titled “Authentication and Authorization”Control access to endpoints using the AllowAnonymous metadata property:
Public Endpoint
Section titled “Public Endpoint”app.MapGet("/public/info", async context =>{ await context.Write("Public information");},new EndpointMetadata( Name: "PublicInfo", AllowAnonymous: true)); // No authentication requiredProtected Endpoint
Section titled “Protected Endpoint”app.MapGet("/private/data", async context =>{ // Only accessible to authenticated users var user = context.User; await context.Write($"Hello, {user.Identity?.Name}");},new EndpointMetadata( Name: "PrivateData", AllowAnonymous: false)); // Authentication requiredFor more details on authentication, see Authentication.
Integration with OpenAPI
Section titled “Integration with OpenAPI”Endpoints mapped with MapGet() and MapPost() are automatically included in the OpenAPI specification when using the OpenAPI extensions:
using Cratis.Arc;using Cratis.Arc.OpenApi;
var builder = ArcApplication.CreateBuilder(args);var app = builder.Build();
// Map custom endpointsapp.MapGet("/api/status", async context => { await context.Write("Running"); }, new EndpointMetadata( Name: "GetStatus", Summary: "Get application status", Tags: ["System"])) .MapOpenApi(); // Generate OpenAPI document
await app.RunAsync();The OpenAPI document will include your custom endpoints with the metadata you provided. See OpenAPI Specifications for more details.
Best Practices
Section titled “Best Practices”Use Descriptive Route Patterns
Section titled “Use Descriptive Route Patterns”// Goodapp.MapGet("/api/users/{id}");app.MapPost("/api/orders");
// Avoidapp.MapGet("/u/{i}");app.MapPost("/data");Provide Endpoint Metadata
Section titled “Provide Endpoint Metadata”Always include metadata for documentation and tooling:
app.MapGet("/api/resource", handler, new EndpointMetadata( Name: "GetResource", Summary: "Clear description", Tags: ["ResourceCategory"], AllowAnonymous: false));Handle Errors Gracefully
Section titled “Handle Errors Gracefully”app.MapPost("/api/data", async context =>{ try { var data = await context.ReadBodyAsJson(typeof(MyData)); // Process data } catch (Exception ex) { context.SetStatusCode(500); var error = new { Error = "Internal server error" }; await context.WriteResponseAsJson(error, error.GetType()); }});Set Appropriate Status Codes
Section titled “Set Appropriate Status Codes”context.SetStatusCode(200); // OKcontext.SetStatusCode(201); // Createdcontext.SetStatusCode(400); // Bad Requestcontext.SetStatusCode(401); // Unauthorizedcontext.SetStatusCode(404); // Not Foundcontext.SetStatusCode(500); // Internal Server ErrorUse Dependency Injection
Section titled “Use Dependency Injection”Access services through the request context:
app.MapGet("/api/users", async context =>{ var userService = context.RequestServices.GetRequiredService<IUserService>(); var users = await userService.GetAllAsync(); await context.WriteResponseAsJson(users, users.GetType());});Limitations
Section titled “Limitations”The current endpoint mapping implementation:
- Supports GET and POST methods only
- Does not support route parameters in the pattern (e.g.,
/users/{id}) - Does not support PUT, DELETE, or PATCH methods
For full HTTP method support and advanced routing, consider using ASP.NET Core with Arc.
See Also
Section titled “See Also”- Getting Started - Learn the basics of Arc.Core
- OpenAPI Specifications - Generate API documentation
- Authentication - Secure your endpoints
- Authorization - Control access to resources