在ServiceStack FluentValidation中自定义响应DTO

本文关键字:响应 DTO 自定义 ServiceStack FluentValidation | 更新日期: 2023-09-27 18:09:39

我正在评估ServiceStack中的FluentValidation处理请求dto的自动验证:

Plugins.Add(new ValidationFeature());
container.RegisterValidators(typeof(MyValidator).Assembly);

错误通过序列化ErrorResponse DTO返回给客户端,可能如下所示:

{
    "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."
        }
    ]
}

我想知道是否有可能使用不同的响应DTO返回错误。例如:

{
    "code": "123",
    "error": "'Age' must be greater than '0'."
}

我知道可以在服务中显式地使用验证器:

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
    }
}

但这里的问题是,是否有可能在某个地方隐式拦截此验证错误,以便我可以替换响应DTO并拥有更干净的服务:

public MyService : Service
{
    public object Get(MyRequestDto request)
    {
        ... at this stage request DTO validation has passed
    }
}

更新:

在进一步挖掘源代码之后,看起来这是烧到ValidationFeature中,更具体地说是它注册的请求过滤器:
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);
    }
}

通过编写自定义验证特性,我能够达到预期的效果。但也许有更优雅的方式?

在ServiceStack FluentValidation中自定义响应DTO

我刚刚检查了一个自定义错误钩子,它允许你指定一个自定义错误过滤器,以便在下一个版本的ServiceStack (v3.9.44+)中更容易支持这个用例。

从CustomValidationErrorTests中,你现在可以配置ValidationFeature来使用一个自定义错误过滤器,它将被返回,例如:

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);
}

现在您的HTTP客户端将收到您自定义的错误响应:

try
{
    var response = "http://example.org/customerror".GetJsonFromUrl();
}
catch (Exception ex)
{
    ex.GetResponseBody().Print(); 
    //{"code":"GreaterThan","error":"'Age' must be greater than '0'."}
}

警告:当以这种方式定制错误响应时,ServiceStack的类型化c#客户端将不再提供类型化异常,因为它们期望错误响应包含ResponseStatus DTO属性。