Sla*_*uma 4 asp.net-mvc xsd xml-validation asp.net-mvc-4 asp.net-web-api
我试图找到一个解决方案来验证POST请求中发送的XML数据是否满足给定的自定义XML模式.
如果我使用XmlMediaTypeFormatterASP.NET Web API提供的,我没有可用的架构验证,据我所知.例如:如果我有型号类型......
public class Order
{
public string Code { get; set; }
public int Quantity { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
...并在一个POST操作ApiController...
public HttpResponseMessage Post(Order order)
{
if (ModelState.IsValid)
{
// process order...
// send 200 OK response for example
}
else
// send 400 BadRequest response with ModelState errors in response body
}
Run Code Online (Sandbox Code Playgroud)
...我可以发布以下"错误的"XML数据,但仍然会得到200 OK响应:
User-Agent: Fiddler
Host: localhost:45678
Content-Type: application/xml; charset=utf-8
<Order> <Code>12345</Nonsense> </Order> // malformed XML
Run Code Online (Sandbox Code Playgroud)
要么:
<Order> <CustomerName>12345</CustomerName> </Order> // invalid property
Run Code Online (Sandbox Code Playgroud)
要么:
<Customer> <Code>12345</Code> </Customer> // invalid root
Run Code Online (Sandbox Code Playgroud)
要么:
"Hello World" // no XML at all
Run Code Online (Sandbox Code Playgroud)
等等
我对请求进行验证的唯一一点是模型绑定:在请求示例1,3和4中,order传递给Post方法的是null,在示例2中,order.Code属性是null我可以通过测试order == null或通过标记Code属性来使其无效[Required]属性.我可以在响应中使用400"BadRequest"Http状态代码和响应正文中的验证消息将此验证结果发回.但我无法确切地说出错误是什么,并且无法区分示例1,3和4中的错误XML(没有order发布,这是我能看到的唯一内容) - 例如.
例如,要求Order必须使用特定的自定义XML模式发布xmlns="http://test.org/OrderSchema.xsd",我想验证发布的XML是否对此模式有效,如果不是,则在响应中发回模式验证错误.为了达到这个目的,我开始使用自定义MediaTypeFormatter:
public class MyXmlMediaTypeFormatter : MediaTypeFormatter
{
// constructor, CanReadType, CanWriteType, ...
public override Task<object> ReadFromStreamAsync(Type type, Stream stream,
HttpContentHeaders contentHeaders, IFormatterLogger formatterLogger)
{
var task = Task.Factory.StartNew(() =>
{
using (var streamReader = new StreamReader(stream))
{
XDocument document = XDocument.Load(streamReader);
// TODO: exceptions must the catched here,
// for example due to malformed XML
XmlSchemaSet schemaSet = new XmlSchemaSet();
schemaSet.Add(null, "OrderSchema.xsd");
var msgs = new List<string>();
document.Validate(schemaSet, (s, e) => msgs.Add(e.Message));
// msgs contains now the list of XML schema validation errors
// I want to send back in the response
if (msgs.Count == 0)
{
var order = ... // deserialize XML to order
return (object)order;
}
else
// WHAT NOW ?
}
});
return task;
}
}
Run Code Online (Sandbox Code Playgroud)
只要一切都正确,这项工作到目前为止.
但我不知道该怎么办msgs.Count > 0.如何将此验证结果列表"转移"到该Post操作,或者如何创建包含这些XML架构验证消息的Http响应?
此外,我不确定自定义MediaTypeFormatter是否是这种XML模式验证的最佳扩展点,如果我的方法不是错误的方法.可能是一个习惯HttpMessageHandler/ DelegatingHandler是一个更好的地方吗?或者是否有更简单的开箱即用的东西?
如果我这样做,我就不会使用Formatter.格式化程序的主要目标是将线表示转换为CLR类型.在这里,您有一个XML文档,您希望根据一个完全不同的任务的模式进行验证.
我建议创建一个新的MessageHandler来进行验证.派生自DelegatingHandler,如果内容类型application/xml将内容加载到XDocument并进行验证.如果失败,则抛出HttpResponseException.
只需将MessageHandler添加到Configuration.MessageHandlers集合中即可进行设置.
使用派生的XmlMediaTypeFormatter的问题在于,您现在正在ObjectContent代码中嵌入的某个点执行,并且干净地退出可能很棘手.此外,使XmlMediaTypeFormatter更复杂可能不是一个好主意.
我有一个创建MessageHandler的工作.我实际上并没有尝试运行此代码,所以买家要小心.此外,如果你避免阻止调用者,任务的东西会变得很毛茸茸.也许有人会为我清理那些代码,无论如何它就是这样.
public class SchemaValidationMessageHandler : DelegatingHandler {
private XmlSchemaSet _schemaSet;
public SchemaValidationMessageHandler() {
_schemaSet = new XmlSchemaSet();
_schemaSet.Add(null, "OrderSchema.xsd");
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
if (request.Content != null && request.Content.Headers.ContentType.MediaType == "application/xml")
{
var tcs = new TaskCompletionSource<HttpResponseMessage>();
var task = request.Content.LoadIntoBufferAsync() // I think this is needed so XmlMediaTypeFormatter will still have access to the content
.ContinueWith(t => {
request.Content.ReadAsStreamAsync()
.ContinueWith(t2 => {
var doc = XDocument.Load(t2.Result);
var msgs = new List<string>();
doc.Validate(_schemaSet, (s, e) => msgs.Add(e.Message));
if (msgs.Count > 0) {
var responseContent = new StringContent(String.Join(Environment.NewLine, msgs.ToArray()));
tcs.TrySetException(new HttpResponseException(
new HttpResponseMessage(HttpStatusCode.BadRequest) {
Content = responseContent
}));
} else {
tcs.TrySetResult(base.SendAsync(request, cancellationToken).Result);
}
});
});
return tcs.Task;
} else {
return base.SendAsync(request, cancellationToken);
}
}
Run Code Online (Sandbox Code Playgroud)
通过反复试验,我找到了一个解决方案(对于WHAT NOW ?问题代码中的占位符):
//...
else
{
PostOrderErrors errors = new PostOrderErrors
{
XmlValidationErrors = msgs
};
HttpResponseMessage response = new HttpResponseMessage(
HttpStatusCode.BadRequest);
response.Content = new ObjectContent(typeof(PostOrderErrors), errors,
GlobalConfiguration.Configuration.Formatters.XmlFormatter);
throw new HttpResponseException(response);
}
Run Code Online (Sandbox Code Playgroud)
...响应类如下:
public class PostOrderErrors
{
public List<string> XmlValidationErrors { get; set; }
//...
}
Run Code Online (Sandbox Code Playgroud)
这似乎有效,然后响应如下所示:
HTTP/1.1 400 Bad Request
Content-Type: application/xml; charset=utf-8
Run Code Online (Sandbox Code Playgroud)
//...
else
{
PostOrderErrors errors = new PostOrderErrors
{
XmlValidationErrors = msgs
};
HttpResponseMessage response = new HttpResponseMessage(
HttpStatusCode.BadRequest);
response.Content = new ObjectContent(typeof(PostOrderErrors), errors,
GlobalConfiguration.Configuration.Formatters.XmlFormatter);
throw new HttpResponseException(response);
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
5615 次 |
| 最近记录: |