Refactoring Repetitive Model Validation in ASP.NET Core

In one of my ASP.NET Core APIs, I was working with [FromBody] models that had a number of required fields, some were nullable integers, others were strings that needed to be non-empty. Initially, I handled validation directly in the controller action with a series of repetitive “if” statements like this:

if (!model.patienId!.HasValue || (int)model.patienId! == 0)
{ return new ContentResult() { Content = "patienId is a required parameter", StatusCode = 400 }; }

if (string.IsNullOrEmpty(model.ndcnumber))
{ return new ContentResult() { Content = "ndcnumber is a required parameter", StatusCode = 400 }; }

if (!model.qty!.HasValue || (int)model.qty! <= 0) 
{ return new ContentResult() { Content = "qty is a required parameter", StatusCode = 400 }; }

// ... and so on for every field

This approach worked well for what I was doing at the time, but it became messy very quickly, especially as I added more fields and repeated the same pattern in multiple actions and across different controllers. It was hard to read, error-prone to update, and encouraged copy/paste over clean reuse.

To clean this up, I decided to refactor the common validation checks into a dedicated "ValidationHelper" class. This utility would let me validate nullable value types and strings in a reusable and centralized way.

In this post, I show a simple refactor I made to clean up these types of repetitive validation "if" statements in my controller actions by introducing a lightweight ValidationHelper. The result is clearer code, consistent error handling, and much better maintainability.

Refactoring with a Validation Helper
Basically, a helper class is a fancy way to describe a separate CS file where I put reusable code like small utility functions that I can call from my controllers (or anywhere else). It keeps things clean and avoids repeating the same logic in every method. So, instead of cluttering my controllers with repetitive checks (like verifying required fields), I just put those checks in a helper class and call them as needed. It makes the code easier to read and maintain.

This helper lives in its own file under the "/Helpers/ValidationHelper.cs" folder, and I keep it in the shared "MyAPI.Helpers" namespace.

// simple helper method for validating required model fields in ASP.NET Core controllers.
public static class ValidationHelper
{
  // Validate a nullable value type is not null and not its default value (e.g., 0 for int, false for bool).
  // <typeparam name="T">A struct type (e.g., int, bool, DateTime).</typeparam>
  // <param name="value">The value to validate.</param>
  // <param name="name">The name of the parameter (used in the error message).</param>
  // Returns ContentResult with a 400 status if the value is missing or default; otherwise, null.
  public static ContentResult? Required<T>(T? value, string name) where T : struct
  {
    // Check if the value is null or equals the default value for its type (e.g., 0 for int).
    if (!value.HasValue || EqualityComparer<T>.Default.Equals(value.Value, default))
    {
      return new ContentResult
      {
        Content = $"{name} is a required parameter",
        StatusCode = 400
      };
    }

    // If valid, return null to indicate no validation error.
    return null;
  }

  // Validate a string is not null, empty, or whitespace.
  // <param name="value">The string value to validate.</param>
  // <param name="name">The name of the parameter (used in the error message).</param>
  // Returns ContentResult with a 400 status if the string is null or empty; otherwise, null.
  public static ContentResult? Required(string? value, string name)
  {
    // Check if the string is null, empty, or whitespace-only.
    if (string.IsNullOrWhiteSpace(value))
    {
      return new ContentResult
      {
        Content = $"{name} is a required parameter",
        StatusCode = 400
      };
    }

    // If valid, return null to indicate no validation error.
    return null;
  }
}

A Sample of My Model

public class MySampleModel
{
  [Required] public int? patientId { get; set; }
  [Required] public string? ndcnumber { get; set; } = string.Empty;
  [Required] public int? qty { get; set; }
}

Sample Usage in my Controller
Now, instead of repeating lots of "if" validation logic in every action I simply do something like this:

[ApiController]
[Route("[controller]")]
public class MyController : ControllerBase
{
  private ContentResult? _validationResult;

  [HttpPost("DoSomethingEndpoint")]
  public IActionResult DoSomethingEndpoint([FromBody] MySampleModel model)
  {
    // Use ValidationHelper to check required fields
    if ((_validationResult = ValidationHelper.Required(model.patientId, "patientId")) != null) return _validationResult;
    if ((_validationResult = ValidationHelper.Required(model.ndcnumber, "ndcnumber")) != null) return _validationResult;
    if ((_validationResult = ValidationHelper.Required(model.qty, "qty")) != null) return _validationResult;

    // if all fields are valid, proceed with processing
    return Ok("Request accepted");
  }
}

Controller-Wide Result Field
To avoid declaring the same "ContentResult? result;" variable in every action, I made it a private field on the controller, like this:
private ContentResult? _validationResult;

Then I reused it across all of my methods.
if ((_validationResult = ValidationHelper.Required(model.qty, "qty")) != null) return _validationResult;
Because ASP.NET Core creates a new instance of the controller per request, this pattern is safe and avoids unnecessary duplication while keeping the logic tight and readable.

Why This Is Better
Switching to a helper-based approach gave me several benefits:
• Cleaner, reusable validation logic. the core logic lives in one place and is reused everywhere
• Consistent error formatting, validation lines are short, clear, and consistent
• Easier maintenance. Changes to the validation rules only require updates in one spot. Easy to extend and apply to other models.
• Cleaner controller actions. No external validation libraries required

Conclusion
This refactor started as a way to clean up clutter in a single action, but it quickly became a general pattern I now apply across my API projects.

Also, for more complex validation rules, like conditional logic (e.g., field B is required if field A is X) I can simply extend this pattern or supplement it with FluentValidation. If you're writing the same validation statements over and over, give a helper like this a try. It might be all you need to make your codebase more elegant and maintainable.

0
Subscribe to my newsletter

Read articles from Sean M. Drew Sr. directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sean M. Drew Sr.
Sean M. Drew Sr.