Validate Nested Complex or Collection Property using Fluent API

How to Validate Nested Complex Type or Collection Property using Fluent API

In this article, I will discuss how to Validate a Nested Complex or Collection Property using Fluent API in ASP.NET Core Web API Applications with Examples. Please read our previous articles discussing how to Implement Fluent API Conditional Validations in ASP.NET Core Web API Applications. 

Validate a Nested Complex Type or Collection Property using Fluent API

To Validate a Nested Complex Type or Collection Property of a model, we need to use the SetValidator() and ForEach() fluent API methods.

  • SetValidator(): The SetValidator() method allows us to use a separate validator class to validate a nested complex type of a model. For example, if our model contains a complex property, we can use the SetValidator() method to validate the properties of that complex type using a separate validator class.
  • ForEach(): The ForEach() method validates each element in a collection property. For example, if you have a collection property within a model, the Fluent API ForEach() method allows us to apply validation rules to each element in that collection.
Real-Time Example:

Let’s build an online store application where customers can place orders with multiple items. We will use Entity Framework Core for data access and FluentValidation to validate our models in an ASP.NET Core Web API Application.

Setting Up the Project

First, create a new ASP.NET Core Web API Project named FluentAPIValidationDemo and install the following required Packages. You can install the packages using the Package Manager Console by executing the following commands:

  • Install-Package Microsoft.EntityFrameworkCore.SqlServer
  • Install-Package Microsoft.EntityFrameworkCore.Tools
  • Install-Package FluentValidation
Create Models

Create a folder named Models in the Project root directory where we will create all our Models:

Customer Entity:

Create a class file named Customer.cs within the Models folder and then copy and paste the following code. The Customer Entity represents a user who places orders. It includes contact information, relationships to their associated orders, and addresses.

namespace FluentAPIValidationDemo.Models
{
    public class Customer
    {
        public int Id { get; set; }
        public string Name { get; set; } // Required, max length 50
        public string Email { get; set; } // Required, valid email format
        public string? Phone { get; set; } // Optional, valid phone number
        public ICollection<Order> Orders { get; set; }
        public ICollection<Address> Addresses { get; set; }
    }
}
Address Entity:

Create a class file named Address.cs within the Models folder and then copy and paste the following code. The Address Entity represents a customer’s location. Orders reference this to determine the shipping destination.

namespace FluentAPIValidationDemo.Models
{
    public class Address
    {
        public int Id { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string ZipCode { get; set; }
        public int CustomerId { get; set; }
        public Customer Customer { get; set; }
    }
}
Product Entity:

Create a class file named Product.cs within the Models folder, and then copy and paste the following code. The Product Entity represents items available for purchase, including pricing and available stock quantity.

using System.ComponentModel.DataAnnotations.Schema;

namespace FluentAPIValidationDemo.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; } // Required, max length 100

        [Column(TypeName = "decimal(18, 2)")]
        public decimal Price { get; set; } // Must be greater than 0
        public int Quantity { get; set; } // Stock quantity, must be >= 0
        public ICollection<OrderItem> OrderItems { get; set; }
    }
}
Order Entity:

Create a class file named Order.cs within the Models folder, and then copy and paste the following code. The Order Entity represents a transaction where a customer purchases products. Includes references to the customer who placed the order, the shipping address, and the list of items purchased.

using System.ComponentModel.DataAnnotations.Schema;
namespace FluentAPIValidationDemo.Models
{
    public class Order
    {
        public int Id { get; set; }
        public int CustomerId { get; set; } // Foreign key
        public Customer Customer { get; set; } // Navigation property
        public int ShippingAddressId { get; set; } // Foreign key
        public Address ShippingAddress { get; set; } // Navigation property
        public DateTime OrderDate { get; set; } // Required
        [Column(TypeName = "decimal(18, 2)")]
        public decimal OrderAmount { get; set; } // Calculated based on OrderItems
        public ICollection<OrderItem> OrderItems { get; set; } // Must contain at least one item
    }
}
OrderItem Entity:

Create a class file named OrderItem.cs within the Models folder, and then copy and paste the following code. The OrderItem Entity represents individual items within an order. Links products to the order and tracks quantity and price at the time of purchase.

