Why can't I deserialize my object if the xml it contains gets slightly larger? - serialization

I am having an issue trying to send a message to the Azure service bus using the REST API and have it received using the .NET azure service bus client API classes. I can happily send and receive messages using these objects using the SDK alone, but get problems when trying to use the REST api.
I have narrowed the issue down to this minimal repro I think. Basically if I serialize my object with a small amoutn of XML as the payload thee everything works ok. If I add slightly more XML then I get an exception deserializing it. this should cut&paste into a new console application to allow repro of the issue:
using System.IO;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Xml;
using System.Xml.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main(string[] args)
{
GetMessageBytes(Guid.NewGuid());
}
private static byte[] GetMessageBytes(Guid requestId)
{
var payload = XElement.Parse(#"
<blah Type='TransactionRequest' Version='1' xmlns=''>
<SomeData Type='Request' Selection='All'></SomeData>
</blah>");
var invalidPayload = XElement.Parse(#"
<blah Type='TransactionRequest' Version='1' xmlns=''>
<SomeData Type='Request' Selection='All'></SomeData>
<SomeData Type='Request' Selection='All'></SomeData>
<SomeData Type='Request' Selection='All'></SomeData>
<SomeData Type='Request' Selection='All'></SomeData>
<SomeData Type='Request' Selection='All'></SomeData>
</blah>");
Message<XElement> message = new Message<XElement>()
{
Label = "Label",
RequestId = requestId,
Payload = payload
};
var messageBytes = EncodeMessage(message);
var expectedResponse = DecodeMessage<Message<XElement>>(messageBytes);
message = new Message<XElement>()
{
Label = "Label",
RequestId = requestId,
Payload = invalidPayload
};
messageBytes = EncodeMessage(message);
expectedResponse = DecodeMessage<Message<XElement>>(messageBytes);
Console.WriteLine(expectedResponse);
return messageBytes;
}
private static byte[] EncodeMessage<T>(T message)
{
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
var memoryStream = new MemoryStream();
XmlDictionaryWriter binaryDictionaryWriter = XmlDictionaryWriter.CreateBinaryWriter(memoryStream);
serializer.WriteObject(binaryDictionaryWriter, message);
binaryDictionaryWriter.Flush();
var bytesToPost = memoryStream.GetBuffer();
return bytesToPost;
}
private static T DecodeMessage<T>(byte[] response)
{
var ms = new MemoryStream(response);
var serializer = new DataContractSerializer(typeof(T));
XmlDictionaryReader binaryDictionaryReader = XmlDictionaryReader.CreateBinaryReader(ms, XmlDictionaryReaderQuotas.Max);
var message = serializer.ReadObject(binaryDictionaryReader);
return (T)message;
}
}
[MessageContract(WrapperName = "Message", WrapperNamespace = "http://blah.co.uk/contracts", IsWrapped = true)]
public sealed class Message<T> where T : class
{
[MessageHeader(Namespace = "http://blah.co.uk/contracts", Name = "RequestId")]
public Guid RequestId { get; set; }
[MessageHeader(Namespace = "http://blah.co.uk/contracts", Name = "Label")]
public string Label { get; set; }
[MessageBodyMember(Namespace = "http://blah.co.uk/contracts", Name = "Payload")]
public T Payload { get; set; }
[MessageBodyMember(Namespace = "http://blah.co.uk/contracts", Name = "MonitoringResults")]
public MessageTimestamp MessageTimestamp { get; set; }
}
[DataContract]
public class MessageTimestamp
{
private ICollection<Timestamp> timestamps = new List<Timestamp>();
[DataMember]
public ICollection<Timestamp> GetAllTimestamps
{
get
{
return timestamps.ToList();
}
private set
{
timestamps = new List<Timestamp>(value);
}
}
}
public class Timestamp
{
public Operation Operation { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public TimeSpan TimeTaken
{
get
{
return EndTime - StartTime;
}
}
}
public enum Operation
{
OverallProcessing
}
The error that is thrown is:
There was an error deserializing the object of type
SerialisationIssue.Message1[[System.Xml.Linq.XElement, System.Xml.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]`.
The input source is not correctly formatted.
I have tried serialising large amounts of just XML (ie no Message<XElement> wrapper) and that works ok, so I'm sure its not actually related to the size of the XML, but that consistently make it break.
I've reflected the actual class that is used in the ServiceBus SDK client libraries and tried to use the deserialiser found in there (DataContractBinarySerializer) but that has made no difference and seems to basically do the same thing that I'm doing manually anyway.
Any ideas on how I can find what the actual problem is? And how I might go about fixing it?

So the issue is indeed related to some apparent bug in the XmlBinaryReader as outlined in this connect issue
which can be worked around by writing some whitespace at the end of the serialized data, as suggested in this post

Related

Web API will not use ISerializable implementation

I thought I had jumped through the necessary hoops to get my JsonMediaTypeFormatter working with custom ISerializable implementations, complete with passing unit tests. But I'm unable to get it to work when I pass in values via Swagger UI.
My key questions are:
What am I doing wrong with my unit test causing it to serialize/deserialize different from what Web API is doing?
What do I need to change to get this working with Web API's serializing/deserialization and Swagger/Swashbuckle?
Class being serialized: (Notice that serializing and then deserializing drops off the time component and only keeps the date component. The helps for testing/observing purposes.)
public class Pet : ISerializable
{
public DateTime Dob { get; set; }
public Pet()
{
Dob = DateTime.Parse("1500-12-25 07:59:59");
}
public Pet(SerializationInfo info, StreamingContext context)
{
Dob = DateTime.Parse(info.GetString("Dob"));
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Dob", Dob.Date.ToString());
}
}
Web API Method: (always returns null)
public class TestController : ApiController
{
[Route("~/api/Pet")]
public string Get([FromUri] Pet data)
{
return data.Dob.ToString();
}
}
Passing Unit Test: (and serialization helpers from MSDN docs)
[TestFixture]
public class SerializationTests
{
[Test]
public void PetTest()
{
var date = new DateTime(2017, 1, 20, 5, 0, 0);
var foo = new Pet { Dob = date };
var jsonFormatter = new JsonMediaTypeFormatter { SerializerSettings = new JsonSerializerSettings { ContractResolver = new DefaultContractResolver { IgnoreSerializableInterface = false } } };
var serialized = SerializationHelpers.Serialize(jsonFormatter, foo);
Console.WriteLine(serialized);
var deserialized = SerializationHelpers.Deserialize<Pet>(jsonFormatter, serialized);
Assert.That(foo.Dob, Is.Not.EqualTo(date.Date));
Assert.That(deserialized.Dob, Is.EqualTo(date.Date));
}
}
public static class SerializationHelpers
{
public static string Serialize<T>(MediaTypeFormatter formatter, T value)
{
// Create a dummy HTTP Content.
Stream stream = new MemoryStream();
var content = new StreamContent(stream);
// Serialize the object.
formatter.WriteToStreamAsync(typeof(T), value, stream, content, null).Wait();
// Read the serialized string.
stream.Position = 0;
return content.ReadAsStringAsync().Result;
}
public static T Deserialize<T>(MediaTypeFormatter formatter, string str) where T : class
{
// Write the serialized string to a memory stream.
Stream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
writer.Write(str);
writer.Flush();
stream.Position = 0;
// Deserialize to an object of type T
return formatter.ReadFromStreamAsync(typeof(T), stream, null, null).Result as T;
}
}
WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
config.Formatters.Clear();
var jsonFormatter = new JsonMediaTypeFormatter { SerializerSettings = new JsonSerializerSettings { ContractResolver = new DefaultContractResolver { IgnoreSerializableInterface = false } } };
config.Formatters.Add(jsonFormatter);
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
A few other notes:
When I run the passing unit test, the Console.WriteLine output is:
{"Dob":"1/20/2017 12:00:00 AM"}
which is exactly what I want/expect.
My Swagger UI looks like this using the default Swashbuckle settings from Nuget. Note that value of the date is what is set in the default constructor, showing that my ISerializable implementation is ignored.
NOTE:
I have changed the question to remove all generics from the picture. This problem is fundamentally about ISerializable implementations now and not about Generics.
WebAPI api does not know how to deserialize this generic object. I see a similar question here in SO but did not personally try/test it. Hope it helps: Generic Web Api method
Rather than having a generic method, you can create a generic controller. So your code above will look something like below.
public abstract class MyClass{ }
public class PersonDto: MyClass{}
public class TestController<T> : ApiController where T: MyClass
{
public string Get([FromUri] T data)
{
...
}
}

Implementing ICloneable with protobuf-net

Can you please explain why the following piece of code fails to work?
static void Main(string[] args)
{
var simpleObject = new SimpleObjectDTO { Id = 1, Name = "Jacob" };
const string format = "{2} object properties are: Id {0} Name {1}";
Console.WriteLine(format, simpleObject.Id, simpleObject.Name, "Original");
var clone = simpleObject.Clone() as SimpleObjectDTO;
// ReSharper disable PossibleNullReferenceException
Console.WriteLine(format, clone.Id, clone.Name, "Clone");
// ReSharper restore PossibleNullReferenceException
Console.ReadLine();
}
where
[ProtoContract]
public class SimpleObjectDTO : ICloneable
{
[ProtoMember(1)]
public int Id { get; set; }
[ProtoMember(2)]
public string Name { get; set; }
public object Clone()
{
using (var stream = new MemoryStream())
{
Serializer.Serialize(stream, this);
stream.Flush();
var clone = Serializer.Deserialize<SimpleObjectDTO>(stream);
return clone;
}
}
}
The code runs just fine but the deserialized object has 0 and an empty string
as the appropriate properties' values.
Upd.:
If I serialize into a binary file and then open if for reading thus creating a new stream
the code works. Is there any possibility of avoiding intermediate binary files and using only one stream for both serializing and deserializing?
Thr problem is the stream's position needs to be reset to zero.
As an alternative:
return Serializer.DeepClone(this);
Figured out the issue, forgot to reset the memory stream's position

WCF Serialization Error Using NetTCP

Using VS2012 and NetTcpBinding. I am getting the following error when I call the serviceContract from the client - The service is hosted in IIS:
There was an error while trying to serialize parameter CS.ServiceContracts.Zzzzzz.Common:GetZipCodesResult. The InnerException message was 'Type 'System.DelegateSerializationHolder+DelegateEntry' with data contract name 'DelegateSerializationHolder.DelegateEntry:http://schemas.datacontract.org/2004/07/System' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.'. Please see InnerException for more details.
Here is my ServiceContract:
[ServiceContract(SessionMode = SessionMode.Allowed, Namespace = "CS.ServiceContracts.Zzzzzz.Common",
Name = "IZzzzzzCommonService")]
public interface IZzzzzzCommonService
{
[OperationContract]
GetZipCodesResponse GetZipCodes(GetZipCodesRequest request);
}
Here is my DataContract:
[DataContract]
[Serializable]
public class GetZipCodesResponse : ResponseBase
{
[DataMember(IsRequired = true)]
public List<ZipCodes> ZipCodes { get; set; }
}
And here is the ResponseBase:
[DataContract]
[Serializable]
[KnownType(typeof(GetZipCodesResponse)), XmlInclude(typeof(GetZipCodesResponse))]
public class ResponseBase
{
[DataMember(IsRequired = true)]
public int ResponseCode { get; set; }
[DataMember(IsRequired = false)]
public int ReasonCode { get; set; }
[DataMember(IsRequired = false)]
public string ReasonText { get; set; }
}
Here is the Implementation of the serviceContract Interface:
public class ZzzzzzCommonService : IZzzzzzCommonService
{
public GetZipCodesResponse GetZipCodes(GetZipCodesRequest request)
{
var response = new GetZipCodesResponse();
try
{
response.ZipCodes = ZipCodes.GetCustom(request.ZipCode, request.City, request.State);
}
catch (Exception ex)
{
this.BuildExceptionResponse(response, ex);
}
return response; // I get to this line ok, but here is where the error occurs
}
}
And here is the client code where I am calling the Service:
public void ZipCodes()
{
var endPoint = new EndpointAddress(
"net.tcp://localhost/CS.WebService.Zzzzzz.Common/ZzzzzzCommonService.svc");
var binding = new NetTcpBinding { TransferMode = TransferMode.Buffered, SendTimeout = TimeSpan.MaxValue, ReceiveTimeout = TimeSpan.MaxValue, MaxReceivedMessageSize = 100000000, MaxBufferSize = 100000000 };
using (var channel = new ChannelFactory<IZzzzzzCommonService>(binding, endPoint))
{
try
{
channel.Endpoint.Contract.SessionMode = SessionMode.Allowed;
var proxy = channel.CreateChannel();
var request = new GetZipCodesRequest();
request = new GetZipCodesRequest { ZipCode = "32701" };
response = proxy.GetZipCodes(request);
}
}
}
I have debugged the code and I am getting into the entity and the dataSet is populated with the appropriate rows but when it sends the dataset back is when I get the errors.
Not sure what I am missing. I believe I have the serviceContract and dataContract members decorated correctly so not sure why it is having problems serializing the List.
The problem was the ZipCode class. It was an Entity class and it was not able to be serialized. So I deconstructed the class and it was able to send it back to the client.

Sending complex type as a parameter in SOAP message

I have a WCF Service like following:
public class Service1 : IService1
{
public string GetData(Person person)
{
if (person != null)
{
return "OK";
}
return "Not OK!";
}
Here is my Person class:
[DataContract]
public class Person
{
[DataMember]
public int Age { get; set; }
[DataMember]
public string Name { get; set; }
}
And I'm calling service like that:
BasicHttpBinding binding = new BasicHttpBinding(BasicHttpSecurityMode.None);
IChannelFactory<IRequestChannel> factory = binding.BuildChannelFactory<IRequestChannel>(new BindingParameterCollection());
factory.Open();
EndpointAddress address = new EndpointAddress(url);
IRequestChannel irc = factory.CreateChannel(address);
using (irc as IDisposable)
{
irc.Open();
string soapMessage = "<GetData><person><Age>24</Age><Name>John</Name></person></GetData>";
XmlReader reader = XmlReader.Create(new StringReader(soapMessage));
Message m = Message.CreateMessage(MessageVersion.Soap11,"http://tempuri.org/IService1/GetData", reader);
Message ret = irc.Request(m);
reader.Close();
return ret.ToString();
}
When I try to send complex type like Person as a parameter to GetData method, person object is coming null. But I have no problem when I send known type like integer, string etc. as a parameter.
How can I manage to send complex type as a parameter to the service method?
I ran into a similar situation, and we ended up changing the interface of the service to be the equivalent of:
public string GetData(string person)
And we did our own object serialization before calling the web service. Immediately within the web service method we would deserialize it, and proceed as normal.

Client WCF DataContract has empty/null values from service

I have a simple WCF service that returns the time from the server. I've confirmed that data is being sent by checking with Fiddler. Here's the result object xml that my service sends.
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<GetTimeResponse xmlns="http://tempuri.org/">
<GetTimeResult xmlns:a="http://schemas.datacontract.org/2004/07/TestService.DataObjects" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:theTime>2010-03-26T09:14:38.066372-06:00</a:theTime>
</GetTimeResult>
</GetTimeResponse>
</s:Body>
</s:Envelope>
So, as far as I can tell, there's nothing wrong on the server end. It's receiving requests and returning results.
But on my silverlight client, all the members of the returned object are either null, blank or a default vaule. As you can see the server returns the current date and time. But in silverlight, theTime property on my object is set to 1/1/0001 12:00 AM (default value).
Sooo methinks that the DataContracts do not match up between the server and the silverlight client. Here's the DataContract for the server
[DataContract]
public class Time
{
[DataMember]
public DateTime theTime { get; set; }
}
Incredibly simple. And here's the datacontract on my silverlight client.
[DataContract]
public class Time
{
[DataMember]
public DateTime theTime { get; set; }
}
Literally the only difference is the namespaces within the application. But still the values being returned are null, empty or a .NET default.
Thanks for you help!
UPDATE
Here is the ClientBase that all my services run through. I read an excellent article here to construct it.
public class ClientBase<T> where T :class
{
private T Channel { get; set; }
private Type ContractType { get; set; }
private ClientBase()
{
ContractType = typeof( T );
}
public ClientBase(string endPointConfiguration) :this()
{
Channel = new ChannelFactory<T>( endPointConfiguration ).CreateChannel();
}
public ClientBase( EndpointAddress address, Binding binding ):this()
{
Channel = new ChannelFactory<T>( binding, address ).CreateChannel();
}
public void Begin(string methodName, object state, params object[] parameterArray)
{
Begin( methodName, null, state, parameterArray );
}
public void Begin(string methodName, EventHandler<ClientEventArgs> callBack, object state, params object[] parameterArray)
{
if(parameterArray != null)
{
Array.Resize(ref parameterArray, parameterArray.Length + 2);
}
else
{
parameterArray = new object[2];
}
parameterArray[ parameterArray.Length - 1 ] = new ObjectClientState {CallBack = callBack, MethodName = methodName, UserState = state};
parameterArray[ parameterArray.Length - 2 ] = new AsyncCallback( OnCallBack );
ContractType.InvokeMember( "Begin" + methodName,
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.InvokeMethod |
System.Reflection.BindingFlags.Public, null, Channel, parameterArray );
}
private void OnCallBack(IAsyncResult result)
{
ObjectClientState state = result.AsyncState as ObjectClientState;
if(state == null)
return;
Object obj = ContractType.InvokeMember( "End" + state.MethodName,
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.InvokeMethod |
System.Reflection.BindingFlags.Public, null, Channel, new object[] {result} );
if(state.CallBack != null)
{
state.CallBack( this, new ClientEventArgs {Object = obj, UserState = state.UserState} );
}
}
public class ClientEventArgs : EventArgs
{
public object Object { get; set; }
public object UserState { get; set; }
public T LoadResult<T>()
{
if( Object is T )
return ( T ) Object;
return default( T );
}
}
private class ObjectClientState
{
public EventHandler<ClientEventArgs> CallBack { get; set; }
public string MethodName { get; set; }
public object UserState { get; set; }
}
}
Here is my interface
[ServiceContract]
public interface ITestService
{
[OperationContract( AsyncPattern = true )]
IAsyncResult BeginGetTime( AsyncCallback callback, object state );
Time EndGetTime( IAsyncResult result );
}
Now I have my service class that makes calls through my BaseService class using this interface.
public class TestSiteService : ClientBase<ITestService>
{
public TestSiteService (string endPointConfiguration):base(endPointConfiguration) { }
public TestSiteService ( EndpointAddress address, Binding binding ) : base( address, binding ) { }
public void GetTime( EventHandler<ClientEventArgs> callBack )
{
Begin( "GetTime", callBack, null, null );
}
}
Finally here is the code that actually calls everything and does the work.
TestSiteService client = new TestSiteService ( new EndpointAddress( "http://localhost:3483/wcf/Service.svc" ), new BasicHttpBinding() );
client.GetTime( delegate( object res, ClientBase<ITestService>.ClientEventArgs e )
{
Dispatcher.BeginInvoke( () => lblDisplay.Text = "Welcome " + e.LoadResult<Time>().theTime );
} );
Whew....I hope no one is lost from all this code I posted :P
Because you don't set the Namespace property on your DataContractAttribute, the namespace will be sythesized from the .NET class/namespace. You can see this in the SOAP message example you posted:
http://schemas.datacontract.org/2004/07/TestService.DataObjects
In order to have the contracts be considered equal, you must set the Namespace property on the DataContract to the same value on both sides. That might look a little something like this:
[DataContract(Namespace="urn:my-test-namespace")]
Extending on Drew Marsh's correct answer (+1 - thx) I had a generated Service Reference which was working, but when I tried to use the Wcf Client Factory one implementing the correct interface (but the namespace was different) then I was experiencing the problem described.
I had no easy way to work out what the "correct" namespace should have been but simply copying the following attributes from the service reference's DataContract entity to the one in the Wcf Client Factory implementation solved the issue;
[System.Runtime.Serialization.DataContractAttribute(Name = "BOSPrice", Namespace = "http://schemas.datacontract.org/2004/07/BOSDataService")]
[System.SerializableAttribute()]