I just wrote this SerializationHelper class, but I can't believe this is necessary!
using System.IO;
using System.Xml.Serialization;
public static class SerializationHelper
{
public static string Serialize<T>(T obj)
{
var outStream = new StringWriter();
var ser = new XmlSerializer(typeof(T));
ser.Serialize(outStream, obj);
return outStream.ToString();
}
public static T Deserialize<T>(string serialized)
{
var inStream = new StringReader(serialized);
var ser = new XmlSerializer(typeof(T));
return (T)ser.Deserialize(inStream);
}
}
And it's used like this:
var serialized = SerializationHelper.Serialize(myObj);
and:
var myObj = SerializationHelper.Deserialize<MyType>(serialized)
Am I missing something in the .NET framework? This is not rocket science!
In actual fact, the bits where you call the .NET API are these:
var ser = new XmlSerializer(typeof(T));
ser.Serialize(outStream, obj);
var ser = new XmlSerializer(typeof(T));
var obj = (T) ser.Deserialize(inStream);
The rest of the code is your personal specialisation. I don't think that two lines of code is too much for calling an API. You could always condense them, e.g.
(new XmlSerializer(typeof(T))).Serialize(outStream, obj);
var obj = (T) (new XmlSerializer(typeof(T))).Deserialize(inStream);
Purely as an aside, I should point out that I regard storing XML data in string variables as a Code Smell. As soon as you take XML data out of its raw binary form (XDocument, XmlDocument, XPathDocument or any other type of DOM), you run up against encoding issues. What if a developer serialises an object to a string with encoding X, then writes the string to a disk file with encoding Y? Not very safe. Besides which, if encoding X is not UTF-16, how would you even represent the data in a .NET string?
It's useful if you are doing any real amount (>1) of serialization/deserialization within a project. This was the case for me one time, so I just put a similar class in a Utils library, along with other reusable functions.
Related
I don't like using MemoryStream objects inbetween stream interfaces. They are awkward, requiring you to re-seek to the start, and will also peak memory usage in demanding situations.
Sometimes a utility will only work a certain way. Perhaps it will output byte[]s, or write to a stream, or is a stream in a pipeline that you read from, pulling the data through.
This Newtonsoft JSON serializer is a utility which can only write to a stream.
var js = new Newtonsoft.Json.JsonSerializer();
var sw = new StreamWriter(ps);
js.Serialize(sw, o);
This is a problem for me, because I want to chain:
IEnumerable
JSON serialization
GZIP compression
HTTP to client
(Network)
HTTP from Server
GZIP decompression
JSON deserialization
IEnumerable
Apart from the difficulties getting the JSON deserializer to present a nice IEnumerable interface, the rest of the parts don't provide an interface suitable for pipelining. Even the GZIP compression side is the wrong way around.
Ideally, on the server-side I would be able to do:
IEnumerable<object> o = GetData();
var js = new Newtonsoft.Json.JsonSerialization(o);
var gz = new System.IO.Compression.GZipStream(js, System.IO.Compression.CompressionMode.Compress, true);
return new FileStreamResult(gz, "application/x-gzip");
I could possibly extend the Newtonsoft project to provide a pipeline implementation, and I may do so. But until then I need a solution, and I believe one is required for other utilities (including the BCL GZipStream).
Are there any solutions which allow one to join such utilities more efficiently?
Is there a library which contains an adapter for such situations?
I am working on such a library, not expecting there to be such a library.
The answer is the brand new StreamAdaptor project:
https://bitbucket.org/merarischroeder/alivate-stream-adaptor. It still needs a bit of work - would be nice to package it as a NuGet package, but it's all there and tested.
So the interface will look a bit like this:
var data = GetData(); //Get the source data
var sa = new StreamAdaptor(); //This is what wraps the write-only utility source
sa.UpstreamSource((ps) => //ps is the dummy stream which does most of the magic
{
//This anon. function is run on a separate thread and can therefore be blocked
var sw = new StreamWriter(ps);
sw.AutoFlush = true;
var js = new Newtonsoft.Json.JsonSerializer();
js.Serialize(sw, data); //This is the main component of the implementation
sw.Flush();
});
var sa2 = new StreamAdaptor();
sa2.UpstreamSource((ps) =>
{
using (var gz = new System.IO.Compression.GZipStream(ps, System.IO.Compression.CompressionMode.Compress, true))
sa.CopyTo(gz);
});
The reverse process is easier with natural support for a read-through pipeline
System.IO.Compression.GZipStream sw = new System.IO.Compression.GZipStream(sa2, System.IO.Compression.CompressionMode.Decompress);
var jsonTextReader = new JsonTextReader(new StreamReader(sw));
return TestA.Deserialize(jsonTextReader);
I also demonstrate there a workaround to the IEnumerable<> deserializing issue. It requires you to create your own deserializer leveraging JsonTextReader, but it works well.
The serializer supports IEnumerable natively. The GetData function above, sets up the data source for the serializer using IEnumerable functions (among other things):
public static IEnumerable<TestB> GetTestBs()
{
for (int i = 0; i < 2; i++)
{
var b = new TestB();
b.A = "A";
b.B = "B";
b.C = TestB.GetCs();
yield return b;
}
}
It's Deserialisation which requires a workaround. Keep in mind that IEnumerable<> properties need to be listed all at the end of the JSON stream/objects, because enumeration is deferred, yet JSON deserialization is linear.
The Deserialization entry point:
public static TestA Deserialize(JsonTextReader reader)
{
TestA a = new TestA();
reader.Read();
reader.Read();
if (!reader.Value.Equals("TestBs"))
throw new Exception("Expected property 'TestBs' first");
reader.Read(); //Start array
a.TestBs = DeserializeTestBs(reader); //IEnumerable property last
return a;
}
One of the IEnumerable deserializer functions:
static IEnumerable<TestB> DeserializeTestBs(JsonTextReader reader)
{
while (reader.Read())
{
if (reader.TokenType == JsonToken.EndArray)
break;
yield return TestB.Deserialize(reader);
}
reader.Read(); //End of object
}
This can of course be achieved with trial and error, although built-in support in JSON.NET is desirable.
I am developing a WCF service which will be consumed by multiple different client applications. In order to make one functionality work, the server needs to read an XML file into a C# DataContract which is then passed on to the concerned client. As far as I understand from the MSDN website, this is possible but I couldn't find any complete examples. In particular, the website talks about a 'stream' parameter which I don't quite get yet.
My data contract has one property field which is a list of another data contract which has multiple simple property fields.
e.g.
[DataContract]
public class MyClass1 {
[DataMember]
public string name;
[DataMember]
public int age;
}
[DataContract]
public class MyClass2 {
[DataMember]
public List<MyClass1> myClass1List;
}
My classes look something like this.
Here is an example
MyClass1 obj = new MyClass1();
DataContractSerializer dcs = new DataContractSerializer(typeof(MyClass1));
using (Stream stream = new FileStream(#"C:\tmp\file.xml", FileMode.Create, FileAccess.Write))
{
using (XmlDictionaryWriter writer =
XmlDictionaryWriter.CreateTextWriter(stream, Encoding.UTF8))
{
writer.WriteStartDocument();
dcs.WriteObject(writer, obj);
}
}
Books b = new Books();
DataContractSerializer dcs = new DataContractSerializer(typeof(Books));
try
{
Stream fs = new FileStream(#"C:\Users\temelm\Desktop\XmlFile.xml", FileMode.Create, FileAccess.Write);
XmlDictionaryWriter xdw = XmlDictionaryWriter.CreateTextWriter(fs, Encoding.UTF8);
xdw.WriteStartDocument();
dcs.WriteObject(xdw, b);
xdw.Close();
fs.Flush();
fs.Close();
}
catch (Exception e)
{
s += e.Message + "\n";
}
This can be useful for you. When you need XElement. For instance when you going append node to XDocument or replece XElement of this document.
private XElement objectToXElement(SomeContractType obj)
{
DataContractSerializer dcs = new DataContractSerializer(typeof(SomeContractType);
var ms = new MemoryStream();
var xw = XmlWriter.Create(ms);
dcs.WriteObject(xw, obj);
xw.Flush();
xw.Close();
ms.Position = 0;
XElement xe = XElement.Load(ms);
return xe;
}
There is the NetDataContractSerializer which solves a whole bunch of problems when using WCF.
See here MSDN NetDataContractSerializer
It is typically used for wrapping all kinds of objects and pass it over WCF.
You can use it for wrapping objects into a byte[] and transport it over WCF,
on the serverside, you can easily Deserialize the objects and do whatever
you want with them.
Here is a discussion on how to use this Serializer correctly:
MSDN Social
Code snippets are provided there also!
Its late and fully possible I'm missing something obvious but what is it?
I'm trying to create a backing property which reveals an int array as serialized (which is then used to build up a Queue).
I'm pretty sure this is right but the getter always return a blank string, even when there are values in there (not that it should ever return a blank string.
Here is my code:
readonly Lazy<XmlSerializer> _queueSerializer = new Lazy<XmlSerializer>(() => new XmlSerializer(typeof(int[])));
[StringLength(1000)]
public string _MostRecentPlayers
{
get
{
var stream = new MemoryStream();
_queueSerializer.Value.Serialize(stream, _mostRecentPlayers.ToArray());
return new StreamReader(stream).ReadToEnd();
}
set
{
if (value.IsEmpty())
{
_mostRecentPlayers.Clear();
return;
}
MemoryStream stream = new MemoryStream(Encoding.ASCII.GetBytes(value));
var tempQueue = _queueSerializer.Value.Deserialize(stream) as int[];
_mostRecentPlayers.Clear();
tempQueue.ForEach(_mostRecentPlayers.Enqueue);
}
}
readonly Queue<int> _mostRecentPlayers = new Queue<int>(_mostRecentAmountTracked);
You haven't rewound the stream; it is positioned at the end. Set .Position = 0 before reading it. Or easier, just serialize to a StringWriter, or if you really want to use a MemoryStream, pass the (oversized) backing array from GetBuffer() along with the .Length to an Encoding and call GetString().
using(var sw = new StringWriter()) {
_queueSerializer.Value.Serialize(sw, _mostRecentPlayers.ToArray());
xml = sw.ToString();
}
or for ASCII (see comments):
using(var ms = new MemoryStream()) {
var settings = new XmlWriterSettings {
Encoding = Encoding.ASCII
};
using(var xw = XmlWriter.Create(ms, settings)) {
_queueSerializer.Value.Serialize(xw, _mostRecentPlayers.ToArray());
}
xml = Encoding.ASCII.GetString(ms.GetBuffer(), 0, (int)ms.Length);
}
Also, unless it is unlikely that you will serialize in the exe, I would suggest simplifying to just:
static readonly XmlSerializer _queueSerializer =new XmlSerializer(typeof(int[]));
Finally, note that xml is quite verbose as a mechansim to throw some ints around. CSV would seem a lot simpler (assuming you want text).
I have a serialization utility that serializes an object to an XDocument. It works quite well :
public static class SerializationUtil
{
public static T Deserialize<T>(XDocument doc)
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
using (var reader = doc.Root.CreateReader())
{
return (T)xmlSerializer.Deserialize(reader);
}
}
public static XDocument Serialize<T>(T value)
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
XDocument doc = new XDocument(z);
using (var writer = doc.CreateWriter())
{
xmlSerializer.Serialize(writer, value);
}
return doc;
}
Been using it quite happily and suddenly I get :
There was an error generating the XML document.
The inner exception is :
This XmlWriter does not support base64 encoded data.
Turns out that the XDocument.CreateWriter() instance method gives you a writer of type System.Xml.XmlWellFormedWriter, and that that writer can't write base64 encoded data (my object contains a byte[]).
MSDN doesn't even seem to mention this class - but I can't seem to create any other type of writer from XDocument.
I could just serialize to a string, but i was trying to be clever and avoid using any hacks. Any way to serialize to XDocument when base64 is needed for certain fields.
According to the docs, there's no allowance for bytes. A surrogate base64 encoded string property is probably your best bet (is it a hack if its by design?).
I want to be able to send a XmlSerializer class (which is generated obvious in remote C# application) over a WebService that will then deserialize it into a class. (I didnt know it its possible either)
My class is:
SystemInfo
I'm serializing it this way:
XmlSerializer mySerializer = new XmlSerializer(typeof(SystemInfo));
StreamWriter myWriter = new StreamWriter(textBox1.Text);
mySerializer.Serialize(myWriter, sysinfo);
How can i build the WebService?
[WebMethod]
public void Reports(XmlSerializer xmlSerializer)
{
.
.
.
}
Can anyone help me out?
Regards
First I assume you want to pass arbitrary types to a single web method, where the types are shared by the client and the server.
There is not much point in sending the XmlSerializer, it only has the logic to serialize/deserialize. It does not have the actual data, that is either read/written to a stream. What you should do is pass either a string or and XmlNode.
The caller of the web service can then a client side instance of XmlSerializer and serialize the object to a string, then call the web method passing the string as an argument. The web method it self can then create an instance of a XmlSerializer and deserialize the string back into an object. Of course to create the server size instance of the serializer you will need to know the root type to create the serializer for, you can pass this as a type name and use Type.GetType() to get the correct type to pass to the XmlSerializer.
If you know upfront which types you are going to be passing then you could also declare your web method more strongly typed and explicitly create methods for the types you expect to recieve.
If the wire format is not too much of a concern, you could also user SoapFormatter or a BinaryFormatter to handle the serialization/deserialization. In the later case of the BinaryFormatter you would declare your web method to take a byte[] argument, the advantage of these formatters (serializers) is that they do not need additional info on the type when you create the instance of the formatter, but they can be slower than an XmlSerializer
Update: Added some simple examples (Untested)
Example using an XmlSerializer, here you will need to pass the type name from the client side, so I made it an additional argument.
[WebMethod]
public void Reports(string xml, string typeName)
{
XmlSerializer xs = new XmlSerializer(Type.GetType(typeName));
object obj = xs.Deserialize(new StringReader(xml));
// use the deserialize object
}
Example using a BinaryFormatter, no type name needed but you the class types will need to be serializable
[WebMethod]
public void Reports(byte[] data)
{
BinaryFormatter bf = new BinaryFormatter();
object obj = bf.Deserialize(new MemoryStream(data));
// use the deserialized object
}
On the client side you would use something like the following to serialize using the BinaryFormatter.
// initialize the SystemInfo instance that you want to pass to the server
SystemInfo si = new SystemInfo() { SystemName = "My System" };
// Serialize to a memory stream
BinaryFormatter bf = new BinaryFormatter();
MemoryStream ms = new MemoryStream();
bf.Serialize(ms, si);
// Call the service, passing the array from the memory stream
ws.Reports(ms.ToArray());
Chris, thanks for helping me out. It was a major step forward.
I solved the problem sending the xml string:
SystemInfo sysinfo = new SystemInfo();
sysinfo.RUN();
XmlSerializer mySerializer = new XmlSerializer(typeof(SystemInfo));
MemoryStream memStream = new MemoryStream();
mySerializer.Serialize(memStream, sysinfo);
memStream.Seek(0, System.IO.SeekOrigin.Begin);
XmlDocument doc = new XmlDocument();
doc.Load(memStream);
memStream.Close();
localhost.WS_Agente dasdsa = new localhost.WS_Agente();
dasdsa.Reports(doc.InnerXml);
And the WebService:
[WebMethod]
public void Reports(string xml)
{
XmlSerializer mySerializer = new XmlSerializer(typeof(SystemInfo));
SystemInfo obj = (SystemInfo)mySerializer.Deserialize(new StringReader(xml));
}
Its working like a charm now :)
My question is: Can i improve the code?
Thanks