WCF+XmlSerializer, leading spaces on [XmlText] field are trimmed
-
mardi 17 avril 2012 18:56
One of products uses XmlSerialier with WCF. Recently we found a deserialization issues that is related to field/property of string type that is decorated with XmlTextAttribute. The symptom is when a message is deserialized, the leading spaces of the field/property gets trimmed.
However when we did stand-alone XmlSerializer tests outside of WCF context, deserialization works without such issue.
I have complete test projects that demonstrate the problem, which I can sent at request.
[System.Xml.Serialization.XmlTextAttribute()] public string Value { get { return this.valueField; } set { this.valueField = value; } }
Toutes les réponses
-
mercredi 18 avril 2012 07:32Modérateur
I did a simple sample that it uses XmlSerialier with WCF and also apply XmlText attribute to the field/property on my computer, but I cannot reproduce your above described scenario. Could you post a simple sample that I can reproduce the above scenario?
Thanks
Please mark the replies as answers if they help or unmark if not. If you have any feedback about my replies, please contact msdnmg@microsoft.com Microsoft One Code Framework
-
mercredi 18 avril 2012 13:16
Thanks for looking, here's the sample code:
[System.ServiceModel.ServiceContractAttribute(Namespace = "http://webservices.csgsystems.com/slbos/ccs", Name = "CcsUpstreamManagementService")] public interface ICcsUpstreamManagement { [System.ServiceModel.OperationContractAttribute(Action = "http://webservices.csgsystems.com/slbos/ccs/GetCCSEquipmentOperationRequest", ReplyAction = "http://webservices.csgsystems.com/slbos/ccs/GetCCSEquipmentOperationResponse")] [System.ServiceModel.XmlSerializerFormatAttribute(SupportFaults = true)] GetCCSEquipmentOperationResponse GetCCSEquipmentOperation(GetCCSEquipmentOperationRequest request); }
[System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://webservices.csgsystems.com/slbos/ccs")] public class CONV_UNIT_ADDR { private string iDXField; private string valueField;
[System.Xml.Serialization.XmlAttributeAttribute()] public string IDX { get { return this.iDXField; } set { this.iDXField = value; } } [System.Xml.Serialization.XmlTextAttribute()] public string Value { get { return this.valueField; } set { this.valueField = value; } } } [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://webservices.csgsystems.com/slbos/ccs")] public class EqptComponent_Data { private string cOMPONENT_STATUSField; private CONV_UNIT_ADDR cONV_UNIT_ADDRField;
[System.Xml.Serialization.XmlElementAttribute(Order = 1)] public string COMPONENT_STATUS { get { return this.cOMPONENT_STATUSField; } set { this.cOMPONENT_STATUSField = value; } } [System.Xml.Serialization.XmlElementAttribute(Order = 2)] public CONV_UNIT_ADDR CONV_UNIT_ADDR { get { return this.cONV_UNIT_ADDRField; } set { this.cONV_UNIT_ADDRField = value; } } } [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://webservices.csgsystems.com/slbos/ccs")] public class GetCCSEquipment_Info { private EqptComponent_Data[] eqptComponent_DataField; [System.Xml.Serialization.XmlElementAttribute("EqptComponent_Data", Order = 61)] public EqptComponent_Data[] EqptComponent_Data { get { return this.eqptComponent_DataField; } set { this.eqptComponent_DataField = value; } } }
[System.ServiceModel.MessageContractAttribute(WrapperName = "GetCCSEquipment", WrapperNamespace = "http://webservices.csgsystems.com/slbos/ccs", IsWrapped = true)] [System.SerializableAttribute()] public class GetCCSEquipmentOperationRequest { }
[System.ServiceModel.MessageContractAttribute(WrapperName = "GetCCSEquipment_Resp", WrapperNamespace = "http://webservices.csgsystems.com/slbos/ccs", IsWrapped = true)] [System.SerializableAttribute()] public partial class GetCCSEquipmentOperationResponse { [System.ServiceModel.MessageBodyMemberAttribute(Namespace = "http://webservices.csgsystems.com/slbos/ccs", Order = 2)] public GetCCSEquipment_Info GetCCSEquipment_Info;
public GetCCSEquipmentOperationResponse() { }
public GetCCSEquipmentOperationResponse(GetCCSEquipment_Info GetCCSEquipment_Info) { this.GetCCSEquipment_Info = GetCCSEquipment_Info; } }
[ServiceBehavior(Namespace = "http://webservices.csgsystems.com/slbos", Name = "EquipmentManagementService")] public class CcsUpstreamManagement : ICcsUpstreamManagement { public GetCCSEquipmentOperationResponse GetCCSEquipmentOperation(GetCCSEquipmentOperationRequest request) { var equipmentComponents = new EqptComponent_Data[2] { new EqptComponent_Data { COMPONENT_STATUS = " First Status ", CONV_UNIT_ADDR = new CONV_UNIT_ADDR { IDX = "2", Value = " 2525 N. 117th Ave. ", }, }, new EqptComponent_Data { COMPONENT_STATUS = " Second Status ", CONV_UNIT_ADDR = new CONV_UNIT_ADDR { IDX = "20", Value = "15113 Whitmore Cir.", }, }, }; var response = new GetCCSEquipmentOperationResponse { GetCCSEquipment_Info = new GetCCSEquipment_Info { EqptComponent_Data = equipmentComponents, }, }; return response; } } }
class Program { static void Main(string[] args) { const string url = "http://localhost:9999/CcsUpstreamManagement"; var serviceHost = new ServiceHost(typeof(CcsUpstreamManagement), new Uri(url)); serviceHost.AddServiceEndpoint(typeof(ICcsUpstreamManagement), new BasicHttpBinding(), url); serviceHost.Open(); var factory = new ChannelFactory<ICcsUpstreamManagement>(new BasicHttpBinding()); var channel = factory.CreateChannel(new EndpointAddress(url)); var response = channel.GetCCSEquipmentOperation(new GetCCSEquipmentOperationRequest()); serviceHost.Close(); } }
-
jeudi 19 avril 2012 03:51Modérateur
I applied your above code in my project, In my solution, I added a web application and add service reference to this WCF service, and when I debug the client code and found the leading space of the field/property decorated with XmlTextAttribute gets trimmed. and the return type of this WCF method is array and does not has any parameter.
using (ServiceReference3.CcsUpstreamManagementServiceClient client1 = new ServiceReference3.CcsUpstreamManagementServiceClient()) { GetCCSEquipment_RespEqptComponent_Data[] data = client1.GetCCSEquipmentOperation(); }and the following GetCCSEquipmentOperation method code in the Reference.cs.
public GetCCSEquipment_RespEqptComponent_Data[] GetCCSEquipmentOperation() { WebApp.ServiceReference3.GetCCSEquipmentOperationRequest inValue = new WebApp.ServiceReference3.GetCCSEquipmentOperationRequest(); WebApp.ServiceReference3.GetCCSEquipmentOperationResponse retVal = ((WebApp.ServiceReference3.CcsUpstreamManagementService)(this)).GetCCSEquipmentOperation(inValue); return retVal.GetCCSEquipment_Info; }If you wrapped the EqptComponent_Data array in one class, as for your example, you have already wrapped the EqptComponent_Data[] in the GetCCSEquipment_Info class and GetCCSEquipment_Info wrapped in the GetCCSEquipmentOperationResponse, and you use GetCCSEquipmentOperationResponse as return type for your WCF method, then you will find the leading space of the field/property does not trim.
If you use Fiddler to see the request and response message (you need to change the URL of wcf http://ipv4.fiddler/UpstreamManagement/CcsUpstreamManagement.svc, since I hosted this WCF service in local IIS). then you also can found the text of CONV_UNIT_ADDR will contains a leading space.
Please mark the replies as answers if they help or unmark if not. If you have any feedback about my replies, please contact msdnmg@microsoft.com Microsoft One Code Framework
-
jeudi 19 avril 2012 13:16
Peter, thank you for running the testing.
I agree the messages on the wire look good based on the Fiddler trace, that's why I believe there's a bug on deserialization side. I don't believe array in the response message is causing the trimming. I took out the arrays and found that the spaces are still trimmed(see code below).
namespace Contract { [System.ServiceModel.ServiceContractAttribute(Namespace = "http://webservices.csgsystems.com/slbos/ccs", Name = "CcsUpstreamManagementService")] public interface ICcsUpstreamManagement // : IServiceDiagnostics { [System.ServiceModel.OperationContractAttribute(Action = "http://webservices.csgsystems.com/slbos/ccs/GetCCSEquipmentOperationRequest", ReplyAction = "http://webservices.csgsystems.com/slbos/ccs/GetCCSEquipmentOperationResponse")] [System.ServiceModel.XmlSerializerFormatAttribute(SupportFaults = true)] GetCCSEquipmentOperationResponse GetCCSEquipmentOperation(GetCCSEquipmentOperationRequest request); } }
namespace Contract { [System.SerializableAttribute()] [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://webservices.csgsystems.com/slbos/ccs")] public class CONV_UNIT_ADDR { private string iDXField; private string valueField; [System.Xml.Serialization.XmlAttributeAttribute()] public string IDX { get { return this.iDXField; } set { this.iDXField = value; } } [System.Xml.Serialization.XmlTextAttribute()] public string Value { get { return this.valueField; } set { this.valueField = value; } } } [System.ServiceModel.MessageContractAttribute(WrapperName = "GetCCSEquipment", WrapperNamespace = "http://webservices.csgsystems.com/slbos/ccs", IsWrapped = true)] [System.SerializableAttribute()] public class GetCCSEquipmentOperationRequest { } [System.ServiceModel.MessageContractAttribute(WrapperName = "GetCCSEquipment_Resp", WrapperNamespace = "http://webservices.csgsystems.com/slbos/ccs", IsWrapped = true)] [System.SerializableAttribute()] public class GetCCSEquipmentOperationResponse { [System.ServiceModel.MessageBodyMemberAttribute(Namespace = "http://webservices.csgsystems.com/slbos/ccs", Order = 2)] public CONV_UNIT_ADDR Address; public GetCCSEquipmentOperationResponse() { } public GetCCSEquipmentOperationResponse(CONV_UNIT_ADDR address) { Address = address; } } }
namespace Service { [ServiceBehavior(Namespace = "http://webservices.csgsystems.com/slbos", Name = "EquipmentManagementService")] public class CcsUpstreamManagement : ICcsUpstreamManagement { public GetCCSEquipmentOperationResponse GetCCSEquipmentOperation(GetCCSEquipmentOperationRequest request) { var address = new CONV_UNIT_ADDR { IDX = "2", Value = " 2525 N. 117th Ave. ", }; var response = new GetCCSEquipmentOperationResponse(address); return response; } } }
namespace TestDriver { class Program { static void Main(string[] args) { const string url = "http://qiuw01-w764:9999/CcsUpstreamManagement"; var serviceHost = new ServiceHost(typeof(CcsUpstreamManagement), new Uri(url)); serviceHost.AddServiceEndpoint(typeof(ICcsUpstreamManagement), new BasicHttpBinding(), url); serviceHost.Open(); var factory = new ChannelFactory<ICcsUpstreamManagement>(new BasicHttpBinding()); var channel = factory.CreateChannel(new EndpointAddress(url)); var response = channel.GetCCSEquipmentOperation(new GetCCSEquipmentOperationRequest()); serviceHost.Close(); } } }
- Modifié Qiu, Wenning jeudi 19 avril 2012 14:46
-
vendredi 20 avril 2012 02:17ModérateurI apply your above sample code and also found the same scenario that the leading space will trim while deserialization. and I also did a simple sample apply XmlSerializer to the custom object and found deserialization will not trim the leading space and the value is the same as before serizlization.
Please mark the replies as answers if they help or unmark if not. If you have any feedback about my replies, please contact msdnmg@microsoft.com Microsoft One Code Framework
-
vendredi 20 avril 2012 02:38Exactly, the stand-alone XmlSerializer works just fine. The issues occurs when it is used in WCF context. It appears to me like a bug.
-
mercredi 25 avril 2012 05:15
I have some different result, when apply the XmlText attribute, the leading space are trimed in both local or remote.
static void Main(string[] args) { const string url = "http://localhost:9999/CcsUpstreamManagement"; var serviceHost = new ServiceHost(typeof(CcsUpstreamManagement), new Uri(url)); serviceHost.AddServiceEndpoint(typeof(ICcsUpstreamManagement), new BasicHttpBinding(), url); serviceHost.Open(); var factory = new ChannelFactory<ICcsUpstreamManagement>(new BasicHttpBinding()); var channel = factory.CreateChannel(new EndpointAddress(url)); var response = channel.GetCCSEquipmentOperation(new GetCCSEquipmentOperationRequest()); string content = null; using (MemoryStream stream = new MemoryStream()) { XmlSerializer serializer = new XmlSerializer(response.GetType()); serializer.Serialize(stream, response); stream.Seek(0, SeekOrigin.Begin); using (StreamReader reader = new StreamReader(stream, Encoding.UTF8)) { content = reader.ReadToEnd(); } } byte[] byteArray = Encoding.UTF8.GetBytes(content); object obj = null; using (MemoryStream stream = new MemoryStream(byteArray)) { XmlSerializer serializer = new XmlSerializer(typeof(GetCCSEquipmentOperationResponse)); obj = serializer.Deserialize(stream); } serviceHost.Close(); } -
jeudi 20 décembre 2012 10:34
I am really suffering from this behavior. Has anybody out there a workaround to prevent the WCF client from killing the leading blanks?
-
jeudi 20 décembre 2012 13:34
It looks like this bug isn't fixed in 4.5 either.
So, if anybody uses MTOM with Security=None in your binding and you want to transfer string-properties without loosing the leading whitespaces this is my actual workaround:
public string StringProperty
{
get
{
return this.StringPropertySerialized.Replace((char)0, ' ');
}
set
{
this.StringPropertySerialized = value.Replace(' ', (char)0);
}
}
string mStringPropertySerialized = "";
[DataMember]
public string StringPropertySerialized
{
get
{
return this.mStringPropertySerialized;
}
set
{
this.mStringPropertySerialized = value;
}
}