using System.ComponentModel.DataAnnotations.Schema;
namespace FluentAPIValidationDemo.Models
{
    public class OrderItem
    {
        public int Id { get; set; }
        public int OrderId { get; set; } // Foreign key
        public Order Order { get; set; } // Navigation Property
        public int ProductId { get; set; } // Foreign key
        public Product Product { get; set; } // Navigation Property
        public int Quantity { get; set; } // Must be greater than 0
        [Column(TypeName = "decimal(18, 2)")]
        public decimal ProductPrice { get; set; } // Price at the time of order
    }
}
Create DbContext Class

First, create a folder named Data in the project root directory. Inside the Data folder, create a class file named ECommerceDbContext.cs and copy and paste the following code. This class represents the bridge between the application’s domain models and the underlying database. It’s used to configure the data model, define relationships, seed initial data, and interact with the database via Entity Framework Core.

using FluentAPIValidationDemo.Models;
using Microsoft.EntityFrameworkCore;

namespace FluentAPIValidationDemo.Data
{
    public class ECommerceDbContext : DbContext
    {
        public ECommerceDbContext(DbContextOptions<ECommerceDbContext> options) : base(options) { }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // Configure relationships to avoid multiple cascade paths

            // Order -> Customer: Disable cascade delete to prevent multiple cascade paths
            modelBuilder.Entity<Order>()
                .HasOne(o => o.Customer)
                .WithMany(c => c.Orders)
                .HasForeignKey(o => o.CustomerId)
                .OnDelete(DeleteBehavior.Restrict); // or DeleteBehavior.NoAction

            // Order -> ShippingAddress: Disable cascade delete for this relationship as well
            modelBuilder.Entity<Order>()
                .HasOne(o => o.ShippingAddress)
                .WithMany() // No navigation property on Address since an address can be used by multiple orders (if needed)
                .HasForeignKey(o => o.ShippingAddressId)
                .OnDelete(DeleteBehavior.Restrict); // or DeleteBehavior.NoAction

            // Seed data for Customers
            modelBuilder.Entity<Customer>().HasData(
                new Customer { Id = 1, Name = "Alice Johnson", Email = "alice@example.com" },
                new Customer { Id = 2, Name = "Bob Smith", Email = "bob@example.com" }
            );

            // Seed data for Addresses
            modelBuilder.Entity<Address>().HasData(
                new Address { Id = 1, City = "Springfield", State = "IL", ZipCode = "62704", CustomerId = 1 },
                new Address { Id = 2, City = "Shelbyville", State = "IL", ZipCode = "62565", CustomerId = 2 }
            );

            // Seed data for Products
            modelBuilder.Entity<Product>().HasData(
                new Product { Id = 1, Name = "Laptop", Price = 999.99M, Quantity = 10 },
                new Product { Id = 2, Name = "Smartphone", Price = 499.99M, Quantity = 50 },
                new Product { Id = 3, Name = "Headphones", Price = 79.99M, Quantity = 100 }
            );
        }

        public DbSet<Customer> Customers { get; set; }
        public DbSet<Address> Addresses { get; set; }
        public DbSet<Order> Orders { get; set; }
        public DbSet<Product> Products { get; set; }
        public DbSet<OrderItem> OrderItems { get; set; }
    }
}
Configure the Database Connection String in the appsettings.json file

To connect our DbContext to the SQL Server database, we need to add the connection string in the appsettings.json file. So, please modify the appsettings.json file as follows. This contains the connection string (ECommerceDBConnection) required to connect to the SQL Server database.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "ECommerceDBConnection": "Server=LAPTOP-6P5NK25R\\SQLSERVER2022DEV;Database=ECommerceDB;Trusted_Connection=True;TrustServerCertificate=True;"
  }
}
Service and Middleware Configuration

It acts as the entry point for the application, ensuring all necessary services are registered, and the HTTP request pipeline is configured correctly before the app starts handling requests. Please modify the Program.cs class file as follows.

using FluentAPIValidationDemo.Data;
using Microsoft.EntityFrameworkCore;
namespace FluentAPIValidationDemo
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // Add services to the container.
            builder.Services.AddControllers()
                // Optionally, configure JSON options or other formatter settings
                .AddJsonOptions(options =>
                {
                    // Configure JSON serializer settings to keep the original names in serialization and deserialization
                    options.JsonSerializerOptions.PropertyNamingPolicy = null;
                });

            // Register the ECommerceDbContext with dependency injection
            builder.Services.AddDbContext<ECommerceDbContext>(options =>
                options.UseSqlServer(builder.Configuration.GetConnectionString("ECommerceDBConnection")));

            // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();

            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }

            app.UseHttpsRedirection();

            app.UseAuthorization();

            app.MapControllers();

            app.Run();
        }
    }
}
Generate and Apply Database Migration:

Now, open the Package Manager Console and execute the Add-Migration command to create a new Migration file. Then, execute the Update-Database command to apply the migration and update and sync the database with our models, as shown in the image below.

Validate Nested Complex or Collection Property using Fluent API

Once you execute the above commands, it should have created the ECommerceDB database with the Required tables as shown in the below image:

How to Validate Nested Complex or Collection Property using Fluent API in ASP.NET Core Web API Applications with Examples

Creating DTOs

DTOs are used to decouple the data transfer layer from the domain models. This provides a structured and controlled way to handle user input, apply validations, and map data to domain entities without directly exposing internal models. So, create a folder named DTOs in the project root directory, where we will create all our DTOs.

OrderItemDTO

Create a class file named OrderItemDTO.cs within the DTOs folder and copy and paste the following code. The OrderItemDTO A part of OrderDTO details each product’s ID and quantity.

namespace FluentAPIValidationDemo.DTOs
{
    public class OrderItemDTO
    {
        public int ProductId { get; set; } // Required
        public int Quantity { get; set; } // Must be greater than 0
    }
}
AddressDTO.cs

Create a class file named AddressDTO.cs within the DTOs folder and copy and paste the following code. The AddressDTO represents the structure for providing a new address when a shipping address ID is not provided.

namespace FluentAPIValidationDemo.DTOs
{
    public class AddressDTO
    {
        public string City { get; set; }
        public string State { get; set; }
        public string ZipCode { get; set; }
    }
}
OrderDTO

Create a class file named OrderDTO.cs within the DTOs folder and copy and paste the following code. The OrderDTO represents the data received from the client to create an order. It includes the customer ID, shipping address information, and the list of items being ordered.

namespace FluentAPIValidationDemo.DTOs
{
    public class OrderDTO
    {
        public int CustomerId { get; set; } // Required

        // If provided (> 0), this must reference an existing address for the customer.
        // If null or 0, then the NewAddress property will be used.
        public int? ShippingAddressId { get; set; }

        // NewAddress details are required if ShippingAddressId is null or 0.
        public AddressDTO? NewAddress { get; set; }

        public List<OrderItemDTO> OrderItems { get; set; } // Must contain at least one item
    }
}
Create Validators for the DTOs Using FluentValidation

Validators provide a systematic approach to verifying incoming data against defined rules. They ensure the data is well-formed and consistent and adheres to the application’s business logic before proceeding. To validate incoming data from the client, we will create validators for AddressDTO, OrderDTO, and OrderItemDTO. First, create a folder named Validators in the project root directory. Inside this folder, we will create our validators.

Validator for AddressDTO

Create a class file named AddressDTOValidator.cs within the Validators folder, and then copy and paste the following code. The AddressDTOValidator ensures that any new address (when ShippingAddressId is null or zero) has valid city, state, and zip code fields.

using FluentAPIValidationDemo.DTOs;
using FluentValidation;

namespace FluentAPIValidationDemo.Validators
{
    public class AddressDTOValidator : AbstractValidator<AddressDTO>
    {
        public AddressDTOValidator()
        {
            RuleFor(a => a.City)
                .NotEmpty().WithMessage("City is required.")
                .MaximumLength(100).WithMessage("City cannot exceed 100 characters.");

            RuleFor(a => a.State)
                .NotEmpty().WithMessage("State is required.")
                .MaximumLength(100).WithMessage("State cannot exceed 100 characters.");

            RuleFor(a => a.ZipCode)
                .NotEmpty().WithMessage("ZipCode is required.")
                .Matches(@"^\d{5,6}$").WithMessage("ZipCode must be 5 or 6 digits.");
        }
    }
}
Validator for OrderItemDTO

Create a class file named OrderItemDTOValidator.cs within the Validators folder, and then copy and paste the following code. The OrderItemDTOValidator ensures that every order item has a valid ProductId and a positive quantity.

