1

I am using .NET Core 3.1 to develop REST API. This is the controller that I am using (stripped down to basics just to demonstrate the issue, it returns the same data as it received):

OrdersController.cs

[Route("Payments/[controller]")]
[ApiController]
public class OrdersController : Controller
{
    [HttpPost]
    [Route("AddOrder")]
    public IActionResult AddOrder(Order order)
    {
        return Json(order);
    }
}

Order.cs

public class Product
{
    [Required]
    public string Manufacturer { get; set; }

    [Required]
    public string Code { get; set; }
}

public class Order
{
    [Required]
    public string Recipient { get; set; }

    [Required]
    public Product Product { get; set; }
}

When I call Payments/Orders/AddOrder with Postman with the following body (notice empty nested Code field):

{
   "Recipient": "John Doe",
   "Product": {
       "Manufacturer": "Company Inc.",
       "Code": ""
   }
}

... I get the following error which is expected since Code is annotaded with [Required]:

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "|7558e077-4f9147019767a0cf.",
    "errors": {
        "Product.Code": [
            "The Code field is required."
        ]
    }
}

However, if I try to validate the Order object with the same data in one of the services, it doesn't detect that field Code is empty.

// manually initialized order with same data as in Postman request
Order order = new Order()
{
    Recipient = "John Doe",
    Product = new Product()
    {
        Manufacturer = "Company Inc.",
        Code = string.Empty
    }
};

ValidationContext context = new ValidationContext(order, serviceProvider: null, items: null);
var validationResults = new List<ValidationResult>();
bool isValid = Validator.TryValidateObject(order, context, validationResults, true);

Here, isValid is true which means that object is valid. How can it be valid if Code is empty? Why does controller automatically detect that nested property is invalid but Validator.TryValidateObject doesn't? Does controller validation work recursively and Validator.TryValidateObject does not? Can I use the same recursive (nested fields) validation that controller uses somewhere else in the code?

EDIT: Why do we even want to validate the object on the service layer?

We developed a shared project to be used in other solutions. It calls REST API with correctly formatted payload and headers. We want to validate object inside shared project's code (service) before it is even sent to REST API server. We want to avoid situations where request is sent out by the shared client but is then rejected at REST API's server.

Sam Carlson
  • 1,891
  • 1
  • 17
  • 44
  • Please check out msdn docs about [Validator](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.validator.tryvalidateobject?view=net-5.0). It says: "It does not recursively validate the property values of the object." – Neistow Aug 25 '21 at 07:14
  • 1
    @Neistow I see. Then what does `ApiController`'s model validation use internally? It must use some sort of recursive validation based on my findings. Can I re-use it in other parts of the application (service layer)? – Sam Carlson Aug 25 '21 at 07:16
  • Can't answer this question. It denifitenly has to do smth with reflection (maybe?) You need to check asp.net-core source code and find out that yourself. Also why do you need validation in service layer? Your models gets validated in the controller and passed into service layer valid. Creating validation like in your example is a bad approach. – Neistow Aug 25 '21 at 07:20
  • 2
    `Validator` is the low-level function. ASP.NET Core's validation does a lot more than call `TryValidateObject` on the root. After all, it has to collect validation results for all objects without wasting a lot of time in reflection. If you check the [ObjectModelValidator](https://github.com/dotnet/aspnetcore/blob/8b30d862de6c9146f466061d51aa3f1414ee2337/src/Mvc/Mvc.Core/src/ModelBinding/ObjectModelValidator.cs) source code you'll see it uses cached validators *and* model metadata to construct a visitor that will validate the entire graph *without* reflection on every call – Panagiotis Kanavos Aug 25 '21 at 07:25
  • @Neistow We developed a [shared project](https://learn.microsoft.com/en-us/xamarin/cross-platform/app-fundamentals/shared-projects?tabs=windows) to be used in other solutions. It calls REST API with correct payload and headers. We want to validate object inside shared project's code (service) before it is even sent to REST API server. – Sam Carlson Aug 25 '21 at 07:28

0 Answers0