2013-04-09 1 views
9

Я хочу, чтобы мой сервис мог принимать и возвращать типы, полученные из BaseType, не зная, какими будут эти типы. Я почти получил решение, используя пользовательский DataContractResolver на основе SharedTypeResolver from this excellent blog post.Deserialize производные типы в службе WCF как базовые типы, но сохранить информацию о типе

Недостающий фрагмент головоломки заключается в том, что типы, которые будет обрабатывать мои службы, могут не использоваться совместно и не известны службе, но я все же хочу их принять и знать, какой тип должен был быть. Я привел следующий пример службы, которая действует как стек. Вы можете нажать и поместить любой тип, полученный от 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 ' , Deserializer не знает ни одного типа, который соответствует этому названию. Рассмотрим изменение реализации метода ResolveName на вашем DataContractResolver вернуть ненулевое значение для имени «TestWcfClient.UnsharedType» и пространства имен «TestWcfClient, Version = 1.0.0.0, культура = нейтральной, PublicKeyToken = NULL» «. , Пожалуйста, см. InnerException для получения более подробной информации.

Мое текущее мышление, чтобы добавить IExtensibleDataObject в BaseType для хранения значений от неразделенного типа и сделать неразделенный тип выглядеть BaseType к службе на десериализации, когда элемент выталкивается; противоположное должно произойти, когда элемент выскочит. Я просто не знаю, как это сделать. Мои мысли до сих пор о возможных подходах:

  • Дополнительная настройка для DataContractResolver, которые могут включать в себя TypeDelegator
  • Использование IDataContractSurrogate вместо неподеленной типа
  • Каким-то образом сохранить сериализированную XML сервис получил, когда элемент выталкивается , а затем использовать это в ответе, когда элемент выталкивается
  • Использование сообщения инспектора манипулировать сообщениями

Я понятия не имею, будет ли кто-нибудь из них работать, что будет задействовано или что будет лучшим решением. Вы?

+0

Вы будете менять объекты на службе, или вам просто нужно их хранить и отправить обратно клиентам? – Enes

+0

Просто храните и отправляйте обратно. Мне нужно знать о базовом типе, поскольку у него есть некоторые свойства, которые служба использует для влияния на хранилище, но он доступен только для чтения, насколько это касается службы. – batwad

ответ

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) 
    { 
    } 
} 
Смежные вопросы