-
Notifications
You must be signed in to change notification settings - Fork 0
Add README with usage examples and API docs links #70
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
f5e2226
Add README with usage examples, API reference links, and feature table
Chris-Wolfgang 1e98896
Add real-world examples: database access, repository, Web API, chaining
Chris-Wolfgang 3c7c555
Address Copilot review: remove stale link, clarify RunAsync cancellation
Chris-Wolfgang File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,363 @@ | ||
| # Wolfgang.TryPattern | ||
|
|
||
| [](https://www.nuget.org/packages/Wolfgang.TryPattern) | ||
| [](LICENSE) | ||
|
|
||
| A lightweight .NET library that provides a Try/Result pattern for executing actions and functions with automatic exception handling. Instead of try/catch blocks scattered throughout your code, wrap operations in `Try.Run()` and get back a `Result` indicating success or failure. | ||
|
|
||
| - **GitHub Repository:** [https://github.com/Chris-Wolfgang/Try-Pattern](https://github.com/Chris-Wolfgang/Try-Pattern) | ||
| - **API Documentation:** [https://chris-wolfgang.github.io/Try-Pattern/](https://chris-wolfgang.github.io/Try-Pattern/) | ||
| - **API Reference:** [https://chris-wolfgang.github.io/Try-Pattern/api/](https://chris-wolfgang.github.io/Try-Pattern/api/) | ||
| - **Contributing Guide:** [CONTRIBUTING.md](CONTRIBUTING.md) | ||
|
|
||
| --- | ||
|
|
||
| ## Installation | ||
|
|
||
| ### Via .NET CLI | ||
|
|
||
| ```bash | ||
| dotnet add package Wolfgang.TryPattern | ||
| ``` | ||
|
|
||
| ### Via Package Manager Console | ||
|
|
||
| ```powershell | ||
| Install-Package Wolfgang.TryPattern | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Quick Start | ||
|
|
||
| ### Execute an action safely | ||
|
|
||
| ```csharp | ||
| using Wolfgang.TryPattern; | ||
|
|
||
| var result = Try.Run(() => File.Delete("temp.txt")); | ||
|
|
||
| if (result.Succeeded) | ||
| { | ||
| Console.WriteLine("File deleted."); | ||
| } | ||
| else | ||
| { | ||
| Console.WriteLine($"Failed: {result.ErrorMessage}"); | ||
| } | ||
| ``` | ||
|
|
||
| ### Execute a function and get the return value | ||
|
|
||
| ```csharp | ||
| var result = Try.Run(() => int.Parse("42")); | ||
|
|
||
| if (result.Succeeded) | ||
| { | ||
| Console.WriteLine($"Parsed value: {result.Value}"); | ||
| } | ||
| else | ||
| { | ||
| Console.WriteLine($"Parse failed: {result.ErrorMessage}"); | ||
| } | ||
| ``` | ||
|
|
||
| ### Async support | ||
|
|
||
| ```csharp | ||
| var result = await Try.RunAsync(async () => | ||
| { | ||
| var response = await httpClient.GetStringAsync("https://example.com"); | ||
| return response; | ||
| }); | ||
|
|
||
| if (result.Failed) | ||
| { | ||
| Console.WriteLine($"Request failed: {result.ErrorMessage}"); | ||
| } | ||
| ``` | ||
|
|
||
| ### Cancellation support | ||
|
|
||
| `Try.RunAsync(Action, CancellationToken)` runs a synchronous action on the thread pool via `Task.Run`. The token cancels the task before it starts; once running, cancellation is cooperative (your code must check the token). `OperationCanceledException` is always rethrown, never captured as a `Result`. | ||
|
|
||
| ```csharp | ||
| var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); | ||
|
|
||
| var result = await Try.RunAsync(() => | ||
| { | ||
| foreach (var item in largeDataSet) | ||
| { | ||
| cts.Token.ThrowIfCancellationRequested(); | ||
| Process(item); | ||
| } | ||
| }, cts.Token); | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Features | ||
|
|
||
| | Feature | Description | | ||
| |---------|-------------| | ||
| | `Try.Run(Action)` | Execute an action, return `Result` | | ||
| | `Try.Run<T>(Func<T>)` | Execute a function, return `Result<T>` with the value | | ||
| | `Try.RunAsync(Action, CancellationToken)` | Run a synchronous action on the thread pool with cooperative cancellation | | ||
| | `Try.RunAsync<T>(Func<Task<T>>, CancellationToken)` | Async function execution with cancellation support | | ||
| | `Result.Success()` | Create a successful result | | ||
|
Chris-Wolfgang marked this conversation as resolved.
|
||
| | `Result.Failure(message)` | Create a failed result with an error message | | ||
| | `Result<T>.Success(value)` | Create a successful result with a value | | ||
| | `Result<T>.Failure(message)` | Create a failed result | | ||
| | `Result.Flatten(results)` | Combine multiple results into one | | ||
| | `Result.AnyFailed(results)` | Check if any results failed | | ||
| | `Result.AllSucceeded(results)` | Check if all results succeeded | | ||
|
|
||
|
Chris-Wolfgang marked this conversation as resolved.
|
||
| ### Result Properties | ||
|
|
||
| | Property | Description | | ||
| |----------|-------------| | ||
| | `Succeeded` | `true` if the operation completed successfully | | ||
| | `Failed` | `true` if the operation failed (inverse of `Succeeded`) | | ||
| | `ErrorMessage` | The error message if failed, empty string if succeeded | | ||
| | `Value` | (Generic only) The return value if succeeded, throws `InvalidOperationException` if failed | | ||
|
|
||
|
Chris-Wolfgang marked this conversation as resolved.
|
||
| --- | ||
|
|
||
| ## Combining Results | ||
|
|
||
| ```csharp | ||
| var r1 = Try.Run(() => ValidateName(name)); | ||
| var r2 = Try.Run(() => ValidateEmail(email)); | ||
| var r3 = Try.Run(() => ValidateAge(age)); | ||
|
|
||
| // Flatten into a single result | ||
| var combined = Result.Flatten(r1, r2, r3); | ||
|
|
||
| if (combined.Failed) | ||
| { | ||
| // ErrorMessage contains all failures separated by newlines | ||
| Console.WriteLine(combined.ErrorMessage); | ||
| } | ||
|
|
||
| // Or check individually | ||
| if (Result.AnyFailed(r1, r2, r3)) | ||
| { | ||
| Console.WriteLine("At least one validation failed."); | ||
| } | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Real-World Examples | ||
|
|
||
| ### Database access with Try.Run | ||
|
|
||
| Wrap database calls to get a clean `Result` instead of scattered try/catch: | ||
|
|
||
| ```csharp | ||
| public Result<Customer> GetCustomerById(int id) | ||
| { | ||
| return Try.Run(() => | ||
| { | ||
| using var connection = new SqlConnection(connectionString); | ||
| connection.Open(); | ||
| using var command = new SqlCommand("SELECT Id, Name, Email FROM Customers WHERE Id = @Id", connection); | ||
| command.Parameters.AddWithValue("@Id", id); | ||
|
|
||
| using var reader = command.ExecuteReader(); | ||
| if (!reader.Read()) | ||
| throw new InvalidOperationException($"Customer {id} not found."); | ||
|
|
||
| return new Customer | ||
| { | ||
| Id = reader.GetInt32(0), | ||
| Name = reader.GetString(1), | ||
| Email = reader.GetString(2) | ||
| }; | ||
| }); | ||
| } | ||
|
|
||
| // Usage | ||
| var result = GetCustomerById(42); | ||
| if (result.Succeeded) | ||
| { | ||
| Console.WriteLine($"Found: {result.Value.Name}"); | ||
| } | ||
| else | ||
| { | ||
| Console.WriteLine($"Lookup failed: {result.ErrorMessage}"); | ||
| } | ||
| ``` | ||
|
|
||
| ### Async database access | ||
|
|
||
| ```csharp | ||
| public async Task<Result<List<Order>>> GetRecentOrdersAsync(int customerId) | ||
| { | ||
| return await Try.RunAsync(async () => | ||
| { | ||
| await using var connection = new SqlConnection(connectionString); | ||
| await connection.OpenAsync(); | ||
| // ... query and return orders | ||
| return orders; | ||
| }); | ||
| } | ||
| ``` | ||
|
|
||
| ### Using Result as a repository return type | ||
|
|
||
| `Result` and `Result<T>` work well as return types from repositories and service layers. You don't need `Try.Run()` to create them -- use the static factory methods directly: | ||
|
|
||
| ```csharp | ||
| public class CustomerRepository | ||
| { | ||
| public Result<Customer> GetById(int id) | ||
| { | ||
| var customer = dbContext.Customers.Find(id); | ||
|
|
||
| return customer is not null | ||
| ? Result<Customer>.Success(customer) | ||
| : Result<Customer>.Failure($"Customer with ID {id} not found."); | ||
| } | ||
|
|
||
| public Result Save(Customer customer) | ||
| { | ||
| if (string.IsNullOrWhiteSpace(customer.Name)) | ||
| return Result.Failure("Customer name is required."); | ||
|
|
||
| if (string.IsNullOrWhiteSpace(customer.Email)) | ||
| return Result.Failure("Customer email is required."); | ||
|
|
||
| dbContext.Customers.Update(customer); | ||
| dbContext.SaveChanges(); | ||
| return Result.Success(); | ||
| } | ||
|
|
||
| public Result Delete(int id) | ||
| { | ||
| var customer = dbContext.Customers.Find(id); | ||
| if (customer is null) | ||
| return Result.Failure($"Customer with ID {id} not found."); | ||
|
|
||
| dbContext.Customers.Remove(customer); | ||
| dbContext.SaveChanges(); | ||
| return Result.Success(); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### Using Result in a Web API controller | ||
|
|
||
| Return `Result` from your service layer and map it to HTTP responses: | ||
|
|
||
| ```csharp | ||
| [ApiController] | ||
| [Route("api/[controller]")] | ||
| public class CustomersController : ControllerBase | ||
| { | ||
| private readonly CustomerRepository _repository; | ||
|
|
||
| public CustomersController(CustomerRepository repository) => _repository = repository; | ||
|
|
||
| [HttpGet("{id}")] | ||
| public IActionResult GetById(int id) | ||
| { | ||
| var result = _repository.GetById(id); | ||
|
|
||
| return result.Succeeded | ||
| ? Ok(result.Value) | ||
| : NotFound(new { error = result.ErrorMessage }); | ||
| } | ||
|
|
||
| [HttpPut("{id}")] | ||
| public IActionResult Update(int id, CustomerDto dto) | ||
| { | ||
| var lookup = _repository.GetById(id); | ||
| if (lookup.Failed) | ||
| return NotFound(new { error = lookup.ErrorMessage }); | ||
|
|
||
| lookup.Value.Name = dto.Name; | ||
| lookup.Value.Email = dto.Email; | ||
|
|
||
| var saveResult = _repository.Save(lookup.Value); | ||
|
|
||
| return saveResult.Succeeded | ||
| ? NoContent() | ||
| : BadRequest(new { error = saveResult.ErrorMessage }); | ||
| } | ||
|
|
||
| [HttpDelete("{id}")] | ||
| public IActionResult Delete(int id) | ||
| { | ||
| var result = _repository.Delete(id); | ||
|
|
||
| return result.Succeeded | ||
| ? NoContent() | ||
| : NotFound(new { error = result.ErrorMessage }); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### Chaining operations with validation | ||
|
|
||
| ```csharp | ||
| public Result<Order> PlaceOrder(OrderRequest request) | ||
| { | ||
| // Validate | ||
| var validation = Result.Flatten( | ||
| ValidateCustomer(request.CustomerId), | ||
| ValidateItems(request.Items), | ||
| ValidatePayment(request.PaymentMethod) | ||
| ); | ||
|
|
||
| if (validation.Failed) | ||
| return Result<Order>.Failure(validation.ErrorMessage); | ||
|
|
||
| // Execute | ||
| return Try.Run(() => | ||
| { | ||
| var order = orderService.Create(request); | ||
| emailService.SendConfirmation(order); | ||
| return order; | ||
| }); | ||
| } | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Target Frameworks | ||
|
|
||
| | Framework | Version | | ||
| |-----------|---------| | ||
| | .NET Framework | 4.6.2+ | | ||
| | .NET Standard | 2.0 | | ||
| | .NET | 8.0, 10.0 | | ||
|
|
||
|
Chris-Wolfgang marked this conversation as resolved.
|
||
| --- | ||
|
|
||
| ## Building from Source | ||
|
|
||
| ```bash | ||
| # Clone the repository | ||
| git clone https://github.com/Chris-Wolfgang/Try-Pattern.git | ||
| cd Try-Pattern | ||
|
|
||
| # Restore and build | ||
| dotnet restore | ||
| dotnet build --configuration Release | ||
|
|
||
| # Run tests | ||
| dotnet test --configuration Release | ||
|
|
||
| # Format code | ||
| dotnet format | ||
|
|
||
| # Verify formatting | ||
| dotnet format --verify-no-changes | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## License | ||
|
|
||
| This project is licensed under the [MIT License](LICENSE). | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.