Search code examples
c#asp.net-core.net-coremodel-binding

ASP.NET Core - ModelState.ValidationState is unvalidated for some properties


I have a controller that takes some model MyModel in a POST method. Inside of the controller method, there is a check:

[HttpPost("mymodel")]
public async Task<IActionResult> DoStuff(MyModel model)
{
   if (!ModelState.IsValid)
   {
     // early return with errors
   }

   // ...
}

With some specific data, ModelState.IsValid == false and ModelState.ErrorCount == 0. The reason why it is invalid, is that some properties end up with ValidationState == Unvalidated, making the whole model Unvalidated, thus not valid.

How can I find out what happens? Why would some properties be just Unvalidated instead of having an error? Some of the Unvalidated properties have validation attributes, some don't, but other properties that end up as valid also follow the same pattern - some have attributes, some don't.

Any help with a digging direction would appreciated.


Solution

  • It turns out, that in our case the properties in Unvalidated state were defined in base class, some of them were get-only. When we called the endpoint, the POST data had values for the get only properties, because model came from GET endpoint.

    Now, in the UI we removed one element from a list, making the previously calculated values out of date. The difference was cause of the error - it went away when stopped sending the properties.

    I tried to create a repro, but it works on minimal example, and I wasted enough time on that. Maybe someone will find that helpful. Here is what I thought would recreate the problem:

    public class Class1
    {
        public List<Class2> Objects { get; set; } = new();
        public List<Class2> Objects2 => Objects.ToList();
        public IEnumerable<int> ObjectsIds => Objects.Select(x => x.Id);
    }
    
    public class Class2
    {
        public int Id { get; set; }
    }
    
    public class SampleController : Controller
    {
        [HttpPost("/post")]
        public ActionResult Post(Class1 arg)
        {
            if (!ModelState.IsValid) // in our code this was invalid
            {
                return ValidationProblem();
            }
    
            return Ok();
        }
    }
    
        [Fact]
        public async Task Test1()
        {
            var application = new WebApplicationFactory<Program>();
    
            var client = application.CreateClient();
    
            var value = new
            {
                Objects = new[]
                {
                    new { Id = 1 },
                    new { Id = 2 },
                    new { Id = 2 },
                },
                Objects2 = new[]
                {
                    new { Id = 1 },
                    new { Id = 2 },
                },
                ObjectsIds = new[] { 1, 2, 3 }
            };
    
            var response = await client.PostAsJsonAsync("/post", value);
            response.StatusCode.Should().Be(HttpStatusCode.OK);
        }
    

    The test is green though, and I have no idea why it fails in our case. Both are dotnet 6. I tried putting the get-only properties in derived class, deeper nesting, minimal example still works.

    Have a nice day!