### Standard Result Operation Chaining Source: https://github.com/ardalis/result/blob/main/README.md This example shows a traditional approach to handling multiple Result operations, including explicit success checks. ```csharp ```csharp [HttpPost("Summary")] public async Task> CreateSummaryForecast([FromBody] ForecastRequestDto model) { var result1 = await _weatherService.GetSingleForecast(model); if (!result1.IsSuccess) { return result1.ToActionResult(this); } var result2 = await _weatherService.GetForecastDetailsAsync(result1.Value.Id); return result2 .Map(details => new WeatherForecastSummaryDto(details.Date, details.Summary)) .ToActionResult(this); } ``` ``` -------------------------------- ### Complete Service Example with Result Pattern Source: https://context7.com/ardalis/result/llms.txt Demonstrates a domain service implementing the Result pattern for handling multiple outcome types, including validation, customer verification, product checks, and order creation. Use this for complex operations requiring granular outcome reporting. ```csharp using Ardalis.Result; using Ardalis.Result.FluentValidation; using FluentValidation; public record CreateOrderCommand(int CustomerId, List Items); public record OrderItem(int ProductId, int Quantity); public record Order(int Id, int CustomerId, decimal Total, string Status); public class CreateOrderCommandValidator : AbstractValidator { public CreateOrderCommandValidator() { RuleFor(x => x.CustomerId).GreaterThan(0); RuleFor(x => x.Items).NotEmpty().WithMessage("Order must contain at least one item"); RuleForEach(x => x.Items).ChildRules(item => { item.RuleFor(x => x.ProductId).GreaterThan(0); item.RuleFor(x => x.Quantity).GreaterThan(0).LessThanOrEqualTo(100); }); } } public class OrderService { private readonly ICustomerRepository _customers; private readonly IProductRepository _products; private readonly IOrderRepository _orders; public async Task> CreateOrderAsync(CreateOrderCommand command) { // Step 1: Validate command var validator = new CreateOrderCommandValidator(); var validationResult = await validator.ValidateAsync(command); if (!validationResult.IsValid) { return Result.Invalid(validationResult.AsErrors()); } // Step 2: Verify customer exists var customer = await _customers.GetByIdAsync(command.CustomerId); if (customer == null) { return Result.NotFound($"Customer {command.CustomerId} not found"); } // Step 3: Check customer status if (customer.IsSuspended) { return Result.Forbidden("Customer account is suspended"); } // Step 4: Validate products and calculate total decimal total = 0; foreach (var item in command.Items) { var product = await _products.GetByIdAsync(item.ProductId); if (product == null) { return Result.NotFound($"Product {item.ProductId} not found"); } if (product.Stock < item.Quantity) { return Result.Conflict( "Insufficient stock for product {product.Name}", $"Requested: {item.Quantity}, Available: {product.Stock}"); } total += product.Price * item.Quantity; } // Step 5: Create order try { var order = new Order( Id: await _orders.GetNextIdAsync(), CustomerId: command.CustomerId, Total: total, Status: "Created" ); await _orders.SaveAsync(order); // Return Created with location return Result.Created(order, $"/api/orders/{order.Id}"); } catch (Exception ex) { return Result.CriticalError($"Failed to create order: {ex.Message}"); } } } // Controller using the service [ApiController] [Route("api/orders")] public class OrdersController : ControllerBase { private readonly OrderService _orderService; [TranslateResultToActionResult] [ExpectedFailures(ResultStatus.Invalid, ResultStatus.NotFound, ResultStatus.Forbidden, ResultStatus.Conflict)] [HttpPost] public async Task> CreateOrder([FromBody] CreateOrderCommand command) { return await _orderService.CreateOrderAsync(command) .MapAsync(order => Task.FromResult(new OrderDto(order.Id, order.Total, order.Status))); } } ``` -------------------------------- ### Map Result to Result Source: https://github.com/ardalis/result/blob/main/docs/index.md This C# example shows how to use the Map method to transform a Result into a Result. It then uses ToActionResult to convert the mapped result into an ActionResult. ```csharp [HttpPost("Summary")] public ActionResult CreateSummaryForecast([FromBody] ForecastRequestDto model) { return _weatherService.GetSingleForecast(model) .Map(wf => new WeatherForecastSummaryDto(wf.Date, wf.Summary)) .ToActionResult(this); } ``` -------------------------------- ### Minimal API Result Translation Source: https://github.com/ardalis/result/blob/main/docs/getting-started/quick-start-guide.md Use the `.ToMinimalApiResult()` extension method in minimal APIs to automatically translate Result types to ActionResults. Ensure the Ardalis.Result.AspNetCore package is installed. ```csharp app.MapPost("/Forecast/New", (ForecastRequestDto request, WeatherService weatherService) => { return weatherService.GetForecast(request).ToMinimalApiResult(); // 👈 }); ``` -------------------------------- ### FluentValidation Integration for Result Source: https://github.com/ardalis/result/blob/main/docs/index.md Example of using Ardalis.Result with FluentValidation to handle validation errors in a service method. ```csharp public async Task> UpdateAsync(BlogCategory blogCategory) { if (Guid.Empty == blogCategory.BlogCategoryId) return Result.NotFound(); var validator = new BlogCategoryValidator(); var validation = await validator.ValidateAsync(blogCategory); if (!validation.IsValid) { return Result.Invalid(validation.AsErrors()); } var itemToUpdate = (await GetByIdAsync(blogCategory.BlogCategoryId)).Value; if (itemToUpdate == null) { return Result.NotFound(); } itemToUpdate.Update(blogCategory.Name, blogCategory.ParentId); return Result.Success(await _blogCategoryRepository.UpdateAsync(itemToUpdate)); } ``` -------------------------------- ### Original Method Throwing Exception Source: https://github.com/ardalis/result/blob/main/docs/getting-started/index.md An example of a method that throws an InvalidOperationException when a record is not found. This is generally discouraged for non-exceptional cases, especially in web APIs. ```csharp public void Remove(int id) { if (!Exists(id)) { throw new InvalidOperationException($ ``` ```csharp // Remove the record } ``` -------------------------------- ### Controller Action Result Translation with Attribute Source: https://github.com/ardalis/result/blob/main/docs/getting-started/quick-start-guide.md Apply the `[TranslateResultToActionResult]` attribute to controller actions to automatically convert Result types to ActionResults. Change the method's return type to `Result` or `Result`. This example also shows `[ExpectedFailures]` attribute usage. ```csharp /// /// This uses a filter to convert an Ardalis.Result return type to an ActionResult. /// This filter could be used per controller or globally! /// /// [TranslateResultToActionResult] // 👈 [ExpectedFailures(ResultStatus.NotFound, ResultStatus.Invalid)] [HttpDelete("Remove/{id}")] public Result RemovePerson(int id) { return _personService.Remove(id); } ``` ```csharp /// /// This uses a filter to convert an Ardalis.Result return type to an ActionResult. /// This filter could be used per controller or globally! /// /// [TranslateResultToActionResult] // 👈 [ExpectedFailures(ResultStatus.NotFound, ResultStatus.Invalid)] [HttpPost("New/")] public Result CreatePerson(CreatePersonRequestDto request) { return _personService.Create(request.FirstName, request.LastName); } ``` -------------------------------- ### Standard Customer Creation Source: https://github.com/ardalis/result/blob/main/docs/index.md Illustrates a typical method for creating a customer in C#. ```csharp public Customer CreateCustomer(string firstName, string lastName) { // more logic return customer; } ``` -------------------------------- ### Test CustomerService with FluentAssertions Source: https://context7.com/ardalis/result/llms.txt Demonstrates various fluent assertion methods for testing different Result outcomes from a CustomerService. Includes checks for success, not found, invalid data, conflicts, forbidden access, service unavailability, critical errors, created resources with location, and success with a message. ```csharp using Ardalis.Result; using Ardalis.Result.FluentAssertions; using FluentAssertions; using Xunit; public class CustomerServiceTests { private readonly CustomerService _service = new(); [Fact] public void GetCustomer_WithValidId_ShouldBeSuccess() { var result = _service.GetCustomer(1); result.ShouldBeSuccess(); result.Value.Should().NotBeNull(); result.Value.Id.Should().Be(1); } [Fact] public void GetCustomer_WithInvalidId_ShouldBeNotFound() { var result = _service.GetCustomer(999); result.ShouldBeNotFound(); // Or with specific error messages: result.ShouldBeNotFound("Customer not found"); } [Fact] public void CreateCustomer_WithInvalidData_ShouldBeInvalid() { var result = _service.CreateCustomer("", ""); result.ShouldBeInvalid(); result.ShouldHaveValidationErrorWithIdentifier("Name"); result.ShouldHaveValidationErrorWithMessage("Name is required"); } [Fact] public void CreateCustomer_WhenExists_ShouldBeConflict() { var result = _service.CreateCustomer("John", "Doe"); result.ShouldBeConflict("Customer already exists"); } [Fact] public void DeleteCustomer_WithoutPermission_ShouldBeForbidden() { var result = _service.DeleteCustomer(1, regularUser); result.ShouldBeForbidden(); } [Fact] public void ProcessPayment_WhenServiceDown_ShouldBeUnavailable() { var result = _service.ProcessPayment(new PaymentRequest()); result.ShouldBeUnavailable("Payment service unavailable"); } [Fact] public void SaveData_WithCriticalFailure_ShouldBeCriticalError() { var result = _service.SaveData(data); result.ShouldBeCriticalError(); } [Fact] public void CreateResource_ShouldBeCreatedWithLocation() { var result = _service.CreateResource(data); result.ShouldBeCreatedWithLocation("/api/resources/123"); } [Fact] public void ExecuteOperation_ShouldBeSuccessWithMessage() { var result = _service.ExecuteOperation(); result.ShouldBeSuccessWithMessage("Operation completed successfully"); } } ``` -------------------------------- ### Chaining Result Operations with BindAsync Source: https://github.com/ardalis/result/blob/main/README.md Demonstrates using `BindAsync` to chain multiple Result operations concisely, avoiding explicit success checks. ```csharp ```csharp [HttpPost("BindedForecast")] public async Task> CreateSummaryForecastWithBind([FromBody] ForecastRequestDto model) { return await _weatherService.GetSingleForecast(model) .BindAsync(wf => _weatherService.GetForecastDetailsAsync(wf.Id)) .Map(details => new WeatherForecastSummaryDto(details.Date, details.Summary)) .ToActionResult(this); } ``` ``` -------------------------------- ### Add Ardalis.Result Package Source: https://github.com/ardalis/result/blob/main/docs/getting-started/index.md Use the dotnet CLI to add the base Ardalis.Result package to your project. ```bash dotnet add package Ardalis.Result ``` -------------------------------- ### Customer Retrieval with Exception Handling Source: https://github.com/ardalis/result/blob/main/docs/index.md Demonstrates handling potential exceptions during customer retrieval in an ASP.NET Core API controller. ```csharp [HttpGet] public async Task> GetCustomer(int customerId) { try { var customer = _repository.GetById(customerId); var customerDTO = CustomerDTO.MapFrom(customer); return Ok(customerDTO); } catch (NullReferenceException ex) { return NotFound(); } catch (Exception ex) { return new StatusCodeResult(StatusCodes.Status500InternalServerError); } } ``` -------------------------------- ### Standard Customer Retrieval Source: https://github.com/ardalis/result/blob/main/docs/index.md Illustrates a typical method for retrieving a customer by ID in C#. ```csharp public Customer GetCustomer(int customerId) { // more logic return customer; } ``` -------------------------------- ### Add Ardalis.Result.AspNetCore Package Source: https://github.com/ardalis/result/blob/main/docs/getting-started/index.md Use the dotnet CLI to add the Ardalis.Result.AspNetCore package for ASP.NET Core integration. ```bash dotnet add package Ardalis.Result.AspNetCore ``` -------------------------------- ### Create Created Results in C# Source: https://context7.com/ardalis/result/llms.txt Use Result.Created() to indicate successful resource creation (HTTP 201). Optionally include a location header for the new resource. Check the Status property for ResultStatus.Created. ```csharp using Ardalis.Result; public class OrderService { public Result CreateOrder(CreateOrderRequest request) { var order = new Order { Id = Guid.NewGuid(), CustomerId = request.CustomerId, Total = request.Items.Sum(i => i.Price) }; // Save order to database... // Return Created result with location return Result.Created(order, $"/api/orders/{order.Id}"); } } // Usage var service = new OrderService(); var result = service.CreateOrder(new CreateOrderRequest { CustomerId = 1, Items = items }); if (result.Status == ResultStatus.Created) { Console.WriteLine($"Order created at: {result.Location}"); Console.WriteLine($"Order ID: {result.Value.Id}"); } // Output: Order created at: /api/orders/550e8400-e29b-41d4-a716-446655440000 // Output: Order ID: 550e8400-e29b-41d4-a716-446655440000 ``` -------------------------------- ### Create Success Results in C# Source: https://context7.com/ardalis/result/llms.txt Use Result.Success() and Result.Success() to create successful results. Values can be implicitly converted to Result. Access the value using the .Value property after checking IsSuccess. ```csharp using Ardalis.Result; // Success without a value (void operations) Result voidResult = Result.Success(); // Success with a value var customer = new Customer { Id = 1, Name = "John Doe" }; Result customerResult = Result.Success(customer); // Success with a value and message Result resultWithMessage = Result.Success(customer, "Customer created successfully"); // Implicit conversion - values can be implicitly converted to Result Result implicitResult = 42; // Access the value and check status if (customerResult.IsSuccess) { Customer value = customerResult.Value; Console.WriteLine($"Customer: {value.Name}"); } // Output: Customer: John Doe ``` -------------------------------- ### Chain Operations with Bind Source: https://context7.com/ardalis/result/llms.txt Use Bind to chain multiple operations that return a Result. The chain short-circuits and returns the first encountered failure, preventing subsequent operations from executing. This is essential for multi-step workflows where failure at any stage should halt the process. ```csharp using Ardalis.Result; public class OrderWorkflow { public Result ValidateCustomer(int customerId) { if (customerId <= 0) return Result.Invalid(new ValidationError("customerId", "Invalid customer")); return Result.Success(new Customer { Id = customerId, Name = "John" }); } public Result CreateOrder(Customer customer) { if (customer.IsSuspended) return Result.Forbidden("Customer account is suspended"); return Result.Success(new Order { CustomerId = customer.Id, Status = "Created" }); } public Result ProcessPayment(Order order) { if (order.Total > 10000) return Result.Error("Amount exceeds maximum allowed"); order.Status = "Paid"; return Result.Success(order); } public Result PlaceOrder(int customerId) { // Chain operations - stops at first failure return ValidateCustomer(customerId) .Bind(customer => CreateOrder(customer)) .Bind(order => ProcessPayment(order)); } } // Usage var workflow = new OrderWorkflow(); // Success case - all steps complete var successResult = workflow.PlaceOrder(1); Console.WriteLine($"Status: {successResult.Status}, Order: {successResult.Value?.Status}"); // Output: Status: Ok, Order: Paid // Failure case - stops at validation var failResult = workflow.PlaceOrder(-1); Console.WriteLine($"Status: {failResult.Status}"); Console.WriteLine($"Error: {failResult.ValidationErrors.First().ErrorMessage}"); // Output: Status: Invalid // Output: Error: Invalid customer ``` -------------------------------- ### Return Not Found Result with Message Source: https://context7.com/ardalis/result/llms.txt Use Result.NotFound() to indicate a resource was not found. Include a message for specific details. This is useful when a requested item does not exist in the system. ```csharp using Ardalis.Result; public class ProductService { private readonly List _products = new() { new Product { Id = 1, Name = "Widget", Price = 9.99m }, new Product { Id = 2, Name = "Gadget", Price = 19.99m } }; public Result GetProductById(int id) { var product = _products.FirstOrDefault(p => p.Id == id); if (product == null) { return Result.NotFound($"Product with ID {id} was not found"); } return Result.Success(product); } public Result GetProductByName(string name) { var product = _products.FirstOrDefault(p => p.Name == name); if (product == null) { // NotFound without message return Result.NotFound(); } return product; // Implicit conversion } } // Usage var service = new ProductService(); var result = service.GetProductById(999); Console.WriteLine($"Status: {result.Status}"); Console.WriteLine($"Errors: {string.Join(", ", result.Errors)}"); // Output: Status: NotFound // Output: Errors: Product with ID 999 was not found ``` -------------------------------- ### Create a Result with Success, Invalid, or NotFound Status Source: https://github.com/ardalis/result/blob/main/docs/index.md This C# method demonstrates how to return a Result from a domain service. It handles cases where data is not found or input validation fails, returning specific Result types. Otherwise, it returns a successful Result with the generated data. ```csharp public Result> GetForecast(ForecastRequestDto model) { if (model.PostalCode == "NotFound") return Result>.NotFound(); // validate model if (model.PostalCode.Length > 10) { return Result>.Invalid(new List { new ValidationError { Identifier = nameof(model.PostalCode), ErrorMessage = "PostalCode cannot exceed 10 characters." } }); } var rng = new Random(); return new Result>(Enumerable.Range(1, 5) .Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)] }) .ToArray()); } ``` -------------------------------- ### Add Ardalis.Result.FluentValidation Package Source: https://github.com/ardalis/result/blob/main/docs/getting-started/index.md Use the dotnet CLI to add the Ardalis.Result.FluentValidation package for FluentValidation integration. ```bash dotnet add package Ardalis.Result.FluentValidation ``` -------------------------------- ### Configure Result Conventions for Swagger/OpenAPI Source: https://context7.com/ardalis/result/llms.txt Use `AddResultConvention()` in `Program.cs` or `Startup.cs` to configure how Ardalis.Result statuses map to HTTP status codes and to generate accurate OpenAPI documentation. This includes customizing mappings and error response formats. ```csharp using Ardalis.Result; using Ardalis.Result.AspNetCore; using System.Net; // In Program.cs or Startup.cs builder.Services.AddControllers(mvcOptions => { // Option 1: Use default mappings mvcOptions.AddDefaultResultConvention(); // Option 2: Customize mappings mvcOptions.AddResultConvention(resultStatusMap => resultStatusMap .AddDefaultMap() // Customize specific status mappings .For(ResultStatus.Ok, HttpStatusCode.OK, options => options .For("POST", HttpStatusCode.Created) // POST returns 201 .For("DELETE", HttpStatusCode.NoContent)) // DELETE returns 204 // Custom error response format .For(ResultStatus.Error, HttpStatusCode.BadRequest, options => options .With((controller, result) => new ProblemDetails { Title = "An error occurred", Detail = string.Join("; ", result.Errors), Status = 400 })) // Remove statuses your API doesn't use .Remove(ResultStatus.Forbidden) .Remove(ResultStatus.Unauthorized) ); }); // This generates proper ProducesResponseType attributes for Swagger: // [ProducesResponseType(typeof(Product), 200)] // [ProducesResponseType(typeof(ValidationProblemDetails), 400)] // [ProducesResponseType(typeof(ProblemDetails), 404)] // etc. ``` -------------------------------- ### MapAsync and BindAsync for Async Result Transformations Source: https://context7.com/ardalis/result/llms.txt Use MapAsync to transform async results and BindAsync to chain multiple async operations. Both methods handle Result unwrapping and error propagation automatically. ```csharp using Ardalis.Result; public class AsyncOrderService { public async Task> GetProductAsync(int productId) { await Task.Delay(10); // Simulate async operation if (productId <= 0) return Result.NotFound(); return Result.Success(new Product { Id = productId, Price = 29.99m }); } public async Task> CalculateTaxAsync(Product product) { await Task.Delay(10); return Result.Success(product.Price * 0.08m); } public async Task> CreateOrderAsync(Product product, decimal tax) { await Task.Delay(10); return Result.Success(new Order { ProductId = product.Id, Total = product.Price + tax }); } } // Usage var service = new AsyncOrderService(); // MapAsync - transform async result var priceResult = await service.GetProductAsync(1) .MapAsync(async product => { var tax = await service.CalculateTaxAsync(product); return product.Price + (tax.IsSuccess ? tax.Value : 0); }); Console.WriteLine($"Total Price: {priceResult.Value:C}"); // Output: Total Price: $32.39 // BindAsync - chain async operations var orderResult = await service.GetProductAsync(1) .BindAsync(async product => { var taxResult = await service.CalculateTaxAsync(product); return taxResult.Bind(tax => Result.Success(new Order { ProductId = product.Id, Total = product.Price + tax })); }); Console.WriteLine($"Order Total: {orderResult.Value.Total:C}"); // Output: Order Total: $32.39 ``` -------------------------------- ### Handle Temporary Unavailability with Result.Unavailable() Source: https://context7.com/ardalis/result/llms.txt Use `Result.Unavailable()` to indicate that a service or resource is temporarily inaccessible. This allows for retries or alternative actions. ```csharp using Ardalis.Result; public class CacheService { private bool _isConnected = false; private readonly Dictionary _cache = new(); public Result GetValue(string key) { if (!_isConnected) { return Result.Unavailable( "Cache service is currently unavailable", "Please retry in a few seconds" ); } if (_cache.TryGetValue(key, out var value)) { return Result.Success(value); } return Result.NotFound(); } public Result DeleteValue(string key) { if (_cache.Remove(key)) { // Successfully deleted, nothing to return return Result.NoContent(); } return Result.NotFound($"Key '{key}' not found"); } } // Usage var service = new CacheService(); var unavailableResult = service.GetValue("mykey"); Console.WriteLine($"Status: {unavailableResult.Status}"); Console.WriteLine($"Errors: {string.Join("; ", unavailableResult.Errors)}"); // Output: Status: Unavailable // Output: Errors: Cache service is currently unavailable; Please retry in a few seconds // After connection established and key exists var deleteResult = service.DeleteValue("existingKey"); Console.WriteLine($"Delete Status: {deleteResult.Status}"); // Output: Delete Status: NoContent ``` -------------------------------- ### Return Not Found Result without Message Source: https://context7.com/ardalis/result/llms.txt Use Result.NotFound() without arguments when the absence of a resource is self-explanatory or detailed error messages are not required. This simplifies the return statement when no specific reason needs to be communicated. ```csharp // NotFound without message return Result.NotFound(); ``` -------------------------------- ### Translate Result to ActionResult with Helper Method Source: https://github.com/ardalis/result/blob/main/docs/index.md This C# code demonstrates using the ToActionResult helper method within an endpoint to translate a Result to an ActionResult. This provides an alternative to using the attribute for result translation. ```csharp [HttpPost("/Forecast/New")] public override ActionResult> Handle(ForecastRequestDto request) { return this.ToActionResult(_weatherService.GetForecast(request)); // alternately // return _weatherService.GetForecast(request).ToActionResult(this); } ``` -------------------------------- ### Translate Result to Minimal API Result Source: https://github.com/ardalis/result/blob/main/docs/index.md This C# code snippet illustrates how to use the ToMinimalApiResult extension method to translate a Result from a domain service into an IResult type suitable for .NET 6+ Minimal APIs. ```csharp app.MapPost("/Forecast/New", (ForecastRequestDto request, WeatherService weatherService) => { return weatherService.GetForecast(request).ToMinimalApiResult(); }) .WithName("NewWeatherForecast"); ``` -------------------------------- ### PagedResult and PagedInfo for Paginated Data Source: https://context7.com/ardalis/result/llms.txt Use PagedResult with PagedInfo to return paginated collections with metadata. Convert regular Result to PagedResult using ToPagedResult() when needed. ```csharp using Ardalis.Result; public class ProductRepository { private readonly List _products = Enumerable.Range(1, 100) .Select(i => new Product { Id = i, Name = $"Product {i}" }) .ToList(); public PagedResult> GetProducts(int pageNumber, int pageSize) { var totalRecords = _products.Count; var totalPages = (long)Math.Ceiling(totalRecords / (double)pageSize); var items = _products .Skip((pageNumber - 1) * pageSize) .Take(pageSize) .ToList(); var pagedInfo = new PagedInfo(pageNumber, pageSize, totalPages, totalRecords); return new PagedResult>(pagedInfo, items); } } // Usage var repo = new ProductRepository(); var result = repo.GetProducts(pageNumber: 2, pageSize: 10); Console.WriteLine($"Page {result.PagedInfo.PageNumber} of {result.PagedInfo.TotalPages}"); Console.WriteLine($"Showing {result.Value.Count} of {result.PagedInfo.TotalRecords} total records"); Console.WriteLine($"First item on page: {result.Value.First().Name}"); // Output: Page 2 of 10 // Output: Showing 10 of 100 total records // Output: First item on page: Product 11 // Convert regular Result to PagedResult var regularResult = Result.Success(new List { new Product { Id = 1, Name = "Test" } }); var pagedFromRegular = regularResult.ToPagedResult(new PagedInfo(1, 10, 1, 1)); ``` -------------------------------- ### Minimal API Integration with ToMinimalApiResult Source: https://context7.com/ardalis/result/llms.txt Converts Ardalis.Result types to .NET 6+ Minimal API IResult types. Handles various Result statuses by mapping them to appropriate HTTP status codes. ```csharp using Ardalis.Result; using Ardalis.Result.AspNetCore; var builder = WebApplication.CreateBuilder(args); builder.Services.AddScoped(); var app = builder.Build(); // GET endpoint app.MapGet("/weather/{postalCode}", (string postalCode, WeatherService service) => { return service.GetForecast(postalCode).ToMinimalApiResult(); // Returns: 200 OK with data, 404 Not Found, 400 Bad Request, etc. }); // POST endpoint app.MapPost("/weather", (ForecastRequest request, WeatherService service) => { return service.CreateForecast(request).ToMinimalApiResult(); // Created results return 201 with value }); // PUT endpoint app.MapPut("/weather/{id}", (int id, ForecastRequest request, WeatherService service) => { return service.UpdateForecast(id, request).ToMinimalApiResult(); }); // DELETE endpoint app.MapDelete("/weather/{id}", (int id, WeatherService service) => { return service.DeleteForecast(id).ToMinimalApiResult(); // NoContent returns 204 }); app.Run(); // Status code mappings for Minimal API: // Ok -> Results.Ok(value) // Created -> Results.Created("", value) // NotFound -> Results.NotFound() or Results.NotFound(ProblemDetails) // Invalid -> Results.BadRequest(validationErrors) // Error -> Results.UnprocessableEntity(ProblemDetails) // Forbidden -> Results.Forbid() or Results.Problem with 403 // Unauthorized -> Results.Unauthorized() or Results.Problem with 401 // Conflict -> Results.Conflict(ProblemDetails) // CriticalError -> Results.Problem with 500 // Unavailable -> Results.Problem with 503 // NoContent -> Results.NoContent() ``` -------------------------------- ### Specify Expected Failures for an Endpoint Source: https://github.com/ardalis/result/blob/main/README.md Marks an endpoint with `[ExpectedFailures]` to indicate specific `ResultStatus` values that can be returned, aiding API metadata generation. ```csharp ```csharp [TranslateResultToActionResult()] [ExpectedFailures(ResultStatus.NotFound, ResultStatus.Invalid)] [HttpDelete("Remove/{id}")] public Result RemovePerson(int id) { // Method logic } ``` ``` -------------------------------- ### Specify Expected Failures for an Endpoint Source: https://github.com/ardalis/result/blob/main/docs/index.md Marks an endpoint with expected failure ResultStatus values using the ExpectedFailures attribute. ```csharp [TranslateResultToActionResult()] [ExpectedFailures(ResultStatus.NotFound, ResultStatus.Invalid)] [HttpDelete("Remove/{id}")] public Result RemovePerson(int id) { // Method logic } ``` -------------------------------- ### Customize ResultConvention Mapping Source: https://github.com/ardalis/result/blob/main/README.md Customizes the mapping of `ResultStatus` to HTTP status codes within the `ResultConvention` configuration. ```csharp ```csharp services.AddControllers(mvcOptions => mvcOptions .AddResultConvention(resultStatusMap => resultStatusMap .AddDefaultMap() .For(ResultStatus.Ok, HttpStatusCode.OK, resultStatusOptions => resultStatusOptions .For("POST", HttpStatusCode.Created) .For("DELETE", HttpStatusCode.NoContent)) .For(ResultStatus.Error, HttpStatusCode.InternalServerError) )); ``` ``` -------------------------------- ### Add Default Result Convention Source: https://github.com/ardalis/result/blob/main/README.md Configures ASP.NET Core controllers to add default `[ProducesResponseType]` attributes for known `ResultStatus` types when `[TranslateResultToActionResult]` is used. ```csharp ```csharp services.AddControllers(mvcOptions => mvcOptions.AddDefaultResultConvention()); ``` ``` -------------------------------- ### Configure Result Body for Errors Source: https://github.com/ardalis/result/blob/main/docs/index.md Configures the content returned in the response body for specific error ResultStatus values, such as validation errors. ```csharp services.AddControllers(mvcOptions => mvcOptions .AddResultConvention(resultStatusMap => resultStatusMap .AddDefaultMap() .For(ResultStatus.Error, HttpStatusCode.BadRequest, resultStatusOptions => resultStatusOptions .With((ctrlr, result) => string.Join("\r\n", result.ValidationErrors))) )); ``` -------------------------------- ### Controller Action Result Translation with Extension Method Source: https://github.com/ardalis/result/blob/main/docs/getting-started/quick-start-guide.md Use extension methods within controller actions to translate Result types to `ActionResult`. The `this.ToActionResult()` method can be called on the result directly, or `result.ToActionResult(this)` can be used when passing the controller instance. ```csharp /// /// This uses an extension method to convert to an ActionResult /// /// [HttpPost("/Person/Create/")] public override ActionResult Handle(CreatePersonRequestDto request) { if (DateTime.Now.Second % 2 == 0) // just so we can show both versions { // Extension method on ControllerBase return this.ToActionResult(_personService.Create(request.FirstName, request.LastName)); // 👈 } Result result = _personService.Create(request.FirstName, request.LastName); // Extension method on a Result instance (passing in ControllerBase instance) return result.ToActionResult(this); // 👈 } ``` -------------------------------- ### Handle Edit Conflicts with Result.Conflict() Source: https://context7.com/ardalis/result/llms.txt Use `Result.Conflict()` when a requested edit operation fails due to concurrent modifications. Provide a user-facing message and an optional technical detail. ```csharp using Ardalis.Result; public class InventoryService { private readonly Dictionary _inventory = new() { { 1, 10 }, { 2, 5 } }; public Result ReserveStock(int productId, int quantity) { if (!_inventory.ContainsKey(productId)) { return Result.NotFound($"Product {productId} not found"); } var available = _inventory[productId]; if (quantity > available) { return Result.Conflict( $"Requested {quantity} units but only {available} available", "Stock has changed since your request" ); } _inventory[productId] -= quantity; return Result.Success(_inventory[productId]); } public Result SyncWithWarehouse() { try { // Simulate critical infrastructure failure throw new InvalidOperationException("Database connection lost"); } catch (Exception ex) { return Result.CriticalError( "Failed to sync with warehouse", $"Technical details: {ex.Message}" ); } } } // Usage var service = new InventoryService(); var conflictResult = service.ReserveStock(2, 100); Console.WriteLine($"Status: {conflictResult.Status}"); foreach (var error in conflictResult.Errors) Console.WriteLine($" - {error}"); // Output: Status: Conflict // Output: - Requested 100 units but only 5 available // Output: - Stock has changed since your request var criticalResult = service.SyncWithWarehouse(); Console.WriteLine($"Status: {criticalResult.Status}"); // Output: Status: CriticalError ``` -------------------------------- ### Return Error Result from Exception Source: https://context7.com/ardalis/result/llms.txt Catch exceptions and return them as Result.Error() to maintain a consistent return type. This ensures that all operational failures, whether anticipated or unexpected, are handled uniformly. ```csharp catch (Exception ex) { return Result.Error($"Unexpected error: {ex.Message}"); } ``` -------------------------------- ### Customize Result Convention Source: https://github.com/ardalis/result/blob/main/docs/index.md Customizes the ResultConvention behavior by providing a custom result status map. ```csharp services.AddControllers(mvcOptions => mvcOptions .AddResultConvention(resultStatusMap => resultStatusMap .AddDefaultMap() )); ``` -------------------------------- ### FluentValidation Integration with AsErrors() Source: https://context7.com/ardalis/result/llms.txt Converts FluentValidation validation results into Ardalis.Result validation errors. This allows for consistent error handling across different validation libraries. ```csharp using Ardalis.Result; using Ardalis.Result.FluentValidation; using FluentValidation; public class Person { public string FirstName { get; set; } public string LastName { get; set; } public string Email { get; set; } } public class PersonValidator : AbstractValidator { public PersonValidator() { RuleFor(p => p.FirstName) .NotEmpty().WithMessage("First name is required") .MaximumLength(50).WithMessage("First name cannot exceed 50 characters"); RuleFor(p => p.LastName) .NotEmpty().WithMessage("Last name is required"); RuleFor(p => p.Email) .NotEmpty().WithMessage("Email is required") .EmailAddress().WithMessage("Invalid email format") .WithSeverity(Severity.Warning); // Severity is preserved } } public class PersonService { public Result CreatePerson(Person person) { var validator = new PersonValidator(); var validationResult = validator.Validate(person); if (!validationResult.IsValid) { // Convert FluentValidation errors to Result validation errors return Result.Invalid(validationResult.AsErrors()); } // Save person... return Result.Success(person); } } // Usage var service = new PersonService(); var result = service.CreatePerson(new Person { FirstName = "", Email = "invalid" }); if (result.Status == ResultStatus.Invalid) { foreach (var error in result.ValidationErrors) { Console.WriteLine($"[{error.Identifier}] {error.ErrorMessage} (Severity: {error.Severity})"); } } // Output: [FirstName] First name is required (Severity: Error) // Output: [LastName] Last name is required (Severity: Error) // Output: [Email] Invalid email format (Severity: Warning) ``` -------------------------------- ### Add Default Result Convention Source: https://github.com/ardalis/result/blob/main/docs/index.md Adds default ProducesResponseType attributes for known ResultStatus to endpoints marked with [TranslateResultToActionResult]. ```csharp services.AddControllers(mvcOptions => mvcOptions.AddDefaultResultConvention()); ``` -------------------------------- ### Transform Result Value with Map Source: https://context7.com/ardalis/result/llms.txt Use Map to transform the success value of a Result to a different type, such as converting a domain entity to a DTO. It preserves the Result's status (Ok, NotFound, Invalid, etc.) and does not execute the transformation function if the Result is not successful. ```csharp using Ardalis.Result; public record Customer(int Id, string Name, string Email, DateTime CreatedAt); public record CustomerDto(int Id, string Name); public class CustomerService { public Result GetCustomer(int id) { if (id <= 0) return Result.Invalid(new ValidationError("id", "ID must be positive")); if (id == 999) return Result.NotFound(); return Result.Success(new Customer(id, "John Doe", "john@example.com", DateTime.UtcNow)); } } // Usage - Map transforms the value while preserving Result status var service = new CustomerService(); // Successful case - Map transforms Customer to CustomerDto var successResult = service.GetCustomer(1) .Map(customer => new CustomerDto(customer.Id, customer.Name)); Console.WriteLine($"Status: {successResult.Status}"); Console.WriteLine($"Value: Id={successResult.Value.Id}, Name={successResult.Value.Name}"); // Output: Status: Ok // Output: Value: Id=1, Name=John Doe // Error case - Map preserves the error status without invoking the transform var notFoundResult = service.GetCustomer(999) .Map(customer => new CustomerDto(customer.Id, customer.Name)); Console.WriteLine($"Status: {notFoundResult.Status}"); // Output: Status: NotFound // Invalid case - ValidationErrors are preserved var invalidResult = service.GetCustomer(-1) .Map(customer => new CustomerDto(customer.Id, customer.Name)); Console.WriteLine($"Status: {invalidResult.Status}"); Console.WriteLine($"Validation Error: {invalidResult.ValidationErrors.First().ErrorMessage}"); // Output: Status: Invalid // Output: Validation Error: ID must be positive ``` -------------------------------- ### Convert Ardalis.Result to ASP.NET Core ActionResult Source: https://context7.com/ardalis/result/llms.txt Use the `ToActionResult()` extension method to convert Ardalis.Result types to ASP.NET Core `ActionResult` types. This method automatically maps Result statuses to appropriate HTTP status codes. ```csharp using Ardalis.Result; using Ardalis.Result.AspNetCore; using Microsoft.AspNetCore.Mvc; [ApiController] [Route("api/[controller]")] public class CustomersController : ControllerBase { private readonly CustomerService _customerService; public CustomersController(CustomerService customerService) { _customerService = customerService; } [HttpGet("{id}")] public ActionResult GetCustomer(int id) { return _customerService.GetCustomer(id) .Map(c => new CustomerDto(c.Id, c.Name)) .ToActionResult(this); // Status mappings: // Ok -> 200 OK with value // NotFound -> 404 Not Found // Invalid -> 400 Bad Request with validation errors // Forbidden -> 403 Forbidden // Unauthorized -> 401 Unauthorized // Error -> 422 Unprocessable Entity // Conflict -> 409 Conflict // CriticalError -> 500 Internal Server Error // Unavailable -> 503 Service Unavailable // NoContent -> 204 No Content } [HttpPost] public ActionResult CreateCustomer([FromBody] CreateCustomerRequest request) { return _customerService.CreateCustomer(request.Name, request.Email) .Map(c => new CustomerDto(c.Id, c.Name)) .ToActionResult(this); // Created results return 201 with Location header } [HttpDelete("{id}")] public ActionResult DeleteCustomer(int id) { return _customerService.DeleteCustomer(id) .ToActionResult(this); // NoContent returns 204, NotFound returns 404 } } ``` -------------------------------- ### Return Unauthorized Result Source: https://context7.com/ardalis/result/llms.txt Use Result.Unauthorized() when a user is not authenticated and therefore cannot access a resource. This is typically the first check in authorization logic for unauthenticated requests. ```csharp using Ardalis.Result; public class DocumentService { public Result GetDocument(int documentId, User currentUser) { if (currentUser == null) { return Result.Unauthorized("Authentication required to access documents"); } var document = FindDocument(documentId); if (document == null) { return Result.NotFound(); } if (!currentUser.HasAccessTo(document)) { return Result.Forbidden("You do not have permission to access this document"); } return Result.Success(document); } public Result DeleteDocument(int documentId, User currentUser) { if (!currentUser.IsAdmin) { return Result.Forbidden("Only administrators can delete documents"); } // Delete document... return Result.Success(); } } // Usage var service = new DocumentService(); // Unauthorized scenario var result1 = service.GetDocument(1, null); Console.WriteLine($"Status: {result1.Status}, Error: {result1.Errors.FirstOrDefault()}"); // Output: Status: Unauthorized, Error: Authentication required to access documents // Forbidden scenario var regularUser = new User { IsAdmin = false }; var result2 = service.DeleteDocument(1, regularUser); Console.WriteLine($"Status: {result2.Status}, Error: {result2.Errors.FirstOrDefault()}"); // Output: Status: Forbidden, Error: Only administrators can delete documents ``` -------------------------------- ### Automatic Result to ActionResult Conversion with Attribute Source: https://context7.com/ardalis/result/llms.txt Apply the `[TranslateResultToActionResult]` attribute to controller actions to automatically convert Ardalis.Result return types to `ActionResult`. This eliminates the need for manual `ToActionResult()` calls. ```csharp using Ardalis.Result; using Ardalis.Result.AspNetCore; using Microsoft.AspNetCore.Mvc; [ApiController] [Route("api/[controller]")] public class ProductsController : ControllerBase { private readonly ProductService _productService; public ProductsController(ProductService productService) { _productService = productService; } // The attribute automatically converts Result to ActionResult [TranslateResultToActionResult] [HttpGet("{id}")] public Result GetProduct(int id) { return _productService.GetProduct(id); // No need to call ToActionResult() - the filter handles it } // Specify expected failure types for OpenAPI documentation [TranslateResultToActionResult] [ExpectedFailures(ResultStatus.NotFound, ResultStatus.Invalid)] [HttpPut("{id}")] public Result UpdateProduct(int id, [FromBody] UpdateProductRequest request) { return _productService.UpdateProduct(id, request); } [TranslateResultToActionResult] [HttpDelete("{id}")] public Result DeleteProduct(int id) { return _productService.DeleteProduct(id); } } ``` -------------------------------- ### Refactor Method to Return Result Source: https://github.com/ardalis/result/blob/main/docs/getting-started/index.md Refactor a method that previously threw an exception for a missing record to return a Result.NotFound type instead. This avoids using exceptions for control flow. ```csharp public Result Remove(int id) { if (!Exists(id)) { return Result.NotFound($ ``` ```csharp // Remove the record return Result.Success(); } ``` -------------------------------- ### Return Error Result with ErrorList and Correlation ID Source: https://context7.com/ardalis/result/llms.txt Use Result.Error() with an ErrorList to provide multiple error messages and an optional correlation ID. This is useful for complex operations where several issues might arise or for tracking requests across systems. ```csharp var errorList = new ErrorList( new[] { "Payment gateway unavailable", "Please try again later" }, correlationId: Guid.NewGuid().ToString() ); return Result.Error(errorList); ``` -------------------------------- ### Customize Result Status to HttpStatusCode Mapping Source: https://github.com/ardalis/result/blob/main/docs/index.md Modifies the mapping between ResultStatus and HttpStatusCode, including specific mappings for HTTP methods. ```csharp services.AddControllers(mvcOptions => mvcOptions .AddResultConvention(resultStatusMap => resultStatusMap .AddDefaultMap() .For(ResultStatus.Ok, HttpStatusCode.OK, resultStatusOptions => resultStatusOptions .For("POST", HttpStatusCode.Created) .For("DELETE", HttpStatusCode.NoContent)) .For(ResultStatus.Error, HttpStatusCode.InternalServerError) )); ``` -------------------------------- ### Return Error Result with Single Message Source: https://context7.com/ardalis/result/llms.txt Use Result.Error() to signal a general execution error. This method can accept a single string message to describe the problem. It's suitable for straightforward error conditions. ```csharp using Ardalis.Result; public class PaymentService { public Result ProcessPayment(PaymentRequest request) { try { // Simulate payment processing failure if (request.Amount <= 0) { return Result.Error("Invalid payment amount"); } // Simulate external service error with correlation ID if (request.CardNumber.StartsWith("0000")) { var errorList = new ErrorList( new[] { "Payment gateway unavailable", "Please try again later" }, correlationId: Guid.NewGuid().ToString() ); return Result.Error(errorList); } return Result.Success(new PaymentConfirmation { TransactionId = "TXN123" }); } catch (Exception ex) { return Result.Error($"Unexpected error: {ex.Message}"); } } } // Usage var service = new PaymentService(); var result = service.ProcessPayment(new PaymentRequest { CardNumber = "0000111122223333", Amount = 100 }); if (result.Status == ResultStatus.Error) { Console.WriteLine($"Errors: {string.Join("; ", result.Errors)}"); Console.WriteLine($"Correlation ID: {result.CorrelationId}"); } // Output: Errors: Payment gateway unavailable; Please try again later // Output: Correlation ID: 550e8400-e29b-41d4-a716-446655440000 ``` -------------------------------- ### Translate Result to ActionResult with Attribute Source: https://github.com/ardalis/result/blob/main/docs/index.md This C# code snippet shows how to use the [TranslateResultToActionResult] attribute on an API endpoint. This attribute automatically converts the Result return type of the method into an appropriate ActionResult, simplifying controller logic. ```csharp [TranslateResultToActionResult] [HttpPost("Create")] public Result> CreateForecast([FromBody]ForecastRequestDto model) { return _weatherService.GetForecast(model); } ``` -------------------------------- ### Customize Failure Result Content Source: https://github.com/ardalis/result/blob/main/README.md Configures `ResultConvention` to return specific content from a `Result` object in case of failure, such as joining validation errors. ```csharp ```csharp services.AddControllers(mvcOptions => mvcOptions .AddResultConvention(resultStatusMap => resultStatusMap .AddDefaultMap() .For(ResultStatus.Error, HttpStatusCode.BadRequest, resultStatusOptions => resultStatusOptions .With((ctrlr, result) => string.Join("\r\n", result.ValidationErrors))) )); ``` ```