I am evaluating FluentValidation in ServiceStack for handling automatic validation of request DTOs:
Plugins.Add(new ValidationFeature());
container.RegisterValidators(typeof(MyValidator).Assembly);
Errors are returned to the client by serializing an ErrorResponse
DTO and may look like this:
{
"ErrorCode": "GreaterThan",
"Message": "'Age' must be greater than '0'.",
"Errors": [
{
"ErrorCode": "GreaterThan",
"FieldName": "Age",
"Message": "'Age' must be greater than '0'."
},
{
"ErrorCode": "NotEmpty",
"FieldName": "Company",
"Message": "'Company' should not be empty."
}
]
}
I would like to know if it is possible to return the errors using a different response DTO. For example:
{
"code": "123",
"error": "'Age' must be greater than '0'."
}
I know that it is possible to use the validator explicitly in the service:
public MyService : Service
{
private readonly IValidator<MyRequestDto> validator;
public MyService(IValidator<MyRequestDto> validator)
{
this.validator = validator;
}
public object Get(MyRequestDto request)
{
var result = this.validator.Validate(request);
if (!result.IsValid)
{
throw new SomeCustomException(result);
}
... at this stage request DTO validation has passed
}
}
But the question here is whether it is possible to have this validation error intercepted implicitly somewhere so that I can replace the response DTO and have a cleaner service:
public MyService : Service
{
public object Get(MyRequestDto request)
{
... at this stage request DTO validation has passed
}
}
UPDATE:
After further digging into the source code, it looks like that this is burnt into the ValidationFeature
and more specifically the request filter that it registers:
public class ValidationFilters
{
public void RequestFilter(IHttpRequest req, IHttpResponse res, object requestDto)
{
var validator = ValidatorCache.GetValidator(req, requestDto.GetType());
if (validator == null) return;
var validatorWithHttpRequest = validator as IRequiresHttpRequest;
if (validatorWithHttpRequest != null)
validatorWithHttpRequest.HttpRequest = req;
var ruleSet = req.HttpMethod;
var validationResult = validator.Validate(
new ValidationContext(requestDto, null, new MultiRuleSetValidatorSelector(ruleSet)));
if (validationResult.IsValid) return;
var errorResponse = DtoUtils.CreateErrorResponse(
requestDto, validationResult.ToErrorResult());
res.WriteToResponse(req, errorResponse);
}
}
By writing a custom validation feature I was able to achieve the desired effect. But maybe there's a more elegant way?
I've just checked in a Customization Error hook that allows you to specify a custom error filter to make it easier to support this use-case in the next version of ServiceStack (v3.9.44+).
From the CustomValidationErrorTests you can now configure the ValidationFeature to use a custom error filter which will be returned instead, e.g:
public override void Configure(Container container)
{
Plugins.Add(new ValidationFeature {
ErrorResponseFilter = CustomValidationError });
container.RegisterValidators(typeof(MyValidator).Assembly);
}
public static object CustomValidationError(
ValidationResult validationResult, object errorDto)
{
var firstError = validationResult.Errors[0];
var dto = new MyCustomErrorDto {
code = firstError.ErrorCode, error = firstError.ErrorMessage };
//Ensure HTTP Clients recognize this as an HTTP Error
return new HttpError(dto, HttpStatusCode.BadRequest, dto.code, dto.error);
}
Now your HTTP Clients will receive your customized error response:
try
{
var response = "http://example.org/customerror".GetJsonFromUrl();
}
catch (Exception ex)
{
ex.GetResponseBody().Print();
//{"code":"GreaterThan","error":"'Age' must be greater than '0'."}
}
Warning: When customizing the Error Response in this way ServiceStack's typed C# clients will no longer provide typed exceptions as they expect the Error Response to contain a ResponseStatus DTO property.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With