### Sample Carter Application Setup and Module Source: https://github.com/cartercommunity/carter/blob/main/README.md This comprehensive sample demonstrates setting up a Carter application with dependency injection for an actor provider, and defining multiple routes within a HomeModule. It includes examples for GET, POST, query string parsing, content negotiation, and form posts with validation. ```csharp var builder = WebApplication.CreateBuilder(args); builder.Services.AddSingleton(); builder.Services.AddCarter(); var app = builder.Build(); app.MapCarter(); app.Run(); public class HomeModule : ICarterModule { public void AddRoutes(IEndpointRouteBuilder app) { app.MapGet("/", () => "Hello from Carter!"); app.MapGet("/qs", (HttpRequest req) => { var ids = req.Query.AsMultiple("ids"); return $"It's {string.Join(",", ids)}"; }); app.MapGet("/conneg", (HttpResponse res) => res.Negotiate(new { Name = "Dave" })); app.MapPost("/validation", HandlePost); app.MapFormPost("/formpost", (Person model) => TypedResults.Ok(model)).DisableAntiforgery(); } private IResult HandlePost(HttpContext ctx, Person person, IDatabase database) { var result = ctx.Request.Validate(person); if (!result.IsValid) { return Results.UnprocessableEntity(result.GetFormattedErrors()); } var id = database.StorePerson(person); ctx.Response.Headers.Location = $"/{id}"; return Results.StatusCode(201); } } public record Person(string Name); public interface IDatabase { int StorePerson(Person person); } public class Database : IDatabase { public int StorePerson(Person person) { //db stuff } } ``` -------------------------------- ### Basic Carter Setup in Program.cs Source: https://context7.com/cartercommunity/carter/llms.txt This snippet shows the minimal setup required to integrate Carter into an ASP.NET Core application. It involves registering Carter services and mapping Carter modules. ```csharp var builder = WebApplication.CreateBuilder(args); // Register Carter services (auto-discovers modules, validators, negotiators) builder.Services.AddCarter(); var app = builder.Build(); // Map all Carter modules to the endpoint routing app.MapCarter(); app.Run(); ``` -------------------------------- ### Install a Local .NET Template Source: https://github.com/cartercommunity/carter/blob/main/TEMPLATE.md Install a template from a local NuGet package file. Replace the path with the actual location of your .nupkg file. ```bash dotnet new -i /path/to/foo.nupkg ``` -------------------------------- ### Implementing ICarterModule for Product Routes Source: https://context7.com/cartercommunity/carter/llms.txt Demonstrates how to define HTTP routes using the ICarterModule interface. Dependencies are injected directly into route handlers. This example covers GET, POST, and DELETE operations for products. ```csharp public class ProductsModule : ICarterModule { public void AddRoutes(IEndpointRouteBuilder app) { app.MapGet("/products", (IProductRepository repo) => { var products = repo.GetAll(); return Results.Ok(products); }); app.MapGet("/products/{id:int}", (int id, IProductRepository repo) => { var product = repo.GetById(id); return product is null ? Results.NotFound() : Results.Ok(product); }); app.MapPost("/products", (Product product, IProductRepository repo) => { var id = repo.Create(product); return Results.Created($"/products/{id}", product); }); app.MapDelete("/products/{id:int}", (int id, IProductRepository repo) => { repo.Delete(id); return Results.NoContent(); }); } } ``` -------------------------------- ### Create a Project from a .NET Template Source: https://github.com/cartercommunity/carter/blob/main/TEMPLATE.md Use this command to create a new project using the installed Carter template. ```bash dotnet new carter ``` -------------------------------- ### Include Route in OpenAPI Documentation Source: https://context7.com/cartercommunity/carter/llms.txt Mark individual API routes for inclusion in OpenAPI/Swagger documentation using the IncludeInOpenApi extension method. This example shows how to configure a GET endpoint for retrieving items. ```csharp app.MapGet("/api/items", () => new[] { "item1", "item2" }) .Produces(200) .WithTags("Items") .WithName("GetItems") .WithDescription("Retrieves all items") .IncludeInOpenApi(); ``` -------------------------------- ### CarterConfigurator for Manual Registration and Custom Lifetimes Source: https://context7.com/cartercommunity/carter/llms.txt Configure Carter services with fine-grained control over module, validator, and response negotiator registration. This example demonstrates registering specific modules, validators, and response negotiators, excluding auto-discovery, setting a default validator lifetime, and using a custom factory for validator lifetimes. ```csharp var builder = WebApplication.CreateBuilder(args); builder.Services.AddCarter(configurator: c => { // Register specific modules only c.WithModule(); c.WithModule(); c.WithModules(typeof(UsersModule), typeof(AuthModule)); // Register specific validators only c.WithValidator(); c.WithValidators(typeof(OrderValidator), typeof(UserValidator)); // Register custom response negotiators c.WithResponseNegotiator(); c.WithResponseNegotiators(typeof(CsvResponseNegotiator)); // Exclude auto-discovery c.WithEmptyModules(); // Don't auto-discover modules c.WithEmptyValidators(); // Don't auto-discover validators c.WithEmptyResponseNegotiators(); // Don't auto-discover negotiators // Set default validator lifetime (default is Singleton) c.WithDefaultValidatorLifetime(ServiceLifetime.Scoped); // Custom validator lifetime factory for fine-grained control c.WithValidatorServiceLifetimeFactory(validatorType => { // Make all validators in a specific namespace transient if (validatorType.Namespace?.Contains("Transient") == true) return ServiceLifetime.Transient; // Check for attribute var attr = validatorType.GetCustomAttribute(); return attr?.Lifetime ?? ServiceLifetime.Singleton; }); }); var app = builder.Build(); app.MapCarter(); app.Run(); ``` -------------------------------- ### Customer Data Endpoint with Negotiate Source: https://context7.com/cartercommunity/carter/llms.txt Handles GET requests for individual customers, negotiating the response format based on the Accept header. Defaults to JSON if no matching negotiator is found. Returns 404 if the customer is not found. ```csharp public class CustomersModule : ICarterModule { public void AddRoutes(IEndpointRouteBuilder app) { app.MapGet("/customers/{id:int}", async (int id, HttpResponse res, ICustomerRepository repo) => { var customer = repo.GetById(id); if (customer is null) { res.StatusCode = 404; return; } // Negotiate selects response format based on Accept header // Defaults to JSON if no matching negotiator found await res.Negotiate(customer); }); app.MapGet("/customers", async (HttpResponse res, ICustomerRepository repo) => { var customers = repo.GetAll(); await res.Negotiate(new { Total = customers.Count(), Data = customers }); }); } } // Request with Accept: application/json returns JSON // Request with Accept: application/xml uses XML negotiator (if registered) ``` -------------------------------- ### Include Multiple Routes in OpenAPI Source: https://context7.com/cartercommunity/carter/llms.txt Demonstrates marking multiple routes within a Carter module for OpenAPI inclusion. This includes GET endpoints for retrieving a single item and a POST endpoint for creating an item, along with their respective configurations. ```csharp app.MapGet("/api/items/{id:int}", (int id) => $"Item {id}") .Produces(200) .Produces(404) .WithTags("Items") .WithName("GetItemById") .IncludeInOpenApi(); app.MapPost("/api/items", (Item item) => Results.Created($"/api/items/{item.Id}", item)) .Accepts("application/json") .Produces(201) .Produces(422) .WithTags("Items") .WithName("CreateItem") .IncludeInOpenApi(); ``` -------------------------------- ### Uninstall a Local .NET Template Source: https://github.com/cartercommunity/carter/blob/main/TEMPLATE.md Uninstall a template that was installed from a local NuGet package. Replace the path with the actual location of your .nupkg file. ```bash dotnet new -u /path/to/foo.nupkg ``` -------------------------------- ### Implement Actors CRUD Module with Carter Source: https://context7.com/cartercommunity/carter/llms.txt Implements a Carter module for managing actors, including endpoints for GET (all and by ID), POST, PUT, and DELETE operations. Uses content negotiation and includes OpenAPI documentation. ```csharp // Models public record Actor(int Id, string Name, int Age); public class ActorValidator : AbstractValidator { public ActorValidator() { RuleFor(x => x.Name).NotEmpty().MaximumLength(100); RuleFor(x => x.Age).InclusiveBetween(0, 150); } } // Module public class ActorsModule : ICarterModule { private static readonly List _actors = new() { new Actor(1, "Brad Pitt", 60), new Actor(2, "Meryl Streep", 74) }; public void AddRoutes(IEndpointRouteBuilder app) { // GET all actors app.MapGet("/actors", () => _actors) .Produces>(200) .WithTags("Actors") .IncludeInOpenApi(); // GET actor by ID with content negotiation app.MapGet("/actors/{id:int}", async (int id, HttpResponse res) => { var actor = _actors.FirstOrDefault(a => a.Id == id); if (actor is null) { res.StatusCode = 404; return; } await res.Negotiate(actor); }) .Produces(200) .Produces(404) .WithTags("Actors") .IncludeInOpenApi(); // POST - Create with manual validation app.MapPost("/actors", async (HttpContext ctx, Actor actor) => { var result = ctx.Request.Validate(actor); if (!result.IsValid) { ctx.Response.StatusCode = 422; await ctx.Response.Negotiate(result.GetFormattedErrors()); return; } var newActor = actor with { Id = _actors.Max(a => a.Id) + 1 }; _actors.Add(newActor); ctx.Response.StatusCode = 201; ctx.Response.Headers.Location = $"/actors/{newActor.Id}"; await ctx.Response.Negotiate(newActor); }) .Accepts("application/json") .Produces(201) .Produces>(422) .WithTags("Actors") .IncludeInOpenApi(); // PUT - Update with auto-validation app.MapPut("/actors/{id:int}", (int id, Actor actor) => { var index = _actors.FindIndex(a => a.Id == id); if (index == -1) return Results.NotFound(); _actors[index] = actor with { Id = id }; return Results.NoContent(); }) .Accepts("application/json") .Produces(204) .Produces(404) .Produces(422) .WithTags("Actors") .IncludeInOpenApi(); // DELETE app.MapDelete("/actors/{id:int}", (int id) => { var actor = _actors.FirstOrDefault(a => a.Id == id); if (actor is null) return Results.NotFound(); _actors.Remove(actor); return Results.NoContent(); }) .Produces(204) .Produces(404) .WithTags("Actors") .IncludeInOpenApi(); // Query string example app.MapGet("/actors/search", (HttpRequest req) => { var name = req.Query.As("name", ""); var minAge = req.Query.As("minAge", 0); var ids = req.Query.AsMultiple("ids"); var query = _actors.AsEnumerable(); if (!string.IsNullOrEmpty(name)) query = query.Where(a => a.Name.Contains(name, StringComparison.OrdinalIgnoreCase)); if (minAge > 0) query = query.Where(a => a.Age >= minAge); if (ids.Any()) query = query.Where(a => ids.Contains(a.Id)); return Results.Ok(query.ToList()); }) .WithTags("Actors") .IncludeInOpenApi(); } } ``` -------------------------------- ### Default Singleton Validator Lifetime Source: https://context7.com/cartercommunity/carter/llms.txt Validators default to a Singleton lifetime unless otherwise configured. This CustomerValidator example shows the default behavior. ```csharp // Default is Singleton (can be changed via configuration) public class CustomerValidator : AbstractValidator { public CustomerValidator() { RuleFor(x => x.Email).EmailAddress(); } } ``` -------------------------------- ### Transient Validator Lifetime with Attribute Source: https://context7.com/cartercommunity/carter/llms.txt Set a validator's service lifetime to Transient using the ValidatorLifetimeAttribute, creating a new instance every time it's needed. This example demonstrates PaymentValidator. ```csharp // Transient lifetime - new instance every time [ValidatorLifetime(ServiceLifetime.Transient)] public class PaymentValidator : AbstractValidator { public PaymentValidator() { RuleFor(x => x.Amount).GreaterThan(0); RuleFor(x => x.CardNumber).CreditCard(); } } ``` -------------------------------- ### Scoped Validator Lifetime with Attribute Source: https://context7.com/cartercommunity/carter/llms.txt Use the ValidatorLifetimeAttribute to set a validator's service lifetime to Scoped, ensuring a new instance per request. This example shows OrderValidator with a dependency on IOrderRepository. ```csharp // Scoped lifetime - new instance per request [ValidatorLifetime(ServiceLifetime.Scoped)] public class OrderValidator : AbstractValidator { private readonly IOrderRepository _repo; public OrderValidator(IOrderRepository repo) { _repo = repo; RuleFor(x => x.ProductId).MustAsync(async (id, ct) => await _repo.ProductExists(id)).WithMessage("Product not found"); } } ``` -------------------------------- ### Configure Carter in Program.cs Source: https://context7.com/cartercommunity/carter/llms.txt Sets up the application services, including Swagger for API documentation and Carter for handling API routes. Ensure Carter is added to the service collection and mapped to the application pipeline. ```csharp var builder = WebApplication.CreateBuilder(args); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddCarter(); var app = builder.Build(); app.UseSwagger(); app.UseSwaggerUI(); app.MapCarter(); app.Run(); ``` -------------------------------- ### Pack a .NET Template Source: https://github.com/cartercommunity/carter/blob/main/TEMPLATE.md Use this command to pack your template project into a NuGet package. ```bash dotnet pack "template\Template.csproj" ``` -------------------------------- ### Search Endpoint with Query String Extensions Source: https://context7.com/cartercommunity/carter/llms.txt Demonstrates retrieving strongly-typed query parameters using As and AsMultiple extensions. Supports default values and parsing multiple values from comma-separated strings or repeated parameters. ```csharp public class SearchModule : ICarterModule { public void AddRoutes(IEndpointRouteBuilder app) { // GET /search?q=laptop&page=2&limit=20&categories=1,2,3&sort=price app.MapGet("/search", (HttpRequest req) => { // Single value with default var query = req.Query.As("q", defaultValue: ""); var page = req.Query.As("page", defaultValue: 1); var limit = req.Query.As("limit", defaultValue: 10); var sortBy = req.Query.As("sort", defaultValue: "relevance"); // Multiple values (comma-separated or repeated params) // Handles: ?categories=1,2,3 OR ?categories=1&categories=2&categories=3 var categoryIds = req.Query.AsMultiple("categories"); var tags = req.Query.AsMultiple("tags"); return Results.Ok(new { Query = query, Page = page, Limit = limit, SortBy = sortBy, Categories = categoryIds.ToList(), Tags = tags.ToList() }); }); } } // GET /search?q=laptop&page=2&categories=1,2,3 // Response: { "query": "laptop", "page": 2, "limit": 10, "sortBy": "relevance", "categories": [1,2,3], "tags": [] } ``` -------------------------------- ### Using CarterModule Base Class for Order Routes (Deprecated) Source: https://context7.com/cartercommunity/carter/llms.txt Illustrates route grouping and module-level configuration using the deprecated CarterModule base class. This includes setting authorization, CORS, rate limiting, OpenAPI metadata, and Before/After filters. ```csharp public class OrdersModule : CarterModule { public OrdersModule() : base("/api/orders") { // Require authorization for all routes in this module this.RequireAuthorization("AdminPolicy"); // Configure CORS this.RequireCors("AllowedOrigins"); // Configure rate limiting this.RequireRateLimiting("OrdersRateLimit"); // OpenAPI configuration this.IncludeInOpenApi(); this.WithTags("Orders"); this.WithDescription("Order management API endpoints"); // Before filter - runs before every route handler this.Before = context => { var logger = context.HttpContext.RequestServices.GetRequiredService>(); logger.LogInformation("Processing order request"); return null; // Return IResult to short-circuit, null to continue }; // After filter - runs after every route handler this.After = context => { var logger = context.HttpContext.RequestServices.GetRequiredService>(); logger.LogInformation("Order request completed"); }; } public override void AddRoutes(IEndpointRouteBuilder app) { // Routes are relative to base path "/api/orders" app.MapGet("/", () => "List all orders"); // GET /api/orders/ app.MapGet("/{id:int}", (int id) => $"Order {id}"); // GET /api/orders/123 app.MapPost("/", (Order order) => Results.Created($"/api/orders/{order.Id}", order)); } } ``` -------------------------------- ### Define Authorized Route with Carter Source: https://github.com/cartercommunity/carter/blob/main/README.md Demonstrates how to define a route with authorization requirements using Carter's extension methods on IEndpointRouteBuilder. ```csharp app.MapGet("/ ).RequireAuthorization(); ``` -------------------------------- ### OpenAPI Integration with IncludeInOpenApi Source: https://context7.com/cartercommunity/carter/llms.txt Mark routes or entire modules for inclusion in OpenAPI/Swagger documentation. ```APIDOC ## OpenAPI Integration with IncludeInOpenApi Mark routes or entire modules for inclusion in OpenAPI/Swagger documentation. ```csharp public class ApiModule : ICarterModule { public void AddRoutes(IEndpointRouteBuilder app) { app.MapGet("/api/items", () => new[] { "item1", "item2" }) .Produces(200) .WithTags("Items") .WithName("GetItems") .WithDescription("Retrieves all items") .IncludeInOpenApi(); app.MapGet("/api/items/{id:int}", (int id) => $"Item {id}") .Produces(200) .Produces(404) .WithTags("Items") .WithName("GetItemById") .IncludeInOpenApi(); app.MapPost("/api/items", (Item item) => Results.Created($"/api/items/{item.Id}", item)) .Accepts("application/json") .Produces(201) .Produces(422) .WithTags("Items") .WithName("CreateItem") .IncludeInOpenApi(); } } // Program.cs - Swagger configuration builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }); // Only include endpoints marked with IncludeInOpenApi options.DocInclusionPredicate((s, description) => { foreach (var metaData in description.ActionDescriptor.EndpointMetadata) { if (metaData is IIncludeOpenApi) return true; } return false; }); }); var app = builder.Build(); app.UseSwagger(); app.UseSwaggerUI(); app.MapCarter(); app.Run(); ``` ``` -------------------------------- ### Configure Carter Services and Modules Source: https://github.com/cartercommunity/carter/blob/main/README.md Customize Carter's behavior by using the CarterConfigurator. This allows manual registration of modules, validators, and response negotiators, as well as configuring validator lifetimes. ```csharp builder.Services.AddCarter(configurator: c => { c.WithResponseNegotiator(); c.WithModule(); c.WithValidator(); c.WithDefaultValidatorLifetime(ServiceLifetime.Singleton); c.WithValidatorServiceLifetimeFactory(t => {if t is PersonValidator...}) }); ``` -------------------------------- ### Save All Files to Disk Source: https://context7.com/cartercommunity/carter/llms.txt Use BindAndSaveFiles to bind all uploaded files and save them to a specified directory on disk. The original file names are preserved. ```csharp app.MapPost("/upload/save-all", async (HttpRequest req) => { var saveLocation = "/uploads/batch"; await req.BindAndSaveFiles(saveLocation); return Results.Ok(new { SavedTo = saveLocation }); }); ``` -------------------------------- ### Stream File for Download Source: https://context7.com/cartercommunity/carter/llms.txt Use FromStream with a ContentDisposition header set to 'Inline = false' to force a file download. Specify the MIME type as 'application/octet-stream' for generic binary data. ```csharp app.MapGet("/media/download/{filename}", async (string filename, HttpResponse res) => { var filePath = $"/files/{filename}"; using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read); // Add Content-Disposition for file download var contentDisposition = new ContentDisposition { FileName = filename, Inline = false // Forces download instead of inline display }; await res.FromStream(fileStream, "application/octet-stream", contentDisposition); }); ``` -------------------------------- ### Stream Video Content with Range Support Source: https://context7.com/cartercommunity/carter/llms.txt Use FromStream to stream file content to the HttpResponse. This method supports Range headers for partial content delivery, enabling video seeking and resume downloads. ```csharp app.MapGet("/media/video/{filename}", async (string filename, HttpResponse res) => { var filePath = $"/media/videos/{filename}"; if (!File.Exists(filePath)) { res.StatusCode = 404; return; } using var videoStream = new FileStream(filePath, FileMode.Open, FileAccess.Read); // Supports Range header for video seeking/streaming await res.FromStream(videoStream, "video/mp4"); }); ``` -------------------------------- ### Create a Basic Carter Module Source: https://github.com/cartercommunity/carter/blob/main/README.md Define a class that implements ICarterModule to add routes to your application. The AddRoutes method is where you define your endpoints using IEndpointRouteBuilder. ```csharp public class HomeModule : ICarterModule { public void AddRoutes(IEndpointRouteBuilder app) { app.MapGet("/", () => "Hello from Carter!"); } } ``` -------------------------------- ### Bind Multiple File Uploads Source: https://context7.com/cartercommunity/carter/llms.txt Use BindFiles to retrieve all uploaded files from a multipart form request. This method returns a collection of files. ```csharp app.MapPost("/upload/multiple", async (HttpRequest req) => { var files = await req.BindFiles(); var fileInfo = files.Select(f => new { f.FileName, f.Length, f.ContentType }); return Results.Ok(new { Files = fileInfo, Count = files.Count() }); }); ``` -------------------------------- ### Synchronous and Async Validation with Carter Source: https://context7.com/cartercommunity/carter/llms.txt Demonstrates how to use `Validate` and `ValidateAsync` extensions for synchronous and asynchronous validation of request models within Carter route handlers. Handles validation failures by returning formatted errors or validation problems. ```csharp public record CreateUserRequest(string Email, string Name, int Age); public class CreateUserRequestValidator : AbstractValidator { public CreateUserRequestValidator() { RuleFor(x => x.Email).NotEmpty().EmailAddress(); RuleFor(x => x.Name).NotEmpty().MinimumLength(2); RuleFor(x => x.Age).GreaterThan(0).LessThan(150); } } public class UsersModule : ICarterModule { public void AddRoutes(IEndpointRouteBuilder app) { // Synchronous validation app.MapPost("/users", async (HttpRequest req, CreateUserRequest user, HttpResponse res) => { var result = req.Validate(user); if (!result.IsValid) { res.StatusCode = 422; await res.Negotiate(result.GetFormattedErrors()); return; } // Save user to database... res.StatusCode = 201; }); // Async validation app.MapPut("/users/{id:int}", async (int id, HttpRequest req, CreateUserRequest user, HttpResponse res) => { var result = await req.ValidateAsync(user); if (!result.IsValid) { return Results.UnprocessableEntity(result.GetValidationProblems()); } // Update user... return Results.NoContent(); }); } } ``` ```json [ { "propertyName": "Email", "errorMessage": "'Email' must not be empty." }, { "propertyName": "Age", "errorMessage": "'Age' must be greater than '0'." } ] ``` -------------------------------- ### Program.cs Swagger Configuration for OpenAPI Inclusion Source: https://context7.com/cartercommunity/carter/llms.txt Configure Swagger generation in Program.cs to only include endpoints marked with IIncludeOpenApi. This ensures that only explicitly included routes appear in the OpenAPI documentation. ```csharp // Program.cs - Swagger configuration builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }); // Only include endpoints marked with IncludeInOpenApi options.DocInclusionPredicate ((s, description) => { foreach (var metaData in description.ActionDescriptor.EndpointMetadata) { if (metaData is IIncludeOpenApi) return true; } return false; }); }); var app = builder.Build(); app.UseSwagger(); app.UseSwaggerUI(); app.MapCarter(); app.Run(); ``` -------------------------------- ### Sales Report Endpoint with AsJson Source: https://context7.com/cartercommunity/carter/llms.txt Generates and returns a sales report, forcing the response to be JSON regardless of the Accept header. This bypasses the standard content negotiation process. ```csharp public class ReportsModule : ICarterModule { public void AddRoutes(IEndpointRouteBuilder app) { app.MapGet("/reports/sales", async (HttpResponse res, IReportService service) => { var report = service.GenerateSalesReport(); // Always returns JSON, ignores Accept header await res.AsJson(new { GeneratedAt = DateTime.UtcNow, Report = report }); }); } } ``` -------------------------------- ### Save Single File to Disk Source: https://context7.com/cartercommunity/carter/llms.txt Use BindAndSaveFile to bind a single uploaded file and automatically save it to a specified location on disk with an optional custom file name. ```csharp app.MapPost("/upload/save", async (HttpRequest req) => { var saveLocation = "/uploads"; var customFileName = $"{Guid.NewGuid()}.dat"; await req.BindAndSaveFile(saveLocation, customFileName); return Results.Ok(new { SavedTo = $"{saveLocation}/{customFileName}" }); }); ``` -------------------------------- ### CarterConfigurator for Manual Registration Source: https://context7.com/cartercommunity/carter/llms.txt Provides fine-grained control over module, validator, and response negotiator registration with custom validator lifetimes. ```APIDOC ## CarterConfigurator for Manual Registration Fine-grained control over module, validator, and response negotiator registration with custom validator lifetimes. ```csharp var builder = WebApplication.CreateBuilder(args); builder.Services.AddCarter(configurator: c => { // Register specific modules only c.WithModule(); c.WithModule(); c.WithModules(typeof(UsersModule), typeof(AuthModule)); // Register specific validators only c.WithValidator(); c.WithValidators(typeof(OrderValidator), typeof(UserValidator)); // Register custom response negotiators c.WithResponseNegotiator(); c.WithResponseNegotiators(typeof(CsvResponseNegotiator)); // Exclude auto-discovery c.WithEmptyModules(); // Don't auto-discover modules c.WithEmptyValidators(); // Don't auto-discover validators c.WithEmptyResponseNegotiators(); // Don't auto-discover negotiators // Set default validator lifetime (default is Singleton) c.WithDefaultValidatorLifetime(ServiceLifetime.Scoped); // Custom validator lifetime factory for fine-grained control c.WithValidatorServiceLifetimeFactory(validatorType => { // Make all validators in a specific namespace transient if (validatorType.Namespace?.Contains("Transient") == true) return ServiceLifetime.Transient; // Check for attribute var attr = validatorType.GetCustomAttribute(); return attr?.Lifetime ?? ServiceLifetime.Singleton; }); }); var app = builder.Build(); app.MapCarter(); app.Run(); ``` ``` -------------------------------- ### Form Data Binding with MapFormPost Source: https://context7.com/cartercommunity/carter/llms.txt Uses the `MapFormPost` extension to automatically bind form data to a model type `T` without needing the `[FromForm]` attribute. This simplifies handling form submissions. ```csharp public record ContactForm(string Name, string Email, string Message); public class ContactModule : ICarterModule { public void AddRoutes(IEndpointRouteBuilder app) { // Automatically binds form data to ContactForm app.MapFormPost("/contact", (ContactForm form) => { // Process the contact form Console.WriteLine($"Contact from {form.Name} ({form.Email}): {form.Message}"); return TypedResults.Ok(new { Success = true, Message = "Thank you for contacting us!" }); }).DisableAntiforgery(); // Disable if not using antiforgery tokens } } ``` ```html
``` -------------------------------- ### Auto-Validation with MapPost and MapPut Source: https://context7.com/cartercommunity/carter/llms.txt Utilizes Carter's route extensions `MapPost` and `MapPut` to automatically validate the model type `T` before the handler executes. Returns a 422 Problem Details response if validation fails. ```csharp public record UpdateProductRequest(string Name, decimal Price, int Stock); public class UpdateProductRequestValidator : AbstractValidator { public UpdateProductRequestValidator() { RuleFor(x => x.Name).NotEmpty(); RuleFor(x => x.Price).GreaterThan(0); RuleFor(x => x.Stock).GreaterThanOrEqualTo(0); } } public class ProductsModule : ICarterModule { public void AddRoutes(IEndpointRouteBuilder app) { // Auto-validates UpdateProductRequest before handler executes // Returns 422 with Problem Details if validation fails app.MapPost("/products", (UpdateProductRequest product, IProductService service) => { // Only reached if validation passes var id = service.Create(product); return Results.Created($"/products/{id}", product); }); app.MapPut("/products/{id:int}", (int id, UpdateProductRequest product, IProductService service) => { // Only reached if validation passes service.Update(id, product); return Results.NoContent(); }); } } ``` ```json { "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", "status": 422, "errors": [ { "propertyName": "Price", "errorMessage": "'Price' must be greater than '0'." } ] } ``` -------------------------------- ### Read Request Body as String (Default Encoding) Source: https://context7.com/cartercommunity/carter/llms.txt Use AsStringAsync on the HttpRequest.Body to read the entire request stream into a string. UTF-8 encoding is used by default. ```csharp app.MapPost("/webhook", async (HttpRequest req) => { // Read raw request body as string (UTF-8 by default) var rawBody = await req.Body.AsStringAsync(); // Or specify encoding var rawBodyUtf16 = await req.Body.AsStringAsync(Encoding.Unicode); // Process webhook payload Console.WriteLine($"Received webhook: {rawBody}"); return Results.Ok(); }); ``` -------------------------------- ### Bind Single File Upload Source: https://context7.com/cartercommunity/carter/llms.txt Use BindFile to access a single uploaded file from a multipart form request. Process the file in memory after binding. ```csharp app.MapPost("/upload/single", async (HttpRequest req) => { var file = await req.BindFile(); // Process file in memory using var stream = file.OpenReadStream(); var content = await new StreamReader(stream).ReadToEndAsync(); return Results.Ok(new { FileName = file.FileName, Size = file.Length, ContentType = file.ContentType }); }); ``` -------------------------------- ### Add Carter to ASP.NET Core Application Source: https://github.com/cartercommunity/carter/blob/main/README.md This snippet shows how to add Carter services and map Carter endpoints in your ASP.NET Core application's Program.cs file. Ensure Carter is added to services and then mapped to the application pipeline. ```csharp var builder = WebApplication.CreateBuilder(args); builder.Services.AddCarter(); var app = builder.Build(); app.MapCarter(); app.Run(); ``` -------------------------------- ### Custom XML Response Negotiator Source: https://context7.com/cartercommunity/carter/llms.txt Implements the IResponseNegotiator interface to handle XML content negotiation. This negotiator is registered with Carter to respond to requests with an Accept header containing 'xml'. ```csharp public class XmlResponseNegotiator : IResponseNegotiator { public bool CanHandle(MediaTypeHeaderValue accept) { return accept.MediaType.ToString().Contains("xml", StringComparison.OrdinalIgnoreCase); } public async Task Handle(HttpRequest req, HttpResponse res, T model, CancellationToken cancellationToken) { res.ContentType = "application/xml; charset=utf-8"; var serializer = new XmlSerializer(typeof(T)); using var writer = new StringWriter(); serializer.Serialize(writer, model); await res.WriteAsync(writer.ToString(), cancellationToken); } } // Registration in Program.cs builder.Services.AddCarter(configurator: c => { c.WithResponseNegotiator(); }); // Or install Carter.ResponseNegotiators.Newtonsoft for Newtonsoft.Json support // dotnet add package Carter.ResponseNegotiators.Newtonsoft // (Auto-registered, no configuration needed) ``` -------------------------------- ### Actors API Endpoints Source: https://context7.com/cartercommunity/carter/llms.txt This section details the CRUD operations available for managing actor resources. ```APIDOC ## GET /actors ### Description Retrieves a list of all actors. ### Method GET ### Endpoint /actors ### Response #### Success Response (200) - **List** - A list of actor objects. #### Response Example ```json [ { "id": 1, "name": "Brad Pitt", "age": 60 }, { "id": 2, "name": "Meryl Streep", "age": 74 } ] ``` ``` ```APIDOC ## GET /actors/{id:int} ### Description Retrieves a specific actor by their ID. Supports content negotiation. ### Method GET ### Endpoint /actors/{id} ### Parameters #### Path Parameters - **id** (int) - Required - The unique identifier of the actor. ### Response #### Success Response (200) - **Actor** - The actor object if found. #### Error Response (404) - Not Found if the actor with the specified ID does not exist. #### Response Example ```json { "id": 1, "name": "Brad Pitt", "age": 60 } ``` ``` ```APIDOC ## POST /actors ### Description Creates a new actor. Requires manual validation. ### Method POST ### Endpoint /actors ### Parameters #### Request Body - **Actor** (Actor) - Required - The actor object to create. ### Request Example ```json { "name": "Tom Hanks", "age": 67 } ``` ### Response #### Success Response (201) - **Actor** - The newly created actor object with its assigned ID. #### Error Response (422) - **IEnumerable** - Returns validation errors if the request body is invalid. #### Response Example ```json { "id": 3, "name": "Tom Hanks", "age": 67 } ``` ``` ```APIDOC ## PUT /actors/{id:int} ### Description Updates an existing actor by their ID. Supports auto-validation. ### Method PUT ### Endpoint /actors/{id} ### Parameters #### Path Parameters - **id** (int) - Required - The unique identifier of the actor to update. #### Request Body - **Actor** (Actor) - Required - The updated actor object. ### Request Example ```json { "name": "Brad Pitt", "age": 61 } ``` ### Response #### Success Response (204) - No Content is returned upon successful update. #### Error Response (404) - Not Found if the actor with the specified ID does not exist. #### Error Response (422) - **ProblemDetails** - Returns validation errors if the request body is invalid. ``` ```APIDOC ## DELETE /actors/{id:int} ### Description Deletes an actor by their ID. ### Method DELETE ### Endpoint /actors/{id} ### Parameters #### Path Parameters - **id** (int) - Required - The unique identifier of the actor to delete. ### Response #### Success Response (204) - No Content is returned upon successful deletion. #### Error Response (404) - Not Found if the actor with the specified ID does not exist. ``` ```APIDOC ## GET /actors/search ### Description Searches for actors based on query parameters like name, minimum age, and IDs. ### Method GET ### Endpoint /actors/search ### Parameters #### Query Parameters - **name** (string) - Optional - Filters actors by name (case-insensitive). - **minAge** (int) - Optional - Filters actors by minimum age. - **ids** (int[]) - Optional - Filters actors by a list of IDs. ### Response #### Success Response (200) - **List** - A list of actors matching the search criteria. #### Response Example ```json [ { "id": 1, "name": "Brad Pitt", "age": 60 } ] ``` ``` -------------------------------- ### Define Actor Model and Validator Source: https://context7.com/cartercommunity/carter/llms.txt Defines the data structure for an Actor and its validation rules using FluentValidation. Ensure FluentValidation is configured in your services. ```csharp public record Actor(int Id, string Name, int Age); public class ActorValidator : AbstractValidator { public ActorValidator() { RuleFor(x => x.Name).NotEmpty().MaximumLength(100); RuleFor(x => x.Age).InclusiveBetween(0, 150); } } ``` -------------------------------- ### Validator Lifetime Attributes Source: https://context7.com/cartercommunity/carter/llms.txt Control the DI service lifetime of individual validators using the ValidatorLifetime attribute. ```APIDOC ## ValidatorLifetimeAttribute Control the DI service lifetime of individual validators using an attribute. ```csharp // Scoped lifetime - new instance per request [ValidatorLifetime(ServiceLifetime.Scoped)] public class OrderValidator : AbstractValidator { private readonly IOrderRepository _repo; public OrderValidator(IOrderRepository repo) { _repo = repo; RuleFor(x => x.ProductId).MustAsync(async (id, ct) => await _repo.ProductExists(id)).WithMessage("Product not found"); } } // Transient lifetime - new instance every time [ValidatorLifetime(ServiceLifetime.Transient)] public class PaymentValidator : AbstractValidator { public PaymentValidator() { RuleFor(x => x.Amount).GreaterThan(0); RuleFor(x => x.CardNumber).CreditCard(); } } // Default is Singleton (can be changed via configuration) public class CustomerValidator : AbstractValidator { public CustomerValidator() { RuleFor(x => x.Email).EmailAddress(); } } ``` ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.