using FluentAPIValidationDemo.DTOs;
using FluentValidation;
namespace FluentAPIValidationDemo.Validators
{
    public class OrderItemDTOValidator : AbstractValidator<OrderItemDTO>
    {
        public OrderItemDTOValidator()
        {
            RuleFor(oi => oi.ProductId)
                .GreaterThan(0).WithMessage("ProductId must be greater than 0.");

            RuleFor(oi => oi.Quantity)
                .GreaterThan(0).WithMessage("Quantity must be greater than 0.");
        }
    }
}
Validator for OrderDTO

Create a class file named OrderDTOValidator.cs within the Validators folder, and then copy and paste the following code. The OrderDTOValidator ensures the overall structure of the order is correct. Validates the customer ID, shipping address ID, and that at least one valid order item is present.

using FluentAPIValidationDemo.Data; 
using FluentAPIValidationDemo.DTOs; 
using FluentValidation; 
using Microsoft.EntityFrameworkCore; 

namespace FluentAPIValidationDemo.Validators
{
    public class OrderDTOValidator : AbstractValidator<OrderDTO>
    {
        // Private field to hold the database context instance
        private readonly ECommerceDbContext _context;

        // Constructor that accepts the ECommerceDbContext via dependency injection
        public OrderDTOValidator(ECommerceDbContext context)
        {
            // Assign the passed DbContext instance to the private field
            _context = context; 

            // -------------------------
            // Validate the CustomerId property:
            // -------------------------
            RuleFor(o => o.CustomerId)
                .GreaterThan(0) // Ensure that the CustomerId is greater than 0
                .WithMessage("CustomerId must be greater than 0.")
                .MustAsync(async (customerId, cancellationToken) =>
                {
                    // Check asynchronously if a customer with the given CustomerId exists in the database
                    return await _context.Customers.AnyAsync(u => u.Id == customerId, cancellationToken);
                })
                .WithMessage("Invalid Customer"); // Provide an error message if the customer does not exist

            // -------------------------
            // Validate the ShippingAddressId when it is provided (i.e., when it's greater than 0):
            // -------------------------
            RuleFor(o => o.ShippingAddressId)
                .MustAsync(async (dto, shippingAddressId, cancellationToken) =>
                {
                    // Check if ShippingAddressId is provided and valid (> 0)
                    if (shippingAddressId.HasValue && shippingAddressId.Value > 0)
                    {
                        // Validate that the address exists in the database and belongs to the customer specified by dto.CustomerId
                        return await _context.Addresses.AnyAsync(
                            a => a.Id == shippingAddressId.Value && a.CustomerId == dto.CustomerId, cancellationToken);
                    }
                    // If ShippingAddressId is null or 0, skip this check by returning true
                    return true;
                })
                .WithMessage("Invalid ShippingAddressId for the specified Customer.")
                .When(o => o.ShippingAddressId.HasValue && o.ShippingAddressId.Value > 0);
        
            // -------------------------
            // Validate the NewAddress property when ShippingAddressId is not provided or equals 0:
            // -------------------------
            RuleFor(o => o.NewAddress)
                .NotNull() // Ensure that NewAddress is not null in this case
                .WithMessage("New address details must be provided when ShippingAddressId is not provided or is 0.")
                .When(o => !o.ShippingAddressId.HasValue || o.ShippingAddressId.Value == 0)
                // After confirming NewAddress is not null, use DependentRules to validate its properties using its own validator
                .DependentRules(() =>
                {
                    RuleFor(o => o.NewAddress!)
                        // Use SetValidator to attach the AddressDTOValidator to the NewAddress property
                        .SetValidator(new AddressDTOValidator());
                });

            // -------------------------
            // Validate the OrderItems collection:
            // -------------------------
            RuleFor(o => o.OrderItems)
                .NotEmpty() // Ensure that the OrderItems collection is not empty
                .WithMessage("Order must have at least one order item.")
                // Use ForEach to apply a validator to each item in the collection
                .ForEach(oi => oi.SetValidator(new OrderItemDTOValidator()));
        }
    }
}
Code Explanations:
  • CustomerId Rule: This rule ensures that the provided customer ID is valid by checking that it is greater than zero and exists in the database.
  • ShippingAddressId Rule: When a shipping address ID is given (and is greater than zero), it validates that the address exists and belongs to the specified customer.
  • NewAddress Rule: When no shipping address ID is provided (or it’s zero), it ensures that a new address is provided. Once confirmed as not null, SetValidator() is used to validate the nested AddressDTO.
  • OrderItems Rule: Ensures at least one order item and applies the OrderItemDTOValidator to every element in the collection using ForEach().
