使用AutoRest客户端时,如何用JSON.NET反序列化Web API的BadRequest(ModelState)?
Great question! I've run into this exact issue with Web API 2 and AutoRest clients before—here are a few reliable approaches to handle deserializing the 400 BadRequest response properly:
1. Align Web API's Error Response with a Defined Model (Most Sustainable Fix)
The root issue is that when you return BadRequest(ModelState) from Web API, it serializes the ModelStateDictionary into a default JSON structure that AutoRest doesn't automatically map to a strongly-typed model (unlike your 200 response). The cleanest fix is to standardize your error output and update your Swagger definition to match, so AutoRest can generate the necessary model and deserialization code.
Step 1: Define a Shared Validation Error Model
Create a POCO that matches the structure Web API outputs (or adjust it to something cleaner):
public class ValidationErrorResponse { // Matches the default ModelState serialization structure public Dictionary<string, string[]> ModelState { get; set; } = new(); }
Step 2: Update Web API to Return This Model
Instead of returning BadRequest(ModelState) directly, wrap it in your custom model. You can do this globally with an action filter or modify individual actions:
public class ValidateModelAttribute : ActionFilterAttribute { public override void OnActionExecuting(HttpActionContext actionContext) { if (!actionContext.ModelState.IsValid) { var errorResponse = new ValidationErrorResponse { ModelState = actionContext.ModelState .ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Errors.Select(e => e.ErrorMessage).ToArray()) }; actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, errorResponse); } } }
Register this filter in your WebApiConfig.cs:
config.Filters.Add(new ValidateModelAttribute());
Step 3: Update Swagger Annotations
Mark your 400 response with the new model so AutoRest picks it up:
[SwaggerResponse(HttpStatusCode.BadRequest, Type = typeof(ValidationErrorResponse))] public IHttpActionResult PostMyEntity(MyEntity entity) { // ... }
Now AutoRest will generate a ValidationErrorResponse class in your client and automatically handle deserialization when a 400 is returned.
2. Manually Deserialize the Response (Quick Fix)
If you can't modify the Web API right now, you can manually parse the raw response content from the HttpOperationException that AutoRest throws. Use a JSON serializer like Json.NET (Newtonsoft.Json) or System.Text.Json to map it to a matching model.
First, define the same ValidationErrorResponse model above in your client project. Then catch the exception and deserialize:
try { var result = await myAutoRestClient.MyEntityPostAsync(new MyEntity()); } catch (HttpOperationException ex) when (ex.Response.StatusCode == HttpStatusCode.BadRequest) { // Use Json.NET var validationErrors = JsonConvert.DeserializeObject<ValidationErrorResponse>(ex.Response.Content); // Or System.Text.Json // var validationErrors = JsonSerializer.Deserialize<ValidationErrorResponse>(ex.Response.Content); // Iterate through the errors foreach (var (field, errors) in validationErrors.ModelState) { Console.WriteLine($"Field: {field}, Errors: {string.Join(", ", errors)}"); } }
3. Tweak Web API's Default ModelState Serialization
If you want to stick with the default BadRequest(ModelState) but make the output easier to parse, you can adjust how Web API serializes the ModelStateDictionary. For example, you can flatten nested error paths or format messages differently:
In WebApiConfig.cs:
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); // Optional: Customize how errors are serialized config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new ModelStateConverter());
Where ModelStateConverter is a custom JsonConverter that reshapes the ModelState output to your liking.
Key Notes
- Make sure your Swagger definition accurately reflects the 400 response schema—this is critical for AutoRest to generate correct client code.
- When testing with
IHttpActionResultin unit tests, you can cast toInvalidModelStateResultdirectly because you're working with in-memory objects, but actual HTTP calls use serialized JSON, hence the difference in structure.
内容的提问来源于stack exchange,提问作者bkwdesign

