I need to use ProblemDetails for the validation errors. It's working as expected. But there is a big problem here, I have to write a similar code in all the action methods and I think it's not a good idea.
public async Task<ActionResult<SampleResponse>> Post([FromBody] SampleRequest getRateApiRequest)
{
try
{
if (ModelState.IsValid == false)
{
ProblemDetails problemDetails = new ProblemDetails();
problemDetails.Detail = "Detail";
problemDetails.Instance = "Instance";
problemDetails.Status = StatusCodes.Status400BadRequest;
problemDetails.Title = "Title";
problemDetails.Type = "Type";
List<FieldCodeMessage> codeMessages = new List<FieldCodeMessage>();
foreach (var modelState in ModelState)
{
if (modelState.Value.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
{
MemberInfo property = typeof(TradeBookingRequestAPI).GetProperty(modelState.Key);
var attribute = property.GetCustomAttributes(typeof(DisplayNameAttribute), true).Cast<DisplayNameAttribute>().Single();
string displayName = attribute.DisplayName;
switch (modelState.Key)
{
case "Property1":
codeMessages.Add(new FieldCodeMessage(field: displayName, code: "01", message: modelState.Value.Errors.Select(a => a.ErrorMessage).FirstOrDefault()));
break;
case "Property2":
codeMessages.Add(new FieldCodeMessage(field: displayName, code: "02", message: modelState.Value.Errors.Select(a => a.ErrorMessage).FirstOrDefault()));
break;
case "Property3":
codeMessages.Add(new FieldCodeMessage(field: displayName, code: "03", message: modelState.Value.Errors.Select(a => a.ErrorMessage).FirstOrDefault()));
break;
case "Property4":
codeMessages.Add(new FieldCodeMessage(field: displayName, code: "04", message: modelState.Value.Errors.Select(a => a.ErrorMessage).FirstOrDefault()));
break;
case "Property5":
codeMessages.Add(new FieldCodeMessage(field: displayName, code: "05", message: modelState.Value.Errors.Select(a => a.ErrorMessage).FirstOrDefault()));
break;
case "Property6":
codeMessages.Add(new FieldCodeMessage(field: displayName, code: "06", message: modelState.Value.Errors.Select(a => a.ErrorMessage).FirstOrDefault()));
break;
}
}
}
problemDetails.Extensions.Add("Invalid Fields", codeMessages);
return BadRequest(problemDetails);
}
}
catch (Exception)
{
...
}
}
So Is there a way to handle this in a centralized place like middleware or something else.
Expected response:
{
"type": "Type",
"title": "Title",
"status": 400,
"detail": "Detail",
"instance": "Instance",
"Invalid Fields": [
{
"field": "Proprty 1",
"code": "01",
"message": "Invalid Proprty 1"
},
{
"field": "Property 2",
"code": "02",
"message": "Invalid Property 2"
}
]
}
I have extened ValidationAttribute
to implement the validation logic for all properties, Below is implementation for Property1
.
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
{
try
{
if (value != null)
{
propertyDisplayName = validationContext.DisplayName;
long property1 = (Int64)value;
Match match = Regex.Match($"{property1}", @"^\d $", RegexOptions.IgnoreCase);
if (!string.IsNullOrWhiteSpace($"{property1}") && match.Success)
{
return ValidationResult.Success;
}
else
{
return new ValidationResult($"Invalid {propertyDisplayName}");
}
}
else
{
return new ValidationResult($"Invalid {propertyDisplayName}");
}
}
catch (Exception ex)
{
...
}
}
If there is a way to handle this scenario in the extended ValidationAttribute
classes also, that will also work for me.
Note: Target framework is .Net5
CodePudding user response:
I could able to solve the issue by using the below code in ConfigureServices
method of Startup.cs.
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Latest).ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = c =>
{
ProblemDetails problemDetails = new ProblemDetails();
problemDetails.Status = StatusCodes.Status400BadRequest;
problemDetails.Title = "One or more validation errors occurred.";
problemDetails.Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1";
List<FieldCodeMessage> codeMessages = new List<FieldCodeMessage>();
foreach (var modelState in c.ModelState)
{
if (modelState.Value.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
{
string[] errorMessageCode = modelState.Value.Errors.Select(a => a.ErrorMessage).FirstOrDefault().Split(':');
string code = errorMessageCode[0];
string message = errorMessageCode[1];
codeMessages.Add(new FieldCodeMessage(field: modelState.Key, code: code, message: message));
}
}
problemDetails.Extensions.Add("Invalid Fields", codeMessages);
return new BadRequestObjectResult(problemDetails);
};
});
I had to use one trick to pass the error code along with the message by using the :
delimiter like this in IsValid
method of the extended ValidationAttribute
.
return new ValidationResult("01:Proprty 1");
If anyone has a better approach or suggestions, please add a comment. I would be happy to know.
CodePudding user response:
My recommendation would be a Middleware library called Hellang.Middleware.ProblemDetails. It's well-documented, popular, and has been around a while. The GitHub repo is here: https://github.com/khellang/Middleware/tree/master/src/ProblemDetails
Or just Google "Hellang problemdetails" and you'll see loads of articles about how to use it to do exactly what you're asking for.