Search code examples
c#asp.net-core-webapi

Who is doing this input validation for me in the framework


ASP.NET Core 8.0 Web API - my controller looks like this:

[HttpGet("teacher/{source}/{id}"]
public async Task<ActionResult<Teacher?>> GetTeacher(TeacherEnumSource source, string id)
{
    var result = await _teacherData.GetTeacher(source, id);
    
    return result == null ? StatusCode(404, $"No Teacher found for Source: {source} and ID: {id}") : Ok(result);
}

Notice the source param is an enum:

public enum TeacherEnumSource
{
    SCHOOL,
    UNIVERSITY,
    KINDERGARTEN
}

If I pass a bad param to my API as source that is not in the enum list, I am automatically getting this error which is kind of nice:

{
    "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "errors": {
        "source": [
            "The value 'camping' is not valid."
        ]
    },
    "traceId": "00-4e2baa0235572f8359e0e15225fffd3e-4ccac5f22f8ede03-00"
}

If I put breakpoints, it is not even running the _teacherData.GetTeacher(source, id) line so no need to show that code.

I have not added error handling yet and I have even commented out these two lines from my program.cs to make sure:

//app.UseStatusCodePages();
//app.UseExceptionHandler();

So who is doing this error handling for me and more importantly: how can I customize it a bit because I do want to LOG that incident to my external logger (DataDog in my case).


Solution

  • You should choose to use a

    1. Model and use Model validator (post not get)

    2. Manually validate the enum value to test if the value is valid

    3. Validate in your route using a route constraint:

      public class RangeConstraint : IRouteConstraint
      {
          private readonly int _min;
          private readonly int _max;
      
          public RangeConstraint(int min, int max)
          {
              _min = min;
              _max = max;
          }
      
          public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
          {
              if (values.TryGetValue(routeKey, out var value) && int.TryParse(value.ToString(), out int intValue))
              {
                  return intValue >= _min && intValue <= _max;
              }
              return false;
          }
      }
      

    and integrate it using:

    services.Configure<RouteOptions>(options =>
    {
        options.ConstraintMap.Add("range", typeof(RangeConstraint));
    });