This repository demonstrates the true potential and power of guard clauses in .NET, showcasing how they can be used to create robust, maintainable, and professional-grade applications. Guard clauses are essential for defensive programming, ensuring that your code fails fast when encountering invalid input or unexpected conditions.
Unlike basic implementations, this project shows how guard clauses can be leveraged in a full-featured web API with Swagger documentation, demonstrating their real-world applicability, and includes advanced patterns like fluent APIs, custom exceptions, attribute-based validation, and extension methods.
- Null Checks:
NotNull<T>()for reference types - String Validation:
NotNullOrEmpty()for strings - Collection Validation:
NotNullOrEmptyList<T>()for collections - Range Validation:
InRange<T>()for comparable values - Numeric Validation:
NotNegative(),NotZeroOrNegative()for numeric types - Pattern Matching:
MatchesRegex()for string pattern validation - Default Value Checks:
NotDefault<T>()for value types
- Fluent API: Chain multiple validations together for a clean, readable syntax
- Custom Exceptions: Use domain-specific exceptions for better error handling
- Attribute-Based Validation: Decorate properties with validation attributes
- Async Guard Clauses: Validate data asynchronously for external service checks
- Composite Guards: Complex business rule validations
- Extension Methods: Extend common .NET types with guard clause functionality
- RESTful API Design: Clean controllers with proper HTTP status codes
- Swagger Integration: Full API documentation with NSwag
- Real-World Examples: Order and Customer management scenarios
- Comprehensive Error Handling: Consistent error responses using guard clauses
- Return Values: Guard clauses return validated values for fluent APIs
- Generic Constraints: Type-safe validation with generics
- Expression-Based Parameter Names: Automatic parameter name detection
- Performance Optimized: Minimal overhead with maximum protection
π¦ GuardClauses
β£ π AdvancedGuards # Advanced guard clause implementations
β β£ π Attributes # Attribute-based validation
β β£ π Examples # Examples of advanced guard clause usage
β β£ π Exceptions # Custom exception types
β β£ π AsyncGuards.cs # Asynchronous guard clauses
β β£ π CollectionGuards.cs # Collection extension methods
β β£ π CompositeGuards.cs # Complex business rule validations
β β£ π DateTimeGuards.cs # DateTime extension methods
β β£ π FluentGuard.cs # Fluent API implementation
β β£ π FluentGuardWithCustomExceptions.cs # Fluent API with custom exceptions
β β£ π Guard.cs # Factory class for fluent guards
β β£ π GuardWithCustomExceptions.cs # Factory class for fluent guards with custom exceptions
β β£ π NumericGuards.cs # Numeric extension methods
β β£ π StringGuards.cs # String extension methods
β β π ObjectValidator.cs # Attribute-based object validator
β£ π Controllers # REST API controllers demonstrating guard clause usage
β£ π UnitTests # NUnit tests for guard clauses
β£ π Ensure.cs # Basic guard clause implementations
β£ π Order.cs # Order model with validation in constructor
β£ π Customer.cs # Customer model with validation in constructor
β£ π Program.cs # Web application entry point with Swagger configuration
β£ π GuardClauses.csproj # .NET web API project with NSwag package reference
β π TestGuardClauses.cs # Console application to test guard clause functionality
Ensure you have the following installed:
- .NET 9.0 SDK or later
- A modern C# IDE (e.g., Visual Studio, Visual Studio Code, or JetBrains Rider)
git clone https://github.com/MrEshboboyev/guard-clauses.git
cd guard-clausesdotnet run --project src/GuardClausesNavigate to http://localhost:5000/swagger to view the interactive API documentation.
dotnet run --project GuardClauses.Demodotnet test// Fluent API with method chaining
var email = Guard.For("user@example.com")
.NotNull()
.NotNullOrEmpty()
.MatchesRegex(@"^[^@\s]+@[^@\s]+\.[^@\s]+$")
.Value;
var age = Guard.For(25)
.NotNegative()
.InRange(0, 120)
.Value;public class ValidatedCustomer
{
[NotNull]
[NotEmpty]
public string Name { get; set; } = string.Empty;
[Range(13, 120)]
public int Age { get; set; }
[NotNull]
[NotEmpty]
public string Email { get; set; } = string.Empty;
}
// Validate the object
var customer = new ValidatedCustomer("John Doe", 30, "john@example.com");
ObjectValidator.Validate(customer);// String extensions
var email = "user@example.com"
.NotNullOrWhiteSpace()
.HasMinimumLength(5)
.HasMaximumLength(100)
.ValidEmail();
// Numeric extensions
var age = 25.Positive().Between(18, 65);
var amount = 99.99m.NonNegative().MaxDecimalPlaces(2);
// DateTime extensions
var futureDate = DateTime.Now.AddDays(10).NotInPast().Weekday();// Validate data asynchronously (e.g., checking if user exists in database)
var userId = await AsyncGuards.SatisfiesAsync(
123,
async id =>
{
// Simulate async database check
await Task.Delay(10);
return id > 0;
},
"User ID must be positive");public static class Ensure
{
public static T NotNull<T>(T? value, [CallerArgumentExpression("value")] string? paramName = null) where T : class
{
if (value is null) throw new ArgumentNullException(
paramName, "The value cannot be null");
return value;
}
public static T InRange<T>(T value, T min, T max, [CallerArgumentExpression("value")] string? paramName = null)
where T : IComparable<T>
{
if (value.CompareTo(min) < 0 || value.CompareTo(max) > 0)
{
throw new ArgumentOutOfRangeException(paramName, $"The value must be between {min} and {max}.");
}
return value;
}
public static string MatchesRegex(string value, string pattern, [CallerArgumentExpression("value")] string? paramName = null)
{
if (!System.Text.RegularExpressions.Regex.IsMatch(value, pattern))
{
throw new ArgumentException($"The value does not match the required pattern: {pattern}", paramName);
}
return value;
}
}public class Customer
{
public string Name { get; set; } = string.Empty;
public int Age { get; set; }
public string Email { get; set; } = string.Empty;
public Customer(string name, int age, string email)
{
// Using advanced fluent guard clauses
Name = Guard.For(name)
.NotNull()
.NotNullOrEmpty()
.Value;
Age = Guard.For(age)
.NotNegative()
.InRange(13, 120)
.Value;
Email = Guard.For(email)
.NotNull()
.NotNullOrEmpty()
.MatchesRegex(@"^[^@\s]+@[^@\s]+\.[^@\s]+$")
.Value;
}
}[HttpPost]
public IActionResult CreateCustomer([FromBody] CustomerRequest request)
{
try
{
// Validate input using guard clauses
var name = Ensure.NotNullOrEmpty(request.Name);
var age = Ensure.InRange(request.Age, 13, 120);
var email = Ensure.MatchesRegex(Ensure.NotNullOrEmpty(request.Email), @"^[^@\s]+@[^@\s]+\.[^@\s]+$");
// Create customer
var customer = new Customer(name, age, email);
// ... rest of implementation
return CreatedAtAction(nameof(GetCustomer), new { email = customer.Email }, customer);
}
catch (Exception ex)
{
return BadRequest(new { error = ex.Message });
}
}- Ensure API inputs meet business requirements before processing
- Prevent invalid data from entering your domain models
- Guarantee that objects are always in a valid state
- Fail-fast behavior prevents corrupted data propagation
- Consistent error responses with meaningful messages
- Reduced boilerplate validation code
- Complex validations that span multiple properties
- Attribute-based validation for declarative rules
- External service checks (e.g., database existence)
- Third-party API validations
All guard clauses are designed to be easily testable. Here's an example of how you could test the NotNull guard clause:
[Test]
public void NotNull_WhenValueIsNull_ThrowsArgumentNullException()
{
string? value = null;
Assert.Throws<ArgumentNullException>(() => Ensure.NotNull(value));
}
[Test]
public void NotNull_WhenValueIsNotNull_ReturnsValue()
{
string value = "test";
var result = Ensure.NotNull(value);
Assert.AreEqual(value, result);
}We've also included comprehensive unit tests in the GuardClauses.UnitTests project that validate all guard clauses with various input scenarios, including the advanced patterns.
- Fail Fast: Identify and handle invalid inputs immediately, preventing issues from propagating through your system.
- Readable Code: Simplify method logic by removing nested validation checks, making code easier to understand.
- Reusable Logic: Custom guard clauses make validation consistent and maintainable across your entire application.
- Professional Quality: Demonstrate craftsmanship through defensive programming practices that protect against runtime errors.
- Self-Documenting: Guard clauses serve as executable documentation of your method's preconditions.
- Flexible: Support for fluent APIs, custom exceptions, attributes, and extension methods allows you to choose the right pattern for each scenario.
This project was developed by MrEshboboyev, a software developer passionate about clean code, defensive programming, and scalable architectures.
This project is licensed under the MIT License. Feel free to use and adapt the code for your own projects.
C#, .NET, Guard Clauses, Defensive Programming, Input Validation, Software Architecture, Clean Code, Error Handling, Web API, REST, Swagger, NSwag, Unit Testing, Fluent API
Feel free to suggest additional features or ask questions! π