Orders Controller:

Create an Empty API controller named OrdersController within the Controllers folder and then copy and paste the following code. This API controller handles order-related HTTP requests, such as creating a new order.

using FluentAPIValidationDemo.Data;            
using FluentAPIValidationDemo.DTOs;            
using FluentAPIValidationDemo.Models;         
using FluentAPIValidationDemo.Validators;     
using Microsoft.AspNetCore.Mvc;                

namespace FluentAPIValidationDemo.Controllers
{
    [ApiController]  
    [Route("api/[controller]")]  
    public class OrdersController : ControllerBase
    {
        private readonly ECommerceDbContext _context;

        public OrdersController(ECommerceDbContext context)
        {
            _context = context;
        }

        // HTTP POST method for creating a new order.
        [HttpPost]
        public async Task<IActionResult> CreateOrder([FromBody] OrderDTO orderDTO)
        {
            // Create an instance of the OrderDTOValidator and pass the DbContext.
            var validator = new OrderDTOValidator(_context);

            // Asynchronously validate the incoming OrderDTO.
            var validationResult = await validator.ValidateAsync(orderDTO);

            // Check if the validation failed.
            if (!validationResult.IsValid)
            {
                // Project the validation errors into a simple format with field names and error messages.
                var errorResponse = validationResult.Errors.Select(e => new
                {
                    Field = e.PropertyName,   // Name of the property that failed validation.
                    Error = e.ErrorMessage    // The corresponding error message.
                });

                // Return a 400 Bad Request with the list of validation errors.
                return BadRequest(new { Errors = errorResponse });
            }

            int shippingAddressId;  // Variable to hold the resolved ShippingAddressId.

            // Check if a valid ShippingAddressId is provided in the OrderDTO.
            if (orderDTO.ShippingAddressId.HasValue && orderDTO.ShippingAddressId.Value > 0)
            {
                // If provided and valid, use the ShippingAddressId from the DTO.
                shippingAddressId = orderDTO.ShippingAddressId.Value;
            }
            else
            {
                // If ShippingAddressId is not provided or is 0, ensure that NewAddress details are provided.
                if (orderDTO.NewAddress == null)
                {
                    // Return 400 Bad Request if NewAddress is missing.
                    return BadRequest("New address details are required when ShippingAddressId is not provided or is 0.");
                }

                // Create a new Address entity using the details from the NewAddress DTO.
                var newAddress = new Address
                {
                    City = orderDTO.NewAddress.City,       
                    State = orderDTO.NewAddress.State,       
                    ZipCode = orderDTO.NewAddress.ZipCode,   
                    CustomerId = orderDTO.CustomerId         
                };

                // Add the new Address entity to the database context.
                _context.Addresses.Add(newAddress);

                // Save changes to the database to generate an Id for the new Address.
                await _context.SaveChangesAsync();

                // Assign the newly generated Address Id to shippingAddressId.
                shippingAddressId = newAddress.Id;
            }

            // Create a new Order entity using details from the OrderDTO and the determined ShippingAddressId.
            var order = new Order
            {
                CustomerId = orderDTO.CustomerId,            // Set the CustomerId.
                ShippingAddressId = shippingAddressId,       // Set the ShippingAddressId determined above.
                OrderDate = DateTime.UtcNow,                 // Set the OrderDate to the current UTC time.
                OrderItems = new List<OrderItem>()           // Initialize the OrderItems collection.
            };

            decimal orderAmount = 0M;  // Initialize a variable to accumulate the total order amount.

            // Iterate through each OrderItemDTO in the OrderDTO.
            foreach (var itemDTO in orderDTO.OrderItems)
            {
                // Check if the product exists in the database using its ProductId.
                var product = await _context.Products.FindAsync(itemDTO.ProductId);
                if (product == null)
                {
                    // Return 400 Bad Request if the product doesn't exist.
                    return BadRequest($"Product with ID {itemDTO.ProductId} does not exist.");
                }

                // Check if the product has enough stock for the quantity requested.
                if (product.Quantity < itemDTO.Quantity)
                {
                    // Return 400 Bad Request if there is insufficient stock.
                    return BadRequest($"Insufficient stock for product '{product.Name}'. Available quantity: {product.Quantity}.");
                }

                // Deduct the ordered quantity from the product's available stock.
                product.Quantity -= itemDTO.Quantity;

                // Create a new OrderItem entity using the product details.
                var orderItem = new OrderItem
                {
                    ProductId = product.Id,            
                    Quantity = itemDTO.Quantity,       
                    ProductPrice = product.Price       
                };

                // Add the new OrderItem to the Order's collection of items.
                order.OrderItems.Add(orderItem);

                // Accumulate the total order amount (price multiplied by quantity).
                orderAmount += product.Price * itemDTO.Quantity;
            }

            // Set the total calculated order amount on the Order entity.
            order.OrderAmount = orderAmount;

            // Add the new Order entity to the database context.
            _context.Orders.Add(order);

            // Save changes to the database to persist the new order and its order items.
            await _context.SaveChangesAsync();

            // Return a 200 OK response with the OrderId and a success message.
            return Ok(new { OrderId = order.Id, Message = "Order created successfully." });
        }
    }
}

