There are quite a few questions related to this topic that I have browsed through.
But it seems that I have a unique case where we are inheriting a class from XmlTextWriter which makes it impossible to use the XmlWriter.Create() method for instantiating an XmlWriter with XmlWriterSettings.
So the question - Is there a way to specify XmlWriterSettings like OmitXmlDeclaration, DoNotEscapeUriAttributes and CloseOutput for the inherited instance ?
Note: I have used Formatting property in the inherited class but could not locate above mentioned properties unless set through XmlWriterSettings.
XmlTextWriter does not support all the options available in XmlWriterSettings. The class was originally created for writing XML in .Net 1.x and was deprecated in favor of XmlWriter.Create() in .Net 2.0, as explained in the docs:
Starting with the .NET Framework 2.0, we recommend that you use the System.Xml.XmlWriter class instead.
Full support for XmlWriterSettings was never added to the old XmlTextWriter and vice versa. This can be confirmed by checking the reference source.
For instance, for CloseOutput, if you look at the reference source for XmlTextWriter.Close() the underlying text writer is closed unconditionally:
public override void Close() {
try {
AutoCompleteAll();
}
catch { // never fail
}
finally {
this.currentState = State.Closed;
textWriter.Close();
}
}
Compare with XmlUtf8RawTextWriter.Close() (this class is one of the XML writers returned by XmlWriter.Create()) where the underlying text writer is conditionally closed:
public override void Close() {
try {
FlushBuffer();
FlushEncoder();
}
finally {
// Future calls to Close or Flush shouldn't write to Stream or Writer
writeToNull = true;
if ( stream != null ) {
try {
stream.Flush();
}
finally {
try {
if ( closeOutput ) {
stream.Close();
}
}
finally {
stream = null;
}
}
}
}
}
(However, you could always construct a StreamWriter that does not close the underlying stream, using one of the answers from Is there any way to close a StreamWriter without closing its BaseStream?.)
Similarly XmlTextWriter.WriteStartDocument() does not appear to have an option to not emit an XML declaration:
public override void WriteStartDocument() {
StartDocument(-1);
}
// Writes out the XML declaration with the version "1.0" and the standalone attribute.
public override void WriteStartDocument(bool standalone) {
StartDocument(standalone ? 1 : 0);
}
void StartDocument(int standalone) {
try {
if (this.currentState != State.Start) {
throw new InvalidOperationException(Res.GetString(Res.Xml_NotTheFirst));
}
this.stateTable = stateTableDocument;
this.currentState = State.Prolog;
StringBuilder bufBld = new StringBuilder(128);
bufBld.Append("version=" + quoteChar + "1.0" + quoteChar);
if (this.encoding != null) {
bufBld.Append(" encoding=");
bufBld.Append(quoteChar);
bufBld.Append(this.encoding.WebName);
bufBld.Append(quoteChar);
}
if (standalone >= 0) {
bufBld.Append(" standalone=");
bufBld.Append(quoteChar);
bufBld.Append(standalone == 0 ? "no" : "yes");
bufBld.Append(quoteChar);
}
InternalWriteProcessingInstruction("xml", bufBld.ToString());
}
catch {
currentState = State.Error;
throw;
}
}
void InternalWriteProcessingInstruction(string name, string text) {
textWriter.Write("<?");
ValidateName(name, false);
textWriter.Write(name);
textWriter.Write(' ');
if (null != text) {
xmlEncoder.WriteRawWithSurrogateChecking(text);
}
textWriter.Write("?>");
}
It would seem StartDocument() must needs be called to initialize the writer's internal state, but when called, an XML declaration is always written.
As an alternative, have you considered using the decorator pattern and wrapping the writer returned from XmlWriter.Create() in a decorator, e.g. as is shown in this answer to How can I stop empty XML elements self-closing using XmlDocument in C#? This would allow you to customize your output before passing it to the underlying XmlWriter similarly to how your subclass of XmlTextWriter could customize the output before passing to the base class's methods.
Related
I am working with a legacy application that does not import abbreviated empty xml elements. For example:
BAD empty:
<foo />
GOOD empty:
<foo></foo>
I know the solution to achieve this, which I will present now:
public class XmlTextWriterFull : XmlTextWriter
{
public XmlTextWriterFull(Stream stream, Encoding enc) : base(stream, enc)
{
}
public XmlTextWriterFull(String str, Encoding enc) : base(str, enc)
{
}
public override void WriteEndElement()
{
base.WriteFullEndElement();
}
}
and the client code:
var x_settings = new XmlWriterSettings();
x_settings.NewLineChars = Environment.NewLine;
x_settings.NewLineOnAttributes = true;
x_settings.NewLineHandling = NewLineHandling.Replace;
x_settings.CloseOutput = true;
x_settings.Indent = true;
x_settings.NewLineOnAttributes = true;
//var memOut = new MemoryStream();
var writer = new XmlTextWriterFull(outputFilename, Encoding.UTF8); //Or the encoding of your choice
var x_serial = new XmlSerializer(typeof(YOUR_OBJECT_TYPE));
x_serial.Serialize(writer, YOUR_OBJECT_INSTANCE);
writer.Close();
However, if you observed carefully the XmlWriterSettings are never used in the client code. Therefore the xml output is terribly formatted. My questions is this: how do I adapt the above code to accept XmlWriterSettings?
The use of factory creation methods and sealed/internal/abstract classes makes this difficult to implement an override.
I will accept an alternative solution, I am not married to my above solution.
WORKAROUND SOLUTION
Step 1: create the following class in your solution:
public class XmlTextWriterFull : XmlTextWriter
{
public XmlTextWriterFull(TextWriter sink) : base(sink)
{
Formatting = Formatting.Indented;
}
public override void WriteEndElement()
{
base.WriteFullEndElement();
}
}
Step 2: Add the following client code. Make sure to replace YOUR_OBJECT_TYPE and YOUR_OBJECT_INSTANCE with the class and instance your are working with:
TextWriter streamWriter = new StreamWriter(outputFilename);
var writer = new XmlTextWriterFull(streamWriter);
var x_serial = new XmlSerializer(typeof (YOUR_OBJECT_TYPE));
x_serial.Serialize(writer, YOUR_OBJECT_INSTANCE);
writer.Close();
The workaround above will produce the following empty xml element formatting:
<foo>
</foo>
The issue with this workaround is that it adds a line feed (notice the elements are on separate lines). This may be acceptable for you but causes issues with my legacy application.
How about this.
Grab the awesome XmlWrappingWriter class from http://www.tkachenko.com/blog/archives/000585.html (I have omitted the code for the sake of brevity).
With that, we can create a sub-class as follows (very similar to your original one):
public class XmlTextWriterFull2 : XmlWrappingWriter
{
public XmlTextWriterFull2(XmlWriter baseWriter)
: base(baseWriter)
{
}
public override void WriteEndElement()
{
base.WriteFullEndElement();
}
}
It can then be invoked like this (again very similar):
var x_settings = new XmlWriterSettings();
x_settings.NewLineChars = Environment.NewLine;
x_settings.NewLineOnAttributes = true;
x_settings.NewLineHandling = NewLineHandling.None;
x_settings.CloseOutput = true;
x_settings.Indent = true;
x_settings.NewLineOnAttributes = true;
using (XmlWriter writer = XmlWriter.Create(outputFilename, x_settings))
{
using (XmlTextWriterFull2 xmlTextWriterFull = new XmlTextWriterFull2(writer))
{
var x_serial = new XmlSerializer(typeof(YOUR_OBJECT_TYPE));
x_serial.Serialize(xmlTextWriterFull, YOUR_OBJECT_INSTANCE);
}
}
In my case, an element that had previously been rendered as
<Foo>
</Foo>
became
<Foo></Foo>
As you alluded to in your question, this is actually quite a tricky problem due to everything being sealed/internal etc., making overrides rather difficult. I think my biggest problem was trying to get an XmlWriter to accept XmlWriterSettings: beyond this approach, I could find no way of getting the original XmlTextWriterFull to respect the given XmlWriterSettings.
MSDN states that this method:
XmlWriter.Create(XmlWriter, XmlWriterSettings)
Can be used to apply the XmlWriterSettings to the XmlWriter. I couldn't get this to work like I wanted (the indentation never worked, for example), and upon decompiling the code, it does not appear that all the settings are used with this particular method, hence why my invocation code just passes in the outputFile (a stream of some sort would work just as well).
The workaround solution you gave in your question adds extra line breaks (when indenting is enabled) because we're telling the writer to treat this element as if it had children.
Here is how I modified your workaround to manipulate the indenting dynamically so as to avoid those extra line breaks.
public class XmlTextWriterFull : XmlTextWriter
{
public XmlTextWriterFull(TextWriter sink)
: base(sink)
{
Formatting = Formatting.Indented;
}
private bool inElement = false;
public override void WriteStartElement(string prefix, string localName, string ns)
{
base.WriteStartElement(prefix, localName, ns);
// Remember that we're in the process of defining an element.
// As soon as a child element is closed, this flag won't be true anymore and we'll know to avoid messing with the indenting.
this.inElement = true;
}
public override void WriteEndElement()
{
if (!this.inElement)
{
// The element being closed has child elements, so we should just let the writer use it's default behavior.
base.WriteEndElement();
}
else
{
// It looks like the element doesn't have children, and we want to avoid emitting a self-closing tag.
// First, let's temporarily disable any indenting, then force the full closing element tag.
var prevFormat = this.Formatting;
this.Formatting = Formatting.None;
base.WriteFullEndElement();
this.Formatting = prevFormat;
this.inElement = false;
}
}
}
Following code snippet force printing of closing tag on the same line (sorry for vb version, it should be easy to rewrite the same using C#):
Imports System.Xml
Imports System.IO
Public Class CustomXmlTextWriter
Inherits XmlTextWriter
Public Sub New(ByRef baseWriter As TextWriter)
MyBase.New(baseWriter)
Formatting = Xml.Formatting.Indented
End Sub
Public Overrides Sub WriteEndElement()
If Not (Me.WriteState = Xml.WriteState.Element) Then
MyBase.WriteEndElement()
Else
Formatting = Xml.Formatting.None
MyBase.WriteFullEndElement()
Formatting = Xml.Formatting.Indented
End If
End Sub
End Class
Another option.
public class XmlCustomTextWriter : XmlTextWriter
{
private TextWriter _tw = null;
public XmlCustomTextWriter(TextWriter sink)
: base(sink)
{
_tw = sink;
Formatting = Formatting.Indented;
Indentation = 0;
}
public void OutputElement(string name, string value)
{
WriteStartElement(name);
string nl = _tw.NewLine;
_tw.NewLine = "";
WriteString(value);
WriteFullEndElement();
_tw.NewLine = nl;
}
}
Leaving this here in case someone needs it; since none of the answers above solved it for me, or seemed like overkill.
FileStream fs = new FileStream("file.xml", FileMode.Create);
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
XmlWriter w = XmlWriter.Create(fs, settings);
w.WriteStartDocument();
w.WriteStartElement("tag1");
w.WriteStartElement("tag2");
w.WriteAttributeString("attr1", "val1");
w.WriteAttributeString("attr2", "val2");
w.WriteFullEndElement();
w.WriteEndElement();
w.WriteEndDocument();
w.Flush();
fs.Close();
The trick was to set the XmlWriterSettings.Indent = true and add it to the XmlWriter.
Edit:
Alternatively you can also use
w.Formatting = Formatting.Indented;
instead of adding an XmlWriterSettings.
I am making a WPF application in which I save list of object on exit of my WPF Application. And get the list of objects on system startup. Everything works fine initially. But Some times it gives the serialization Exception. After getting the exception I looked of the xml serialized file. But it seem to me that the exception was thrown because the xml file was not formed properly. When I corrected it. It again worked fine.
public static class IsolatedStorageCacheManager<T>
{
public static void store(T loc)
{
IsolatedStorageFile appStore = IsolatedStorageFile.GetStore(IsolatedStorageScope.User | IsolatedStorageScope.Assembly | IsolatedStorageScope.Domain, null, null);
using(IsolatedStorageFileStream fileStream=appStore.OpenFile("myFile21.xml",FileMode.OpenOrCreate))
{
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
serializer.WriteObject(fileStream, loc);
}
}
public static T retrieve()
{
T obj = default(T);
IsolatedStorageFile appStore = IsolatedStorageFile.GetStore(IsolatedStorageScope.User | IsolatedStorageScope.Assembly | IsolatedStorageScope.Domain, null, null);
if (appStore.FileExists("myFile21.xml"))
{
using (IsolatedStorageFileStream fileStream = appStore.OpenFile("myFile21.xml", FileMode.Open))
{
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
try
{
obj = (T)serializer.ReadObject(fileStream);
}
catch (SerializationException e)
{
Console.WriteLine(e.StackTrace);
}
}
}
return obj;
}
}
The first thing to do is make sure that the objects passed to store are of a type supported by the DataContractSerializer.
The easiest thing to do is check all store calls yourself.
You could also create a validation method or even better, see if anyone else has implemented one. This method could validate the loc object and return a boolean and be called at the beginning of the store method inside a System.Diagnostics.Debug.Assert call so that it will only run on a debug configuration. Note though that this method could be quite tricky because you will have to validate type T for all the cases mentioned in the specification of DataContractSerializer and if T is a generic validate T's parameter(s) as well.
I try to delete the whole SOAP header from a WCF message, just only want to leave the envelope body. Anybody can give me an idea how can do that?
Create a WCF message like this:
**string response = "Hello World!";
Message msg = Message.CreateMessage(MessageVersion.Soap11, "*", new TextBodyWriter(response));
msg.Headers.Clear();**
The sending SOAP message will be:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header />
<s:Body>
<Binary>Hello World!</Binary>
</s:Body>
</s:Envelope>
But I don't want to the SOAP header element, which I just only need the envelop body.How to remove the header element from a WCF message?
Option 1: Use bacicHttpBinding, it will not add content to the header (when not configured for security)
Option 2: Implement a custom mesaage encoder and strip the header there. anywhere before that there is a chance wcf will add the header back. See sample encoder here.
That question is a tricky one: let's take it step by step
Some Context
The Message class writes its headers in its ToString() method. ToString() then calls an internal overload ToString(XmlDictionaryWriter writer) which then starts writing:
// System.ServiceModel.Channels.Message
internal void ToString(XmlDictionaryWriter writer)
{
if (this.IsDisposed)
{
throw TraceUtility.ThrowHelperError(this.CreateMessageDisposedException(), this);
}
if (this.Version.Envelope != EnvelopeVersion.None)
{
this.WriteStartEnvelope(writer);
this.WriteStartHeaders(writer);
MessageHeaders headers = this.Headers;
for (int i = 0; i < headers.Count; i++)
{
headers.WriteHeader(i, writer);
}
writer.WriteEndElement();
MessageDictionary arg_60_0 = XD.MessageDictionary;
this.WriteStartBody(writer);
}
this.BodyToString(writer);
if (this.Version.Envelope != EnvelopeVersion.None)
{
writer.WriteEndElement();
writer.WriteEndElement();
}
}
The this.WriteStartHeaders(writer); code writes the header tag regardless of the number of headers. It is matched by the writer.WriteEndElement() after the for loop. This writer.WriteEndElement() must be matched with the header tag being written, else the Xml document will be invalid.
So there is no way we can override a virtual method to get rid of the headers: WriteStartHeaders calls the virtual method OnWriteStartHeaders but the tag closing prevents simply shutting it off). We have to change the whole ToString() method in order to remove any header-related structure, to arrive at:
- write start of envelope
- write start of body
- write body
- write end of body
- write end of envelope
Solutions
In the above pseudocode, we have control on everything but the "write body" part. All methods called in the initial ToString(XmlDictionaryWriter writer) are public except BodyToString. So we will need to call it through reflection or whichever method fits your needs. Writing a message without its headers simply becomes:
private void ProcessMessage(Message msg, XmlDictionaryWriter writer)
{
msg.WriteStartEnvelope(writer); // start of envelope
msg.WriteStartBody(writer); // start of body
var bodyToStringMethod = msg.GetType()
.GetMethod("BodyToString", BindingFlags.Instance | BindingFlags.NonPublic);
bodyToStringMethod.Invoke(msg, new object[] {writer}); // write body
writer.WriteEndElement(); // write end of body
writer.WriteEndElement(); // write end of envelope
}
Now we have a way to get our message content without the headers. But how should this method be invoked?
We only want the message without headers as a string
Great, we don't need to care about overriding the ToString() method that then calls the initial writing of the message. Just create a method in your program that takes a Message and an XmlDictionaryWriter and call it to get the message without its headers.
We want the ToString() method to return the message without headers
This one is a bit more complicated. We cannot easily inherit from the Message class because we would need to pull out a lot of dependencies out of the System.ServiceModel assembly. I won't go there in this answer.
What we can do is use the capabilities of some frameworks to create a proxy around an existing object and to intercept some calls to the original object in order to replace/enhance its behavior: I'm used to Castle Dynamic proxy so let's use that.
We want to intercept the ToString() method so we create a proxy around the Message object we are using and add an interceptor to replace the ToString method of the Message with our implementation:
var msg = Message.CreateMessage(MessageVersion.Soap11, "*");
msg.Headers.Clear();
var proxyGenerator = new Castle.DynamicProxy.ProxyGenerator();
var proxiedMessage = proxyGenerator.CreateClassProxyWithTarget(msg, new ProxyGenerationOptions(),
new ToStringInterceptor());
The ToStringInterceptor needs to do almost the same thing as the initial ToString() method, we will however use our ProcessMessage method defined above:
public class ToStringInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
if (invocation.Method.Name != "ToString")
{
invocation.Proceed();
}
else
{
var result = string.Empty;
var msg = invocation.InvocationTarget as Message;
StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture);
XmlDictionaryWriter xmlDictionaryWriter =
XmlDictionaryWriter.CreateDictionaryWriter(new XmlTextWriter(stringWriter));
try
{
ProcessMessage(msg, xmlDictionaryWriter);
xmlDictionaryWriter.Flush();
result = stringWriter.ToString();
}
catch (XmlException ex)
{
result = "ErrorMessage";
}
invocation.ReturnValue = result;
}
}
private void ProcessMessage(Message msg, XmlDictionaryWriter writer)
{
// same method as above
}
}
And here we are: calls to the ToString() method of the message will now return a envelope without headers. We can pass the message to other parts of the framework and know it should mostly work: direct calls to some of the internal plumbing of Message can still produce the initial output but short of a full reimplementation we cannot control that.
Points of note
This is the shortest way to removing the headers I found. The fact that the header serialisation in the writer was not handled in one virtual function only was a big problem. The code doesn't give you much wriggle room.
This implementation doesn't use the same XmlWriter as the one used in the original implementation of ToString() in the Message, EncodingFallbackAwareXmlTextWriter. This class is internal in System.ServiceModel and pulling it out is left as an exercice to the reader. As a result, the output differs slightly since the xml is not formatted with the simple XmlTextWriter I use.
The interceptor could simply have parsed the xml returned from the initial ToString() call and removed the headers node before letting the value bubble up. This is another viable solution.
Raw code
public class ToStringInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
if (invocation.Method.Name != "ToString")
{
invocation.Proceed();
}
else
{
var result = string.Empty;
var msg = invocation.InvocationTarget as Message;
StringWriter stringWriter = new StringWriter(CultureInfo.InvariantCulture);
XmlDictionaryWriter xmlDictionaryWriter =
XmlDictionaryWriter.CreateDictionaryWriter(new XmlTextWriter(stringWriter));
try
{
ProcessMessage(msg, xmlDictionaryWriter);
xmlDictionaryWriter.Flush();
result = stringWriter.ToString();
}
catch (XmlException ex)
{
result = "ErrorMessage";
}
invocation.ReturnValue = result;
}
}
private void ProcessMessage(Message msg, XmlDictionaryWriter writer)
{
msg.WriteStartEnvelope(writer);
msg.WriteStartBody(writer);
var bodyToStringMethod = msg.GetType()
.GetMethod("BodyToString", BindingFlags.Instance | BindingFlags.NonPublic);
bodyToStringMethod.Invoke(msg, new object[] { writer });
writer.WriteEndElement();
writer.WriteEndElement();
}
}
internal class Program
{
private static void Main(string[] args)
{
var msg = Message.CreateMessage(MessageVersion.Soap11, "*");
msg.Headers.Clear();
var proxyGenerator = new Castle.DynamicProxy.ProxyGenerator();
var proxiedMessage = proxyGenerator.CreateClassProxyWithTarget(msg, new ProxyGenerationOptions(),
new ToStringInterceptor());
var initialResult = msg.ToString();
var proxiedResult = proxiedMessage.ToString();
Console.WriteLine("Initial result");
Console.WriteLine(initialResult);
Console.WriteLine();
Console.WriteLine("Proxied result");
Console.WriteLine(proxiedResult);
Console.ReadLine();
}
}
I did not have your XmlBodyWriter but you could use data contract serializer or your xml body writer
But the trick is to use msg.WriteBody. this will omit the headers
var response = "Hello";
Message msg = Message.CreateMessage(MessageVersion.Soap11, "*",response, new DataContractSerializer(response.GetType()));
msg.Headers.Clear();
var sb = new StringBuilder();
var xmlWriter = new XmlTextWriter(new StringWriter(sb));
msg.WriteBody(xmlWriter);
Should be something like this:
XmlDocument xml = new XmlDocument();
xml.LoadXml(myXmlString); // suppose that myXmlString contains "<Body>...</Body>"
XmlNodeList xnList = xml.SelectNodes("/Envelope/Body");
foreach (XmlNode xn in xnList)
{
string binary1 = xn["Binary1"].InnerText;
string binary2 = xn["Binary2"].InnerText;
Console.WriteLine("Binary: {0} {1}", binary1 , binary2);
}
I been having trouble trying to figure this out. When I think I have it I get told no. Here is a picture of it.
I am working on the save button. Now after the user adds the first name, last name and job title they can save it. If a user loads the file and it comes up in the listbox, that person should be able to click on the name and then hit the edit button and they should be able to edit it. I have code, but I did get inform it looked wackey and the string should have the first name, last name and job title.
It is getting me really confused as I am learning C#. I know how to use savefiledialog but I am not allowed to use it on this one. Here is what I am suppose to be doing:
When the user clicks the “Save” button, write the selected record to
the file specified in txtFilePath (absolute path not relative) without
truncating the values currently inside.
I am still working on my code since I got told that it will be better file writes records in a group of three strings. But this is the code I have right now.
private void Save_Click(object sender, EventArgs e)
{
string path = txtFilePath.Text;
if (File.Exists(path))
{
using (StreamWriter sw = File.CreateText(path))
{
foreach (Employee employee in employeeList.Items)
sw.WriteLine(employee);
}
}
else
try
{
StreamWriter sw = File.AppendText(path);
foreach (var item in employeeList.Items)
sw.WriteLine(item.ToString());
}
catch
{
MessageBox.Show("Please enter something in");
}
Now I can not use save or open file dialog. The user should be able to open any file on the C,E,F drive or where it is. I was also told it should be obj.Also the program should handle and exceptions that arise.
I know this might be a noobie question but my mind is stuck as I am still learning how to code with C#. Now I have been searching and reading. But I am not finding something to help me understand how to have all this into 1 code. If someone might be able to help or even point to a better web site I would appreciate it.
There are many, many ways to store data in a file. This code demonstrates 4 methods that are pretty easy to use. But the point is that you should probably be splitting up your data into separate pieces rather than storing them as one long string.
public class MyPublicData
{
public int id;
public string value;
}
[Serializable()]
class MyEncapsulatedData
{
private DateTime created;
private int length;
public MyEncapsulatedData(int length)
{
created = DateTime.Now;
this.length = length;
}
public DateTime ExpirationDate
{
get { return created.AddDays(length); }
}
}
class Program
{
static void Main(string[] args)
{
string testpath = System.IO.Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "TestFile");
// Method 1: Automatic XML serialization
// Requires that the type being serialized and all its serializable members are public
System.Xml.Serialization.XmlSerializer xs =
new System.Xml.Serialization.XmlSerializer(typeof(MyPublicData));
MyPublicData o1 = new MyPublicData() {id = 3141, value = "a test object"};
MyEncapsulatedData o2 = new MyEncapsulatedData(7);
using (System.IO.StreamWriter w = new System.IO.StreamWriter(testpath + ".xml"))
{
xs.Serialize(w, o1);
}
// Method 2: Manual XML serialization
System.Xml.XmlWriter xw = System.Xml.XmlWriter.Create(testpath + "1.xml");
xw.WriteStartElement("MyPublicData");
xw.WriteStartAttribute("id");
xw.WriteValue(o1.id);
xw.WriteEndAttribute();
xw.WriteAttributeString("value", o1.value);
xw.WriteEndElement();
xw.Close();
// Method 3: Automatic binary serialization
// Requires that the type being serialized be marked with the "Serializable" attribute
using (System.IO.FileStream f = new System.IO.FileStream(testpath + ".bin", System.IO.FileMode.Create))
{
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf =
new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
bf.Serialize(f, o2);
}
// Demonstrate how automatic binary deserialization works
// and prove that it handles objects with private members
using (System.IO.FileStream f = new System.IO.FileStream(testpath + ".bin", System.IO.FileMode.Open))
{
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf =
new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
MyEncapsulatedData o3 = (MyEncapsulatedData)bf.Deserialize(f);
Console.WriteLine(o3.ExpirationDate.ToString());
}
// Method 4: Manual binary serialization
using (System.IO.FileStream f = new System.IO.FileStream(testpath + "1.bin", System.IO.FileMode.Create))
{
using (System.IO.BinaryWriter w = new System.IO.BinaryWriter(f))
{
w.Write(o1.id);
w.Write(o1.value);
}
}
// Demonstrate how manual binary deserialization works
using (System.IO.FileStream f = new System.IO.FileStream(testpath + "1.bin", System.IO.FileMode.Open))
{
using (System.IO.BinaryReader r = new System.IO.BinaryReader(f))
{
MyPublicData o4 = new MyPublicData() { id = r.ReadInt32(), value = r.ReadString() };
Console.WriteLine("{0}: {1}", o4.id, o4.value);
}
}
}
}
As you are writing the employee objects with WriteLine, the underlying ToString() is being invoked. What you have to do first is to customize that ToString() methods to fit your needs, in this way:
public class Employee
{
public string FirstName;
public string LastName;
public string JobTitle;
// all other declarations here
...........
// Override ToString()
public override string ToString()
{
return string.Format("'{0}', '{1}', '{2}'", this.FirstName, this.LastName, this.JobTitle);
}
}
This way, your writing code still keeps clean and readable.
By the way, there is not a reverse equivalent of ToSTring, but to follow .Net standards, I suggest you to implement an Employee's method like:
public static Employee Parse(string)
{
// your code here, return a new Employee object
}
You have to determine a way of saving that suits your needs. A simple way to store this info could be CSV:
"Firstname1","Lastname 1", "Jobtitle1"
" Firstname2", "Lastname2","Jobtitle2 "
As you can see, data won't be truncated, since the delimiter " is used to determine string boundaries.
As shown in this question, using CsvHelper might be an option. But given this is homework and the constraints therein, you might have to create this method yourself. You could put this in Employee (or make it override ToString()) that does something along those lines:
public String GetAsCSV(String firstName, String lastName, String jobTitle)
{
return String.Format("\"{0}\",\"{1}\",\"{2}\"", firstName, lastName, jobTitle);
}
I'll leave the way how to read the data back in as an exercise to you. ;-)
I have an application that serializes data using BinaryFormatter. A member was added to the class that was serialized from one version to the next without changing the class name. Code was added to handle the possible absence of the added member in old serialized files:
private void readData(FileStream fs, SymmetricAlgorithm dataKey)
{
CryptoStream cs = null;
try
{
cs = new CryptoStream(fs, dataKey.CreateDecryptor(),
CryptoStreamMode.Read);
BinaryFormatter bf = new BinaryFormatter();
string string1 = (string)bf.Deserialize(cs);
// do stuff with string1
bool bool1 = (bool)bf.Deserialize(cs);
// do stuff with bool1
ushort ushort1 = (ushort)bf.Deserialize(cs);
// do stuff with ushort1
// etc. etc. ...
// this field was added later, so it may not be present
// in the serialized binary data. Check for it, and if
// it's not there, do some default behavior
NewStuffIncludedRecently newStuff = null;
try
{
newStuff = (NewStuffIncludedRecently)bf.Deserialize(cs);
}
catch
{
newStuff = null;
}
_newStuff = newStuff != null ?
new NewStuffIncludedRecently(newStuff) :
new NewStuffIncludedRecently();
}
catch (Exception e)
{
// ...
}
finally
{
// ...
}
}
The point I'm at now is that I'd really like to just rinse and repeat with another member I'd like to add, which would mean I'd add another field and try-catch block similar to that for NewStuffIncludedRecently.
I had thought of just making the entire class [Serializable] but wouldn't that break compatibility with the old serialized data?
My main concern is that I'm not clear how the deserialization works. If I add in handling for another optional field similarly to above, will it work? What are other options I have for handling these changes better?
Thanks in advance as always.
If you mark the new fields with [OptionalField] it should work, but I have heard reports of flakiness in some cases. I can't say for sure, since I avoid BinaryFormatter, because it has so many issues when versioning :) (plus, it isn't as "tight" as some alternatives, and has severe issues if you want to go cross-platform, or to CF/SL etc)
If you are implementing ISerializable, you might try:
foreach(SerializationEntry entry in info) {
switch(entry.Name) {
case "Name": Name = (string)info.Value;
case "Id": Id = (int)info.Value;
...
}
}
But again, must stress - this is doing things the hard way :p
With this approach, you only process the data that is actually there.