ServiceStack - 在映射到 DTO 之前验证 json 数据

ServiceStack - validate json data before it is mapped to a DTO

问题:

使用 ServiceStack,是否可以在将数据(通过 ServiceStack)映射到 DTO 之前验证 JSON 数据?

示例:

我的 DTO 形状:

public class ExampleDto
{
  public int? MyValue {get;set;}
}

示例(probalamatic)有效载荷:

{
  "MyValue": "BOB"
}

问题:

我的问题是我的 API 的使用者没有正确查看文档,并试图传递一个字符串,而 ServiceStack 映射期望映射一个可为 null 的整数。这只是通过 NULL.

我在我的 API 中使用了非常酷的 validation feature,但这只会在 数据(由我的 API) 映射到 DTO。据我所知,它没有看到用户试图传递无法映射到 DTO 的值。

ServiceStack 中是否有任何方法可以验证任何潜在的序列化错误?

理想情况下,我希望能够 return 与 FluentValidation 功能 return 相同的错误列表中的不匹配序列化以保持一致性,但我会接受不允许最终用户能够提出这种请求。

更新:为了更容易支持这种情况,我在 this commit 中添加了对重写 JSON 反序列化的支持,您可以通过重写来拦截 JSON 反序列化OnDeserializeJson() 在您的 AppHost 中,例如:

class AppHost : AppHostBase
{
    //..
    public override object OnDeserializeJson(Type intoType, Stream fromStream)
    {
        if (MyShouldValidate(intoType))
        {
            var ms = MemoryStreamFactory.GetStream();
            fromStream.CopyTo(ms); // copy forward-only stream
            ms.Position = 0;
            var json = ms.ReadToEnd();
            // validate json...
            fromStream = ms; // use buffer
        }

        return base.OnDeserializeJson(intoType, fromStream);
    }    
}

此更改适用于 v5.12.1+,即现在 available on MyGet

这也可以在早期版本中通过注册 custom JSON Format 来实现,这实际上是通过将现有的 JSON 反序列化方法路由到可覆盖的 OnDeserializeJson().


您应该参考 Order of Operation 文档以了解在反序列化之前可以使用哪些自定义挂钩:

  1. IAppHost.PreRequestFilters 在反序列化请求 DTO 之前执行
  2. 默认请求 DTO 绑定或Custom Request Binding(如果已注册)

但是如果你想在 PreRequestFilters 中读取 JSON 请求主体,你需要 Buffer the Request 然后你可以读取 JSON 来反序列化和验证自己做。

appHost.PreRequestFilters.Add((httpReq, httpRes) => {
    if (!httpReq.PathInfo.StartsWith("/my-example")) continue;
    httpReq.UseBufferedStream = true;  // Buffer Request Input

    var json = httpReq.GetRawBody();
    // validate JSON ...
    if (badJson) {
        //Write directly to Response
        httpRes.StatusCode = (int)HttpStatusCode.BadRequest;
        httpRes.StatusDescription = "Bad Request, see docs: ...";
        httpRes.EndRequest();

        //Alternative: Write Exception Response
        //httpRes.WriteError(new ArgumentException("Bad Field","field"));            
    }
});

如果请求有效 JSON,但对类型无效,您可以将 JSON 解析为无类型字典并使用 JS Utils 检查其值,例如:

try {
    var obj = (Dictionary<string, object>) JSON.parse(json);
} catch (Exception ex) {
    // invalid JSON
}

或者你可以接管请求的反序列化 Custom Request Binding,即:

base.RegisterRequestBinder<ExampleDto>(httpReq => ... /*ExampleDto*/);

这会抛出它自己的异常,尽管你应该注意 ServiceStack APIs 反序列化它的 Request DTO from multiple sources 所以如果你只检查 JSON 请求体它会被忽略API 的所有其他调用方式。

接管请求 DTO 反序列化的另一种方法是让您的服务 read directly from the Request Stream 通过让您的请求 DTO 实现 IRequiresRequestStream 告诉 ServiceStack 跳过反序列化,这样您的服务就可以做到这一点并且手动验证它,例如:

public class ExampleDto : IRequiresRequestStream
{
    public Stream RequestStream { get; set; }
    public int? MyValue {get;set;}
}

public class MyServices : Service
{
    public IValidator<ExampleDto> Validator { get; set; }

    public async Task<object> PostAsync(ExampleDto request)
    {
        var json = await request.RequestStream.ReadToEndAsync();
        // validate JSON...
        // deserialize into request DTO and validate manuallly
        var dto = json.FromJson<ExampleDto>();
        await Validator.ValidateAndThrowAsync(dto, ApplyTo.Post);
    }
}