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 文档以了解在反序列化之前可以使用哪些自定义挂钩:
IAppHost.PreRequestFilters
在反序列化请求 DTO 之前执行
- 默认请求 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);
}
}
问题:
使用 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 文档以了解在反序列化之前可以使用哪些自定义挂钩:
IAppHost.PreRequestFilters
在反序列化请求 DTO 之前执行- 默认请求 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);
}
}