0

I want to return 400 Bad request when ModelState.IsValid is false, i.e., when a string is not mapped correctly to an appropriate enum. (For example when calling GET v1/abc/def/gamma for API GET v1/abc/def/{id} where id is an enum with values alpha & beta) How to do this validation when the controllers are auto-generated from api.yaml file using NSwag? Is there a way to modify the CController's GetCInfo() or pass ModelState to GetCInfoAsync()?

The controller in auto-generated file (ApiControllers.Generated.cs)

...
    [GeneratedCode("NSwag", "13.18.2.0 (NJsonSchema v10.8.0.0 (Newtonsoft.Json v13.0.0.0))")]
    [Route("v1")]
    public partial class CController : ControllerBase
    {
        private ICController _implementation;

        public CController(ICController implementation)
        {
            _implementation = implementation;
        }
...
        [HttpGet, Route("abc/def/{id}")]
        public Task<ActionResult<CInfo>> GetCInfo([BindRequired] CId id, CancellationToken cancellationToken)
        {
            return _implementation.GetCInfoAsync(id, cancellationToken);
        }
...
    }
...
    [GeneratedCode("NJsonSchema", "13.18.2.0 (NJsonSchema v10.8.0.0 (Newtonsoft.Json v13.0.0.0))")]
    public enum CId
    {
        [EnumMember(Value = @"alpha")]
        Alpha= 0,
        [EnumMember(Value = @"beta")]
        Beta= 1,
    }
...

Is there a way to do any one of the following three?

1. Add decorator by setting some property in nswag.json?

...
    [ApiController]
    [GeneratedCode("NSwag", "13.18.2.0 (NJsonSchema v10.8.0.0 (Newtonsoft.Json v13.0.0.0))")]
    [Route("v1")]
    public partial class CController : ControllerBase
    {
...
    }
...

2. Checking ModelState.IsValid in auto-generated controller?

...
    public partial class CController : ControllerBase
    {
            if (!ModelState.IsValid)
            {
                return BadRequest();
            }
...
    }
...

3. Passing ModelState to implementation?

...
    public partial class CController : ControllerBase
    {
            return _implementation.GetConsumableInfoAsync(id, ModelState, cancellationToken);
    }
...

(The string enum converter for JSON only is JsonStringEnumConverter() of System.Text.Json.Serialization set in Program.cs. This doesn't do anything for API URLs/ route data.)

I've tried writing middleware but that requires checking string for each API. String to enum conversion is done twice -- in custom middleware and in System.Text.Json.Serialization.JsonStringEnumConverter. Its easier to just check ModelState.IsValid.

12oClock
  • 1
  • 4
  • This would probably best be done using a Filter (note the capital). A filter is a bit of logic that examines the request as it comes in, and could be used to interrogate the ModelState (or whatever), and respond - without the request actually hitting a controller in the first place. – DiskJunky May 09 '23 at 15:50

1 Answers1

0

I created a custom Filter (ValidateModel.cs) to check if ModelState is valid and add it to Filters in Program.cs.

ValidateModel.cs

    public class ValidateModelActionFilter : IActionFilter
    {
        void IActionFilter.OnActionExecuted(ActionExecutedContext context){}

        void IActionFilter.OnActionExecuting(ActionExecutingContext context)
        {
            if (!context.ModelState.IsValid)
            {
                context.Result = new BadRequestObjectResult(context.ModelState);
            }
        }
    }

Program.cs

...
builder.Services.AddControllers(options => options.Filters.Add<ValidateModelActionFilter>());
...

(ModelStateInvalidFilter of ApiControllerAttribute does the similar ModelState validation but I was not able to add it to Filters in Program.cs.)

12oClock
  • 1
  • 4