我希望我的服务能够接受并返回从BaseType
派生的类型,而不知道这些类型是什么。我几乎已经使用基于SharedTypeResolver from this excellent blog post的自定义DataContractResolver
获得了解决方案。
这个难题的缺失部分是我的服务将处理的类型可能不会被服务共享和知道,但我仍然希望接受它们并且知道该类型应该是什么。我已经提出了以下作为堆栈的服务示例。如果您使用BaseType
并且类型在客户端和服务器之间共享,则可以推送和弹出从SharedTypeResolver
派生的任何类型。
[DataContract]
public class BaseType
{
[DataMember]
public string SomeText { get; set; }
public override string ToString()
{
return this.GetType().Name + ": " + this.SomeText;
}
}
[DataContract]
public class DerivedType : BaseType
{
[DataMember]
public int SomeNumber { get; set; }
public override string ToString()
{
return base.ToString() + ", " + this.SomeNumber;
}
}
[ServiceContract]
public interface ITypeStack
{
[OperationContract]
void Push(BaseType item);
[OperationContract]
BaseType Pop();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class TypeStackService : ITypeStack
{
private Stack<BaseType> stack = new Stack<BaseType>();
public void Push(BaseType item)
{
this.stack.Push(item);
}
public BaseType Pop()
{
return this.stack.Pop();
}
}
这显然是我遇到的问题的一个简化示例。客户端可以非常快乐地推送和弹出BaseType
或DerivedType
,因为客户端和服务器都知道它们,但是如果客户端推送服务不知道的UnsharedType
,那么我会收到错误期待。
格式化程序在尝试反序列化时抛出异常 消息:尝试反序列化参数时出错 http://tempuri.org/:item。 InnerException消息是'错误输入 第1行位置316.元素“http://tempuri.org/:item”包含数据 从映射到名称'TestWcfClient,Version = 1.0.0.0的类型, Culture = neutral,PublicKeyToken = null:TestWcfClient.UnsharedType'。该 反序列化器不知道映射到此名称的任何类型。 考虑更改您的ResolveName方法的实现 DataContractResolver返回name的非null值 'TestWcfClient.UnsharedType'和命名空间'TestWcfClient, Version = 1.0.0.0,Culture = neutral,PublicKeyToken = null'。'。请参阅 InnerException以获取更多详细信息。
我目前的想法是将IExtensibleDataObject
添加到BaseType
以保存非共享类型的值,并在项目被推送时使非共享类型看起来像BaseType
到反序列化服务;当弹出一个项目时,需要进行相反的操作。我只是不确定如何去做。到目前为止我对可能的方法的看法:
DataContractResolver
,可能涉及TypeDelegator
IDataContractSurrogate
代替非共享类型我不知道这些是否有效,涉及的内容或最佳解决方案是什么。你呢?
答案 0 :(得分:3)
我使用消息检查器和实现IExtensibleDataObject
的占位符类型在此方面取得了一些进展。检查器maniuplates传入的消息并将类型提示更改为占位符的类型提示,并将原始类型添加为属性。当然后在回复中发送类型时会发生相反的情况,从而使占位符看起来像原始类型。
我对此解决方案的不满是它与服务绑定,因为我必须包含服务的XML命名空间并明确命名要进行maniuplated的方法和参数。除此之外似乎工作得相当好,虽然我只是在BaseType
派生的相当简单的类型上进行了测试。
有人可以改进吗?这对你有好处。
public class PlaceholderType : BaseType, IExtensibleDataObject
{
[IgnoreDataMember]
public string OriginalTypeName { get; set; }
[IgnoreDataMember]
public string OriginalNamespace { get; set; }
ExtensionDataObject IExtensibleDataObject.ExtensionData { get; set; }
}
public class FunkadelicInspector : IDispatchMessageInspector, IContractBehavior
{
const string PlaceholderNamespace = "http://my.placeholder.namespace";
const string ServiceNamespace = "http://tempuri.org/";
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
XmlDocument xmlDoc = ReadMessage(request);
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);
// Dislike: having to know the service namespace, method and parameters
nsmgr.AddNamespace("s", ServiceNamespace);
XmlNode itemElement = xmlDoc.SelectSingleNode("//s:Push/s:item", nsmgr);
if (itemElement != null)
{
XmlAttribute typeAttribute = itemElement.Attributes["type", "http://www.w3.org/2001/XMLSchema-instance"];
if (typeAttribute != null)
{
// Record original type
string[] parts = typeAttribute.Value.Split(':');
string originalTypeName = parts[1];
// Replace with placeholder type
typeAttribute.Value = parts[0] + ":" + typeof(PlaceholderType).FullName;
// Record original assembly
XmlAttribute nsAtt = itemElement.Attributes["xmlns:" + parts[0]];
string originalAssembly = nsAtt.Value;
// Replace with placeholder type's assembly
nsAtt.Value = typeof(PlaceholderType).Assembly.FullName;
// Add placeholders
itemElement.AppendChild(xmlDoc.CreateElement("OriginalType", PlaceholderNamespace)).InnerText = originalTypeName;
itemElement.AppendChild(xmlDoc.CreateElement("OriginalAssembly", PlaceholderNamespace)).InnerText = originalAssembly;
}
}
//Now recreate the message
request = WriteMessage(request, xmlDoc);
return null;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
XmlDocument xmlDoc = ReadMessage(reply);
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDoc.NameTable);
nsmgr.AddNamespace("s", ServiceNamespace);
nsmgr.AddNamespace("plc", PlaceholderNamespace);
// Dislike: having to know the service namespace, method and parameters
XmlNode resultElement = xmlDoc.SelectSingleNode("//s:PopResponse/s:PopResult", nsmgr);
if (resultElement != null)
{
XmlElement originalType = resultElement.SelectSingleNode("plc:OriginalType", nsmgr) as XmlElement;
XmlElement originalAssembly = resultElement.SelectSingleNode("plc:OriginalAssembly", nsmgr) as XmlElement;
if (originalType != null && originalAssembly != null)
{
// Replace original type
XmlAttribute type = resultElement.Attributes["type", "http://www.w3.org/2001/XMLSchema-instance"];
string[] parts = type.Value.Split(':'); // 0 is an alias for the assembly, 1 is the type
type.Value = parts[0] + ":" + originalType.InnerText;
// Replace original assembly
XmlAttribute ass = resultElement.Attributes["xmlns:" + parts[0]];
ass.Value = originalAssembly.InnerText;
// Remove placeholders
resultElement.RemoveChild(originalType);
resultElement.RemoveChild(originalAssembly);
}
}
//Now recreate the message
reply = WriteMessage(reply, xmlDoc);
}
private static Message WriteMessage(Message original, XmlDocument xmlDoc)
{
MemoryStream ms = new MemoryStream();
xmlDoc.Save(ms);
ms.Position = 0;
XmlReader reader = XmlReader.Create(ms);
Message newMessage = Message.CreateMessage(reader, int.MaxValue, original.Version);
newMessage.Properties.CopyProperties(original.Properties);
return newMessage;
}
private static XmlDocument ReadMessage(Message message)
{
MemoryStream ms = new MemoryStream();
using (XmlWriter writer = XmlWriter.Create(ms))
{
message.WriteMessage(writer); // the message was consumed here
writer.Flush();
}
ms.Position = 0;
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(ms);
return xmlDoc;
}
void IContractBehavior.AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
void IContractBehavior.ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
}
void IContractBehavior.ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
{
dispatchRuntime.MessageInspectors.Add(this);
}
void IContractBehavior.Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
{
}
}