Conditional Nested Validation with FluentValidation

This example demonstrating how to conditionally validate nested objects using FluentValidation’s When method.

Scenario

Let’s say we have:

  • Person class with an Address property
  • We only want to validate the Address when HasAddress is true
Model Classes
public class Person
{
    public string Name { get; set; }
    public bool HasAddress { get; set; }
    public Address Address { get; set; }
}

public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
    public string ZipCode { get; set; }
}

Validator Implementation

public class PersonValidator : AbstractValidator<Person>
{
    public PersonValidator()
    {
        // Always validate name
        RuleFor(x => x.Name).NotEmpty().MaximumLength(100);
        
        // Only validate address when HasAddress is true
        When(x => x.HasAddress, () => 
        {
            RuleFor(x => x.Address)
                .NotNull()
                .WithMessage("Address must be provided when HasAddress is true");
                
            RuleFor(x => x.Address.Street)
                .NotEmpty()
                .When(x => x.Address != null)
                .WithMessage("Street is required");
                
            RuleFor(x => x.Address.City)
                .NotEmpty()
                .When(x => x.Address != null)
                .MaximumLength(50);
                
            RuleFor(x => x.Address.ZipCode)
                .NotEmpty()
                .When(x => x.Address != null)
                .Matches(@"^\d{5}(-\d{4})?$");
        });
    }
}

Alternative Approach (Using Child Validator)

public class PersonValidator : AbstractValidator<Person>
{
    public PersonValidator()
    {
        RuleFor(x => x.Name).NotEmpty().MaximumLength(100);
        
        When(x => x.HasAddress, () => 
        {
            RuleFor(x => x.Address)
                .NotNull()
                .SetValidator(new AddressValidator());
        });
    }
}

public class AddressValidator : AbstractValidator<Address>
{
    public AddressValidator()
    {
        RuleFor(x => x.Street).NotEmpty();
        RuleFor(x => x.City).NotEmpty().MaximumLength(50);
        RuleFor(x => x.ZipCode).NotEmpty().Matches(@"^\d{5}(-\d{4})?$");
    }
}

Usage Example

var person = new Person 
{
    Name = "Khan",
    HasAddress = false,
    Address = null
};

var validator = new PersonValidator();
var result = validator.Validate(person); // Won't validate address

Key Points:

  1. The When condition determines whether the nested rules should execute
  2. The nested rules are only evaluated if HasAddress is true
  3. We still need null checks for the address object itself
  4. The second approach using a separate validator is cleaner for complex nested objects

This pattern is useful when you want to validate complex objects only in certain scenarios, reducing unnecessary validation overhead.

400 vs 422 response in post action

There are three possible types of client errors on API calls that receive request bodies:

Sending invalid JSON will result in a 400 Bad Request response:

HTTP/1.1 400 Bad Request
Content-Length: 35
{"message":"Problems parsing JSON"}

Sending the wrong type of JSON values will result in a 400 Bad Request response:

HTTP/1.1 400 Bad Request
Content-Length: 40

{"message":"Body should be a JSON object"}

Sending invalid fields will result in a 422 Unprocessable Entity response:

HTTP/1.1 422 Unprocessable Entity
Content-Length: 149

{
  "message": "Validation Failed",
  "errors": [
    {
      "resource": "Issue",
      "field": "title",
      "code": "missing_field"
    }
  ]
}

Reference

https://stackoverflow.com/questions/16133923/400-vs-422-response-to-post-of-data

Attribute Routing in ASP.NET Web API 2

Routing is how Web API matches a URI to an action. Web API 2 supports a new type of routing, called attribute routing. As the name implies, attribute routing uses attributes to define routes. Attribute routing gives you more control over the URIs in your web API. For example, you can easily create URIs that describe hierarchies of resources.

The earlier style of routing, called convention-based routing, is still fully supported. In fact, you can combine both techniques in the same project.

One advantage of convention-based routing is that templates are defined in a single place, and the routing rules are applied consistently across all controllers. Unfortunately, convention-based routing makes it hard to support certain URI patterns that are common in RESTful APIs. For example, resources often contain child resources: Customers have orders, movies have actors, books have authors, and so forth. It’s natural to create URIs that reflect these relations:

/customers/1/orders

This type of URI is difficult to create using convention-based routing. Although it can be done, the results don’t scale well if you have many controllers or resource types.

With attribute routing, it’s trivial to define a route for this URI. You simply add an attribute to the controller action:

[Route("customers/{customerId}/orders")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId) { ... }

Attribute routing for REST APIs

REST APIs should use attribute routing to model the app’s functionality as a set of resources where operations are represented by HTTP verbs.

Attribute routing uses a set of attributes to map actions directly to route templates. The following code is typical for a REST API and is used in the next sample:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

In the preceding code, MapControllers is called to map attribute routed controllers.

In the following example:

  • HomeController matches a set of URLs similar to what the default conventional route {controller=Home}/{action=Index}/{id?} matches.
public class HomeController : Controller
{
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    [Route("Home/Index/{id?}")]
    public IActionResult Index(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult About(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Read more here;

https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-9.0

https://learn.microsoft.com/en-us/aspnet/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2

Working with different type of WebClient to consume Web API

This is a list of web clients used in different environments to consume Web API.

Web Client in .NET Framework

To begin with, you have three different choices for consuming REST APIs when working in the .NET Framework: WebClient, HttpClient, and HttpWebRequest. In this post we will look at these three ways we can access REST APIs from within the managed environment, i.e., without resorting to third-party libraries. In the sections that follow I will illustrate these approaches with relevant code examples to help you gain a better understanding of the concepts.

In a nutshell, WebRequest—in its HTTP-specific implementation, HttpWebRequest—represents the original way to consume HTTP requests in .NET Framework. WebClient provides a simple but limited wrapper around HttpWebRequest. And HttpClient is the new and improved way of doing HTTP requests and posts, having arrived with .NET Framework 4.5.

Let’s start our discussion with the WebRequest abstract class.

System.Net.WebRequest

The System.Net.WebRequest class is an abstract class. Thus you will need to create a HttpWebRequest or FileWebRequest to consume HTTP requests using this class. The following code snippet shows how you can work with WebRequest.

WebRequest webRequest = WebRequest.Create(uri);
webRequest.Credentials = CredentialCache.DefaultCredentials;
webRequest.Method ="GET";
HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse();
System.Net.HttpWebRequest

WebRequest was the first class provided in the .NET Framework to consume HTTP requests. It gives you a lot of flexibility in handling each and every aspect of the request and response objects, without blocking the user interface thread. You can use this class to access and work with headers, cookies, protocols, and timeouts when working with HTTP. The following code snippet illustrates how HttpWebRequest can be used.

HttpWebRequest http = HttpWebRequest)WebRequest.Create(“http://localhost:8900/api/default”);
WebResponse response = http.GetResponse();
MemoryStream memoryStream = response.GetResponseStream();
StreamReader streamReader = new StreamReader(memoryStream);
string data = streamReader.ReadToEnd();

You can find Microsoft’s documentation on HttpWebRequest here

System.Net.WebClient

The System.Net.WebClient class in .NET provides a high-level abstraction on top of HttpWebRequest. WebClient is just a wrapper around HttpWebRequest, so uses HttpWebRequest internally. Thus WebClient is a bit slow compared to HttpWebRequest, but requires you to write much less code. You can use WebClient for simple ways to connect to and work with HTTP services. It is generally a better choice than HttpWebRequest unless you need to leverage the additional features that HttpWebRequest provides. The following code snippet shows how you can work with WebClient.

string data = null;
using (var webClient = new WebClient())
{
    data = webClient.DownloadString(url);
}
System.Net.Http.HttpClient

HttpClient was introduced in .NET Framework 4.5. For developers using .NET 4.5 or later, it is the preferred way to consume HTTP requests unless you have a specific reason not to use it. In essence, HttpClient combines the flexibility of HttpWebRequest and the simplicity of WebClient, giving you the best of both the worlds.

The HttpWebRequest class provides a lot of control over the request/response object. However, you should be aware that HttpClient was never designed to be a replacement for WebClient. You should use HttpWebRequest instead of HttpClient whenever you need the additional features that HttpWebRequest provides. Further, unlike WebClient, HttpClient lacks support for progress reporting and custom URI schemes. 

Although HttpClient doesn’t support FTP, mocking and testing HttpClient is easier. All I/O bound methods in HttpClient are asynchronous, and you can use the same HttpClient instance to make concurrent requests as well. The following code snippet illustrates how you can work with HttpClient.

public async Task<Author> GetAuthorsAsync(string uri)
{
    Author author = null;
    HttpResponseMessage response = await client.GetAsync(uri);
    if (response.IsSuccessStatusCode)
    {
        author = await response.Content.ReadAsAsync<Author>();
    }
    return author;
}

Note that when there is an error in the response, HttpClient doesn’t throw an error. Rather, it sets the IsSuccessStatusCode property to false. If you want to throw an exception if the IsSuccessStatusCode property is false, you can make a call to the EnsureSuccessStatusCode method on the response instance as shown below.

response.EnsureSuccessStatusCode();

HttpClient was designed to be instantiated once and reused throughout the application’s lifecycle—you should not create a new HttpClient instance for every request that your application needs to process. If you do, the available sockets could become exhausted by heavy traffic, resulting in SocketException errors. The recommended practice is to create a single, shared HttpClient instance.

HttpClientFactory

To do…

Web Client in Angular

To do..

Serilog ASP.NET 6 configuration

Here is the simple approach to log to console, file and database at the same time;

Step 1 — Add Environment Variable

The environment variable, ASPNETCORE_ENVIRONMENT, will be used to determine whether the application is running in Development mode or Production mode. Setting it as “Development” here allows us to put configuration values in appsettings.Development.json, which will get created soon.

Step 2 — Install Serilog NuGet packages

Assuming we want to write the logs to both the Console and also rolling text files, we can use the following Serilog packages:

Step 3— Create appsettings.Development.json

This is a JSON file where we store configuration values that will be used when the application runs in development mode, which is specified in Step 1. As you can see in the code snippet below, logging will be written into Console, Database and a rolling file with a rolling interval of day, which means each day, a new text file will be created for logging. The name of the text will become log-log-20210123.txt, while the date is appended by Serilog.

  "Serilog": {
    "Using": [
      "Serilog.Sinks.Console",
      "Serilog.Sinks.File",
      "Serilog.Sinks.MSSqlServer"
    ],
    "MinimumLevel": {
      "Default": "Debug",
      "Override": {
        "Microsoft": "Warning",
        "System": "Error"
      }
    },
    "WriteTo": [
      {
        "Name": "File",
        "Args": {
   
          "path": "C:\\Home\\LogFiles\\AppName\\log.txt",
          "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] {Message}{NewLine}{Exception}",
          "rollingInterval": "Day"
        }
      },
      {
        "Name": "Console",
        "Args": {
          "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console",
          "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff}] [{Level}] {MachineName} ({ThreadId}) <{SourceContext}> {Message}{NewLine}{Exception}"
        }
      },
      {
        "Name": "MSSqlServer",
        "Args": {
          "connectionString": "Data Source=url;Initial Catalog=FOO;Persist Security Info=True;User ID=Adam;Password=xyz",
          "sinkOptionsSection": {
            "tableName": "MyLog",
            "schemaName": "dbo",
            "autoCreateSqlTable": true,
            "batchPostingLimit": 1000,
            "period": "0.00:00:30"
          },
          "columnOptionsSection": {
            "disableTriggers": true,
            "PrimaryKeyColumnName": "Id",
            "addStandardColumns": [ "LogEvent" ],
            "removeStandardColumns": [ "MessageTemplate", "Properties" ],
            "timeStamp": {
              "columnName": "Timestamp",
              "convertToUtc": false
            }
          }
        }
      }
    ]
  }

You can clear the appsettings.json content if all configuration values are stored in environment-specific json file, and just leave an empty curly brackets, {}.

Step 4 — Configure Serilog in Program.cs

Here is how;

builder.Host.UseSerilog((context, provider, config) => {
    config.ReadFrom.Configuration(context.Configuration);
    });

app.UseSerilogRequestLogging();

Resources

A reference from Code Maze web site

Here is a good reference