我正在尝试将几个XAML文档片段作为basicHttpBinding
实例传递给SOAP XElement
Web服务。
[ServiceContract(Namespace = "someNamespace")]
interface IMyService
{
[OperationContract]
void Start(XElement someArgument);
}
消息的结尾格式如下。
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:exec="someNamespace"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<soapenv:Header/>
<soapenv:Body>
<exec:Start>
<exec:someArgument>
<SomeObject xmlns="someAnotherNamespace"
xmlns:myPrefix="yetAnotherNamespace">
<AnotherObject Property="{myPrefix:Foo}" />
</SomeObject>
</exec:someArgument>
</exec:Start>
</soapenv:Body>
</soapenv:Envelope>
一切都很好myPrefix
是在XAML岛的根上定义的(即<SomeObject>
)。但是,如果我将名称空间声明移动到<soapenv:Envelope>
(显然是允许的,禁止任何前缀冲突)
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:exec="someNamespace"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:myPrefix="yetAnotherNamespace">
<soapenv:Header/>
<soapenv:Body>
<exec:Start>
<exec:someArgument>
<SomeObject xmlns="someAnotherNamespace">
<AnotherObject Property="{myPrefix:Foo}" />
</SomeObject>
</exec:someArgument>
</exec:Start>
</soapenv:Body>
</soapenv:Envelope>
结果XElement
不再了解URI yetAnotherNamespace
实际上由前缀myPrefix
表示。而是分配了通用前缀(p2
)。
因此,XamlXmlReader
将无法读取标记扩展名{myPrefix:Foo}
,因为它仍与旧前缀myPrefix
而非p2
相关联。实际上,对于WCF内部的XML处理,标记扩展是一个字符串属性,并没有收到名称空间前缀的特定处理。
成功读取XML岛的行为取决于xmlns:
前缀声明的位置非常容易混淆。 有没有办法以某种方式重新映射前缀以获得更正确的行为?
答案 0 :(得分:0)
玩了一下我意识到如果我使用 XmlElement
而不是XElement
,问题就会消失,因为原始前缀都会被保留。但是,我仍然很好奇是否存在更优雅的解决方案...
不幸的是,从System.Xml.Linq
还原到System.Xml
没有帮助,因为如果命名空间前缀仅用于标记扩展(而不是标记),则其前缀将无法恢复。相反,我实现了一个IDispatchMessageInspector
,它重新声明SOAP信封中所选XML元素的名称空间(从整个信封继承xmlns:
声明),以便使XML的选定片段自包含。
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
namespace RedeclareNamespaces
{
internal class RedeclareNamespacesMessageInspector : IDispatchMessageInspector, IEndpointBehavior
{
private readonly IReadOnlyCollection<string> actionFilters;
private readonly IXmlNamespaceResolver namespaceResolver;
private readonly string targetElements;
public RedeclareNamespacesMessageInspector(IReadOnlyCollection<string> actionFilters,
IXmlNamespaceResolver namespaceResolver, string targetElements)
{
this.actionFilters = actionFilters;
this.namespaceResolver = namespaceResolver;
this.targetElements = targetElements;
}
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
if (ShouldProcessRequest(request))
DoAfterReceiveRequest(ref request);
return null;
}
private bool ShouldProcessRequest(Message request)
{
return actionFilters == null || actionFilters.Contains(request.Headers.Action);
}
private void DoAfterReceiveRequest(ref Message request)
{
var document = ReadXDocument(request);
RedeclareNamespaces(document);
var xmlReader = CreateMessageXmlReader(document);
request = Message.CreateMessage(xmlReader, int.MaxValue, request.Headers.MessageVersion);
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
// Nothing to fix here.
}
private static XDocument ReadXDocument(Message request)
{
// A buffered copy of the message must be created before the whole envelope can be read.
var buffer = request.CreateBufferedCopy(int.MaxValue);
var copyOfRequest = buffer.CreateMessage();
var document = new XDocument();
using (var writer = document.CreateWriter())
{
copyOfRequest.WriteMessage(writer);
writer.Flush();
}
return document;
}
private void RedeclareNamespaces(XDocument document)
{
var elementsToFix = document.XPathSelectElements(targetElements, namespaceResolver);
foreach (var element in elementsToFix)
{
var inheritedXmlnsAttributes = GetInheritedXmlnsAttributes(element);
element.Add(inheritedXmlnsAttributes);
}
}
private object[] GetInheritedXmlnsAttributes(XElement targetElement)
{
var prefixesSeen = new HashSet<string>();
var attributes = new List<object>();
var element = targetElement;
while (element != null)
{
for (var attribute = element.FirstAttribute; attribute != null; attribute = attribute.NextAttribute)
{
string localName = attribute.Name.LocalName;
if (!attribute.IsNamespaceDeclaration || prefixesSeen.Contains(localName))
continue;
prefixesSeen.Add(localName);
// Do not add attributes declared on the element itself twice, but mark them as seen.
if (element != targetElement)
attributes.Add(new XAttribute(attribute));
}
element = element.Parent;
}
return attributes.ToArray();
}
private static XmlReader CreateMessageXmlReader(XDocument document)
{
// Do not dispose this XmlReader and Stream before the message is consumed.
var stream = new MemoryStream();
// We must save the message into a buffer, directly reading the XDocument nodes is not supported by WCF.
document.Save(stream, SaveOptions.DisableFormatting);
stream.Position = 0;
var xmlReader = XmlReader.Create(stream);
return xmlReader;
}
void IEndpointBehavior.Validate(ServiceEndpoint endpoint)
{
// Nothing to do.
}
void IEndpointBehavior.AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
// Nothing to do.
}
void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(this);
}
void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
// Nothing to do.
}
}
public class RedeclareNamespacesConfigurationSection : BehaviorExtensionElement
{
private const string ActionFilterAttribute = "actionFilter";
[ConfigurationProperty(ActionFilterAttribute)]
public string ActionFilter
{
get { return (string) this[ActionFilterAttribute]; }
set { this[ActionFilterAttribute] = value; }
}
private const string NamespacesAttribute = "namespaces";
[ConfigurationProperty(NamespacesAttribute)]
public string Namespaces
{
get { return (string)this[NamespacesAttribute]; }
set { this[NamespacesAttribute] = value; }
}
private const string TargetElementsAttribute = "targetElements";
[ConfigurationProperty(TargetElementsAttribute)]
public string TargetElements
{
get { return (string)this[TargetElementsAttribute]; }
set { this[TargetElementsAttribute] = value; }
}
protected override object CreateBehavior()
{
var actionFilters = ActionFilter?.Split(';').Select(i => i.Trim()).ToArray();
var namespaceResolver = CreateNamespaceResolver();
if (TargetElements == null)
throw new ArgumentNullException(TargetElementsAttribute, "TargetElements query must be provided.");
return new RedeclareNamespacesMessageInspector(actionFilters, namespaceResolver, TargetElements);
}
public override Type BehaviorType => typeof(RedeclareNamespacesMessageInspector);
private XmlNamespaceManager CreateNamespaceResolver()
{
var namespaceResolver = new XmlNamespaceManager(new NameTable());
if (Namespaces != null)
{
foreach (var rawDeclaration in Namespaces.Split(';'))
{
var namespaceDeclaration = rawDeclaration.Trim();
var index = namespaceDeclaration.IndexOf("=");
if (index < 0)
throw new ArgumentException("Namespaces must be of the form \"ns1=uri1;ns2=uri2;...\"",
NamespacesAttribute);
var prefix = namespaceDeclaration.Substring(0, index);
var uri = namespaceDeclaration.Substring(index + 1);
namespaceResolver.AddNamespace(prefix, uri);
}
}
return namespaceResolver;
}
}
}
可以在App.config中使用,如下所示。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<extensions>
<behaviorExtensions>
<add name="redeclareNamespaces"
type="RedeclareNamespaces.RedeclareNamespacesConfigurationSection,RedeclareNamespaces"/>
</behaviorExtensions>
</extensions>
<behaviors>
<endpointBehaviors>
<behavior name="RedeclareNamespacesBehavior">
<redeclareNamespaces actionFilter="http://myExampleUri/IExampleService/Frobnicate"
namespaces="soapenv=http://schemas.xmlsoap.org/soap/envelope/;
myNamespace=http://myExampleUri"
targetElements="/soapenv:Envelope/soapenv:Body//myNamespace:Frobnicate/myNamespace:xamlArgumentHere/*"/>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
不幸的是,消息检查器必须至少复制整个信封两次:首先必须将Message
复制到缓冲区,然后在转换后必须将XDocument
保存到流中。如果我直接将document.CreateReader()
传递给Message
构造函数,由于XML节点流无效,我遇到了一些令人困惑的例外。