Now, let us proceed and test different scenarios for a better understanding.

Example 1: Valid Request Body

In this valid request, we are creating an order for a customer (CustomerId: 1) who already has an address (ShippingAddressId: 1). The OrderItems array contains valid product IDs and quantities.

{
  "CustomerId": 1,
  "ShippingAddressId": 1,
  "OrderItems": [
    {
      "ProductId": 1,
      "Quantity": 2
    },
    {
      "ProductId": 3,
      "Quantity": 1
    }
  ]
}

Expected Response: You should receive a successful response with details like the newly created OrderId and a message indicating success.

How to Validate a Nested Complex or Collection Property using Fluent API in ASP.NET Core Web API

Example 2: Invalid Request Body

This invalid request contains multiple issues:

  • ShippingAddressId: The value is set to 2, which belongs to a different customer (CustomerId: 2 in our seed data) instead of CustomerId: 1.
  • OrderItems: The order item has a negative quantity (-1), which violates the rule that quantity must be greater than 0.

Request Body:

{
  "CustomerId": 1,
  "ShippingAddressId": 2,
  "OrderItems": [
    {
      "ProductId": 1,
      "Quantity": -1
    }
  ]
}

Expected Response: You should receive a 400 Bad Request response with error details specifying:

  • That ShippingAddressId is invalid for the provided CustomerId.
  • The quantity for the order item must be greater than 0.

You should get the following response:

Nested Complex or Collection Property using Fluent API

Example 3: Alternative Valid Scenario

If you want to create a new shipping address on the fly (i.e., when ShippingAddressId is 0 or not provided), you can send a payload like this:

{
  "CustomerId": 1,
  "ShippingAddressId": 0,
  "NewAddress": {
    "City": "New City",
    "State": "NC",
    "ZipCode": "12345"
  },
  "OrderItems": [
    {
      "ProductId": 2,
      "Quantity": 1
    }
  ]
}

Expected Response: A valid request returns an HTTP 200 status along with the order details, while an invalid request returns an HTTP 400 status with error messages indicating which validations failed.

SetValidator() and ForEach() Fluent API methods

SetValidator()

This method attaches a separate validator to a complex property (i.e., an object that is nested within another object). In OrderDTOValidator, after ensuring that NewAddress is not null, the SetValidator() method applies the AddressDTOValidator to validate the nested AddressDTO properties.

ForEach()

This method applies a set of validation rules to every element within a collection property. In OrderDTOValidator, ForEach() iterates over the OrderItems collection and applies OrderItemDTOValidator to each order item.

We achieve a clean separation of concerns by organizing our application into Models/Entities, DTOs, Validators, and the DbContext. This makes our application more maintainable, scalable, and testable. Using SetValidator() and ForEach() further modularizes our validation logic, ensuring that nested objects and collections are validated consistently and effectively.

In the next article, I will discuss Fluent API Real-time Examples in ASP.NET Core Web API Applications with Examples. In this article, I explain How to Validate a Nested Complex or Collection Property using Fluent API in ASP.NET Core Web API Applications with Examples. I hope you enjoy this article, How to Validate Nested Complex or Collection Property using Fluent API in ASP.NET Core Web API.

Leave a Reply

Your email address will not be published. Required fields are marked *