I'm writing my own debugger visualiser. All works great to show up to visualiser with the data.
Now I add the code for more clearness:
public class MyVisualiserObjectSource : VisualizerObjectSource
{
public override void GetData(object target, Stream outgoingData)
{
string data= target as string;
var writer = new StreamWriter(outgoingData);
writer.Write(data);
writer.Flush();
}
}
public class MyVirtualizer : DialogDebuggerVisualizer
{
protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
{
var streamReader = new StreamReader(objectProvider.GetData());
string data = streamReader.ReadToEnd();
using (var form = new MyVirtualizerForm(data))
{
windowService.ShowDialog(form);
}
}
}
The string here is passed to the visualizer and show my own form. It works.
But now I want to pass back the modified data from the form to the variable.
How do I do that?
Edit:
I found out that I need to override the TransferData method in VisualizerObjectSource. But in the MSDN is no detail information about how I implement this correctly.
Can someone help me please?
Edit 2:
I looked with IL-Spy what TransferData method does. It throws an exception.
So I override the method. But it is still not working. In the incomingData is the modified string from the Form. But I do not get back this value into the variable :(
public class StringVisualiserObjectSource : VisualizerObjectSource
{
public override void GetData(object target, Stream outgoingData)
{
var data = target as string;
var writer = new StreamWriter(outgoingData);
writer.Write(data);
writer.Flush();
}
public override void TransferData(object target, Stream incomingData, Stream outgoingData)
{
incomingData.CopyTo(outgoingData);
}
}
You just need to add a public property to your form that contains the data you wish to "pass back". For example, if your form contains a textbox called MyDataTextBox, you need to create a public property on your form like:
public MyVirtualizerForm : System.Windows.Form
{
public string MyData
{
get{ return MyDataTextBox.Text;}
}
}
You can then get access to the text of the textbox when the form is closed by doing the following:
Edited - Passing the data back to the variable
This assumes that the data you are getting back from the form is a string.
using (var form = new MyVirtualizerForm(data))
{
windowService.ShowDialog(form);
var formData = form.MyData;
using (MemoryStream returnStream =
new MemoryStream(ASCIIEncoding.Default.GetBytes(formData)))
{
objectProvider.TransferData(returnStream);
}
}
Is this what you want to achieve? Also, you may find the following link useful as reference: http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.debuggervisualizers.ivisualizerobjectprovider.transferdata.aspx
Related
I have two WinForms applications and want to be able to drag objects from one to the other.
My data object code is very simple:
// the data object
[ComVisible(true)]
[Serializable]
public class MyData : ISerializable {
public int Value1 { get; set; }
public int Value2 { get; set; }
public MyData() { }
public MyData(int value1, int value2) {
Value1 = value1;
Value2 = value2;
}
public void GetObjectData(SerializationInfo info, StreamingContext context) {
info.AddValue(nameof(Value1), Value1);
info.AddValue(nameof(Value2), Value2);
}
}
The object is part of a dll, which is referenced in both of my WinForms applications.
I'm initializing the drag drop using:
// inside some control
MyData toBeTransmitted = new MyData(0, 0);
IDataObject dataObject = new DataObject(DataFormats.Serializable, toBeTransmitted);
this.DoDragDrop(dataObject, DragDropEffects.All);
and handling it using:
// inside some drag over handler
IDataObject dataObject = dragEvent.DataObject;
if (dataObject.GetDataPresent(DataFormats.Serializable)) {
object obj = e.DataObject.GetData(DataFormats.Serializable);
}
All this works fine, as long as I'm dragging and dropping data inside a single application.
But as soon as I drag data over from one process to the other retrieving the dragged data returns an object of type System.__ComObject instead of MyData.
How can I retrieve the actual data contained inside the IDataObject?
(note: I also tried using a custom format instead of DataFormats.Serializable, no luck there.)
The problem:
When DataFormats.Serializable is specified, the DataObject class uses a BinaryFormatter to serialize a class object that implement ISerializable (BTW, you should add a public MyData(SerializationInfo info, StreamingContext context) constructor).
The BinaryFormatter object is created using the standard form with a default Binder, which implies that the serialized object contains strict Assembly information.
As a consequence, you can Drag/Drop your object(s) to/from Processes that represent instances of the same Assembly without problem, but if the Assemblies are different or their version doesn't match, the BinaryFormatter fails deserialization and you get an unwrapped IComDataObject as result.
You could marshal this COM object yourself, which means you have to build a compatible FORMATETC struct object (System.Runtime.InteropServices.ComTypes.FORMATETC), get the STGMEDIUM from IDataObject.GetData([FORMATETC], out [STGMEDIUM]), get the IStream object using Marshal.GetObjectForIUnknown(), passing the [STGMEDIUM].unionmember pointer.
Then create a BinaryFormatter specifying a less restrictive / custom Binder and deserialize the Stream ignoring or replacing the Assembly name.
Before you ask, you cannot set the [SerializationInfo].AssemblyName (even though it's not read-only) directly in your ISerializable class, won't work.
A possible solution:
A simple approach is to replace the BinaryFormatter with a different serializer and create the IDataObject setting a custom format (or a predefined DataFormat compatible with the generated data).
An example using XmlSerializer as serializer and a MemoryStream:
The class object can be simplified, removing the ISerializable implementation:
[Serializable]
public class MyData {
public int Value1 { get; set; }
public int Value2 { get; set; }
public MyData() { }
public MyData(int value1, int value2)
{
Value1 = value1;
Value2 = value2;
}
public override string ToString()
{
return $"Value1: {Value1}, Value2: {Value2}";
}
}
Two static methods used to generate an IDataObject on the Source side and to extract its content on the Target side. XmlSerializer is used to serialize / deserialize the class object(s).
private static IDataObject SetObjectData<T>(object value, string format) where T : class
{
using (var ms = new MemoryStream())
using (var sw = new StreamWriter(ms)) {
var serializer = new XmlSerializer(typeof(T), "");
serializer.Serialize(sw, value);
sw.Flush();
var data = new DataObject(format, ms.ToArray());
// Failsafe custom data type - could be a GUID, anything else, or removed entirely
data.SetData("MyApp_DataObjectType", format);
return data;
};
}
private static T GetObjectData<T>(IDataObject data, string format) where T : class
{
// Throws if the byte[] cast fails
using (var ms = new MemoryStream(data.GetData(format) as byte[])) {
var serializer = new XmlSerializer(typeof(T));
var obj = serializer.Deserialize(ms);
return (T)obj;
}
}
In the example, I'm using a Dictionary<string, Action> to call methods nased on the Type contained in the IDataObject received from the DragDrop operation.
This because I suppose you could transfer different Types. Of course you can use anything else.
You could also use a common Interface and just use this as <T>. It would simplify a lot the whole implementation (and future expansion, if generic enough methods and properties can be defined).
Dictionary<string, Action<IDataObject>> dataActions = new Dictionary<string, Action<IDataObject>>() {
["MyData"] = (data) => {
// The Action delegate deserialzies the IDataObject...
var myData = GetObjectData<MyData>(data, "MyData");
// ...and calls a method passing the class object
MessageBox.Show(myData.ToString());
},
["MyOtherData"] = (data) => {
var otherData = GetObjectData<MyOtherData>(data, "MyOtherData");
MessageBox.Show(otherData.ToString());
}
};
▶ On the Source side (Drag/Drop initiator):
Point mouseDownPos = Point.Empty;
private void SomeSourceControl_MouseDown(object sender, MouseEventArgs e)
{
mouseDownPos = e.Location;
}
private void SomeSourceControl_MouseMove(object sender, MouseEventArgs e)
{
MyData toBeTransmitted = new MyData(100, 100);
if (e.Button == MouseButtons.Left &&
((Math.Abs(e.X - mouseDownPos.X) > SystemInformation.DragSize.Width) ||
(Math.Abs(e.Y - mouseDownPos.Y) > SystemInformation.DragSize.Height))) {
var data = SetObjectData<MyData>(toBeTransmitted, "MyData");
DoDragDrop(data, DragDropEffects.All);
}
}
▶ On the Target side (Drag/Drop target)
private void SomeTargetControl_DragEnter(object sender, DragEventArgs e)
{
var formats = e.Data.GetFormats();
// Verify that a Data Type is defined in the Dictionary
if (formats.Any(f => dataActions.ContainsKey(f))) {
e.Effect = DragDropEffects.All;
}
}
private void SomeTargetControl_DragDrop(object sender, DragEventArgs e)
{
// Double check: the fail-safe Data Type is present
string dataType = (string)e.Data.GetData("MyApp_DataObjectType");
// If the Data Type is in the Dictionary, deserialize and call the Action
if (dataActions.ContainsKey(dataType)) {
dataActions[dataType](e.Data);
}
}
Pretty much what it says in the title.
I've built a second .dll file which contains my Content Importer/Processor and added that .dll file in my 'References' section for my Content Manager for my project, but nothing. The Manager doesn't detect my custom importer/processor. No idea what's going on and I wasn't able to find anyone else having an issue like this, so I was hoping someone more experienced could help me out here!
I am using Json.NET for the Json Deserialization by the way.
Thank you in advence :)
MapJson Code:
public class MapJson
{
[JsonProperty("name")]
public String Name = "";
[JsonProperty("width")]
public Int32 MapWidth = 0;
[JsonProperty("height")]
public Int32 MapHeight = 0;
}
Importer Code:
[ContentImporter(".amap", DefaultProcessor = "MapProcessor", DisplayName = "Map Importer - Engine")]
public class MapImporter : ContentImporter<MapJson>
{
public override MapJsonImport(string filename, ContentImporterContext context)
{
string json = new FileHandle(filename).ReadAll();
MapJson data = JsonConvert.DeserializeObject<MapJson>(json);
return data;
}
}
Processor Code:
[ContentProcessor(DisplayName = "Map Processor - Engine")]
public class MapProcessor : ContentProcessor<MapJson, MapJson>
{
public override MapJson Process(MapJson input, ContentProcessorContext context)
{
return input;
}
}
Writer Code:
[ContentTypeWriter]
public class MapWriter : ContentTypeWriter<MapJson>
{
protected override void Write(ContentWriter writer, MapJson value)
{
writer.Write(value.Name);
writer.Write(value.MapWidth);
writer.Write(value.MapHeight);
}
}
Reader Code:
public class MapReader : ContentTypeReader<Map>
{
protected override Map Read(ContentReader reader, Map existingInstance)
{
MapJson data = new MapJson();
data.Name = reader.ReadString();
data.MapWidth = reader.ReadInt32();
data.MapHeight = reader.ReadInt32();
// this constructor just sets my 'Map' class's Name, MapWidth and MapHeight variables
return new Map(data);
}
}
Well uhh this is embarrassing...
The solution was to first build the game project, then to actually re-build the content importer/processor project, and then to link it with the content manager!
I feel so stupid haha
I can't get what's the problem. Please check my code's fragments. Each time when I add resource data, it clears last data and writes new records in .resx.
For example, Applications.resx has "MyApp1" key with "MyApp1Path" value. Next time if I add "MyApp2" key with "MyApp2Path" value, I notice that {"MyApp1", "MyApp1Path"} doesn't exist.
//Adding Application in Applications List
ResourceHelper.AddResource("Applications", _appName, _appPath);
Here is ResourceHelper class:
public class ResourceHelper
{
public static void AddResource(string resxFileName, string name, string value)
{
using (var resx = new ResXResourceWriter(String.Format(#".\Resources\{0}.resx", resxFileName)))
{
resx.AddResource(name, value);
}
}
}
Yes this is expected, ResXResourceWriter just adds nodes, it doesn't append.
However, you could just read the nodes out, and add them again
public static void AddResource(string resxFileName, string name, object value)
{
var fileName = $#".\Resources\{resxFileName}.resx";
using (var writer = new ResXResourceWriter(fileName))
{
if (File.Exists(fileName))
{
using (var reader = new ResXResourceReader(fileName))
{
var node = reader.GetEnumerator();
while (node.MoveNext())
{
writer.AddResource(node.Key.ToString(), node.Value);
}
}
}
writer.AddResource(name, value);
}
}
Disclaimer, untested and probably needs error checking
I have a WCF message inspector which inspects requests and responses: Message. The inspector works fine. A Message object can only be read once so once you read it, you cannot simply propagate as WCF will complain that the message has been read. Therefore, I am creating a brand new copy of the message and propagating that.
I have designed a class that allows message reading and after the caller has read whatever they want, they need to call Close which will return a copy of the message. Here is the skeleton of my class:
using System.ServiceModel.Channels;
internal abstract class MessageReader
{
internal string ReadSomething(string id)
{
// Return string
}
internal string ReadSomethingElse(string id)
{
// Return string
}
internal Message Close()
{
// Create copy and return it.
}
}
Users of my class may forget to call Close() which is fine because WCF will yell at them. Right now I have documentation to let users know they need to call Close().
Here is the question
Is there a pattern, or something similar, to C#'s using construct but one which returns an object at the end? This will be really convenient because then users of my class can just use a construct like that and at the end it will return the copy of the message. Something like this:
UsingSomeConstruct(var reader = new MessageReader(ref originalMessage))
{
var a = reader.ReadSomething("something");
var b = reader.ReadSomethingElse("something");
// Do something with what was read
}
// At this point originalMessage will be the copy of the message and no longer the original message.
EDIT
I thought about hacking IDisposable to achieve this but I am NOT going to do it that way so looking for other ideas.
There is no such language construct of course.
What I could suggest is to use IDisposable for cleaning up, and add ref Message message argument to each ReadXXX method. I know it will not be so convenient for your users, but from the other side they cannot forget passing the parameter.
So the implementation would be something like this:
internal class MessageReader : IDisposable
{
private MessageBuffer buffer;
private Message message;
private void Release()
{
if (buffer == null) return;
buffer.Close();
buffer = null;
message = null;
}
protected void OnReadRequest(ref Message message)
{
if (message == null) throw new ArgumentNullException("message");
if (this.message == message) return;
Release();
this.buffer = message.CreateBufferedCopy(int.MaxValue);
message = this.message = buffer.CreateMessage();
}
public void Dispose()
{
Release();
}
internal string ReadSomething(ref Message message, string id)
{
OnReadRequest(ref message);
// Return string
}
internal string ReadSomethingElse(ref Message message, string id)
{
OnReadRequest(ref message);
// Return string
}
}
and the sample usage:
using (var reader = new MessageReader())
{
var a = reader.ReadSomething(ref originalMessage, "something");
var b = reader.ReadSomethingElse(ref originalMessage, "something");
// Do something with what was read
}
// At this point originalMessage will be the copy of the message and no longer the original message.
The way I'd do this is as follows:
public MessageReader: IDisposable
{
public static MessageReader Create(ref Message message)
{
var buffer = message.CreateBufferedCopy(/*whatever is fit*/);
try
{
var reader = new MessageReader(buffer);
message = buffer.CreateMessage();
return reader;
}
catch
{
buffer.Close();
throw;
}
}
private readonly MessageBuffer buffer;
private bool disposed;
private MessageReader(MessageBuffer buffer) { this.buffer = buffer; }
public void Dispose()
{
if (disposed)
return;
buffer.Close();
disposed = true;
}
public string Read(string id)
{
var newCopy = buffer.CreateMessage();
//work with new copy...
}
}
And you'd simply use it like this:
using (var reader = MessageReader.Create(ref message))
//message here is already an untouched copy with no need of user active
//intervention and is never touched again by the reader.
{
var a = reader.Read("something"); //reads copy
...
}
IMHO, this is as clean as it can be. Note that MessageReader implements IDisposable exclusively because it holds a reference to the disposable private MessageBuffer.
Thanks to all the help from #InBetween, #quetzalcoatl, and #Ivan Stoev. Upvoted your answers because it helped me arrive at the following.
In the constructor, I create a copy of the message and set the original message to the copy. Since the status of this message is Created WCF will be happy propogating it. I create another copy and use that for reading multiple times.
#Ivan said but what if the user does not ask for anything to be read then the copying was wasted work. That is a good point but in my case, this is an interceptor and all messages are intercepted to be read.
Here is the code I ended up with suggestions from all of you:
public class MessageReader : IDisposable {
private readonly Message message;
public MessageReader(ref Message originalMessage) {
using( var buffer = originalMessage.CreateBufferedCopy( int.MaxValue ) ) {
// Keep original message for reading
this.message = buffer.CreateMessage();
// Set original message to a copy of the original
originalMessage = buffer.CreateMessage();
}
}
public int ReadSomething(string id) {
// Read from this.message;
}
public int ReadSomethingElse(string id) {
// Read from this.message;
}
public void Close() {
this.Dispose();
}
public void Dispose() {
this.message.Close();
}
}
The caller can either use it in a using block or without it. The using block is used for good reasons and not as a hack.
public object AfterReceiveRequest(ref Message request, IClientChannel channel,
InstanceContext instanceContext) {
try {
using( var rdr = new MessageReader(ref request) ) {
var value= rdr.ReadSomething( someIdentifier );
return value;
}
}
catch( System.Exception ex ) {
throw CreateFault( ex, request );
}
}
Nope, there is no such construct. It is simply too specific to exist there out of the box. There are extension methods which often are very helpful, but you won't be able to use them on this ref Message parameter..
However, if you are willing to use ref at all, then why dont simply include all that logic it in Reader's constructor?
Here's an example, somewhat contrived, but it should show what I mean. Like others mentioned in comments, I also suggest implementing IDisposable on the Reader object instead of Close, so I included that already.
TL;DR: In example below, the most important thing is in Reader(ref msg) constructor which clones the message, copies the data, and replaces the original message with a safe-message class which can be read many times..
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace Rextester
{
public class Program
{
public static void Main(string[] args)
{
// real-world variables, keep them typed as base Message
// to be able to silently replace them with different objects
Message original1;
Message original2;
// let's construct some one-time readable messages
{
var tmp1 = new OneTimeMessage();
tmp1.data["mom"] = "dad";
tmp1.data["cat"] = "dog";
original1 = tmp1;
var tmp2 = new OneTimeMessage();
tmp2.data["mom"] = "dad";
tmp2.data["cat"] = "dog";
original2 = tmp2;
}
// test1 - can't read twice
Console.WriteLine("test0A:" + original1.GetData("mom"));
//Console.WriteLine("test0B:" + original1.GetData("mom")); // fail
// test2 - can read twice with Reader's help
var backup1 = original2;
using(var rd1 = new Reader(ref original2))
{
Console.WriteLine("test1A:" + rd1.ReadSomething("mom"));
}
var backup2 = original2;
using(var rd2 = new Reader(ref original2))
{
Console.WriteLine("test1A:" + rd2.ReadSomething("mom"));
//^ ok - becase Reader replaced 'original2' with SafeMessage
}
// test3: Reader's ctor is intelligent
// so no more SafeMessages created during future usage
var backup3 = original2;
using(var rd3 = new Reader(ref original2))
{
}
var backup4 = original2;
using(var rd4 = new Reader(ref original2))
{
}
Console.WriteLine("checking for copies:" + (original2 == backup1));
Console.WriteLine("checking for copies:" + (original2 == backup2));
Console.WriteLine("checking for copies:" + (original2 == backup3));
Console.WriteLine("checking for copies:" + (original2 == backup4));
}
}
}
public abstract class Message
{
public abstract string GetData(string id);
}
public class OneTimeMessage : Message // this models your current one-time-readable message
{
public IDictionary<string, string> data = new Dictionary<string, string>();
public override string GetData(string id)
{
var tmp = data[id];
data.Remove(id);
// that's nonsense, but I want to show that you can't
// read the same thing twice from this object
return tmp;
}
}
public class SafeMessage : Message
{
public IDictionary<string, string> data;
public override String GetData(string id)
{
return data[id];
}
public SafeMessage(Message msg)
{
// read out the full msg's data and store it
// since this is example, we can do it in a pretty simple way
// in your code that will probably be more complex
this.data = new Dictionary<string,string>(((OneTimeMessage)msg).data);
}
}
public class Reader : IDisposable
{
private Message message;
public Reader(ref Message src)
{
src = src is SafeMessage ? src : new SafeMessage(src);
this.message = src;
}
public string ReadSomething(string id){ return message.GetData(id); }
public void Dispose(){ Close(); }
public void Close(){ message=null; Console.WriteLine("reader closed"); }
}
EDIT: improved example
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Channels;
using System.Text.RegularExpressions;
using System.Xml;
namespace MyProgram
{
public class Program
{
public static void Main(string[] args)
{
// real-world variables, keep them typed as base Message
// to be able to silently replace them with different objects
Message original1;
Message original2;
// let's construct some one-time readable messages
{
original1 = new TheMessage("dad", "dog");
original2 = new TheMessage("dad", "dog");
}
// test1 - can't read twice
Console.WriteLine("test0A:" + original1.GetReaderAtBodyContents().ReadOuterXml());
// Console.WriteLine("test0B:" + original1.GetReaderAtBodyContents().ReadOuterXml()); // fail: InvalidOperationException - it was already read
// test2 - can read ONCE with Reader's help, but the message is replaced and is usable again
var backup1 = original2;
using (var rd1 = new ReaderOnce(ref original2))
{
Console.WriteLine("is message replaced after opening Reader:" + (original2 != backup1));
Console.WriteLine("test1A:" + rd1.ReadBodyXml());
// Console.WriteLine("test1B:" + rd1.ReadBodyXml()); // fail: InvalidOperationException - it was already read
}
// test3 - can read MANY TIMES with ReaderMany's help
// also note we use 'original2' again, which was already used above, so in fact ReaderOnce really works as well
var backup2 = original2;
using (var rd1 = new ReaderMany(ref original2))
{
Console.WriteLine("is message replaced after opening Reader:" + (original2 != backup2));
Console.WriteLine("test2A:" + rd1.ReadBodyXml());
Console.WriteLine("test2B:" + rd1.ReadBodyXml()); // ok
}
Console.WriteLine("Press enter to exit");
Console.ReadLine();
}
}
}
// solution1
public class ReaderOnce : IDisposable
{
private Message localCopy;
public ReaderOnce(ref Message src)
{
// create a WCF MessageBuffer to assist in copying messages
// btw. I suppose you should set some sane limit instead of that below
using (var tempBuffer = src.CreateBufferedCopy(int.MaxValue))
{
src = tempBuffer.CreateMessage(); // FIRST copy for outer use
localCopy = tempBuffer.CreateMessage(); // SECOND copy for internal use in the Reader
}
}
public void Dispose() { Close(); }
public void Close()
{
localCopy.Close(); // but that does NOT affect FIRST copy sent to outer scope outside reader
Console.WriteLine("reader closed");
}
public string ReadBodyXml() // careful: that's again ONE TIME readable
{
return localCopy.GetReaderAtBodyContents().ReadOuterXml();
}
}
// solution2
public class ReaderMany : IDisposable
{
private MessageBuffer localBuffer;
public ReaderMany(ref Message src)
{
localBuffer = src.CreateBufferedCopy(int.MaxValue);
src = localBuffer.CreateMessage(); // FIRST copy for outer use
}
public void Dispose() { Close(); }
public void Close()
{
localBuffer.Close();
Console.WriteLine("reader closed");
}
public string ReadBodyXml() // this is readable multiple times
{
using (var tmp = localBuffer.CreateMessage())
return tmp.GetReaderAtBodyContents().ReadOuterXml();
}
}
// let's fake some Message type to have something to test the Reader on
public class TheMessage : Message
{
public override MessageHeaders Headers => _mh;
public override MessageProperties Properties => _mp;
public override MessageVersion Version => _mv;
private MessageHeaders _mh;
private MessageProperties _mp;
private MessageVersion _mv;
private string data1;
private string data2;
// btw. below: surprise! XmlDictionaryWriter is in "System.Runtime.Serialization", not in "System.Xml"
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
writer.WriteStartElement("foo");
writer.WriteAttributeString("data1", data1);
writer.WriteAttributeString("data2", data2);
writer.WriteEndElement();
}
public TheMessage(string data1, string data2)
{
// remember, this class is just an example, you will work on your own messages you already have
_mv = MessageVersion.Soap12;
_mh = new MessageHeaders(_mv);
_mp = new MessageProperties();
// below: yeah, that's super-naive and wrong, but that's an example
this.data1 = data1;
this.data2 = data2;
}
}
There is no language construct in c# that does what you are asking. As stated in comments, you could abuse IDisposable and the language and use a using block to achieve what you want.
But, I fail see what you are gaining, you are just punting the problem; now users will need to remember to use usinginstead of Close. The latter is simple and clean, the former uses a very known language construct to do something different to what it was thought for, something that will potentially be very confusing.
i get an empty xmlfile after serializing an object. I'm using Monodevelop and Unity 4. I searched for such a long time mostly in this community, but i only found difficult problems with even more difficult answers :) I think mine is so simple, please help me. (I am new to c#)
The serialized object is this:
[System.Serializable]
public class information {
private string data1;
private string data2;
private string data3;
public void Data1(string text)
{
data1 = text;
}
public string GetData1 ()
{
return data1;
}
public void Data2(string text)
{
data2 = text;
}
public string GetData2 ()
{
return data2;
}
public void Data3(string text)
{
data3 = text;
}
}
the serializing class is this, here might be the problem:
public class SaveXml {
public void SaveData(object obj, string filename)
{
XmlSerializer sr = new XmlSerializer(obj.GetType());
TextWriter writer = new StreamWriter(filename);
sr.Serialize(writer, obj);
writer.Close();
}
public string Load()
{
if(File.Exists("accdata.xml"))
{
XmlSerializer xs = new XmlSerializer(typeof(information));
FileStream read = new FileStream("accdata.xml",FileMode.Open, FileAccess.Read, FileShare.Read);
information info = (information)xs.Deserialize(read);
return info.GetData1();
}
else
{
return "file does not exist";
}
}
And the serializing and the serialized object get called by a menu that has this 2 buttons:
if(GUI.Button(new Rect(10,50,300,100),"Save"))
{
SaveXml saver = new SaveXml();
information infol = new information();
infol.Data1("textone");
infol.Data2("texttwo");
infol.Data3( "textthree");
saver.SaveData(infol, "accdata.xml");
}
if(GUI.Button(new Rect(500,50,300,100),"Load"))
{
SaveXml saver1 = new SaveXml();
text = saver1.Load();
}
so the variable text that is declared in the class menu, should be "textone", after i clicked the Save Button and the LoadButton. The Savebutton creates a file that is empty.
The Deserialization seems to work but of course there is no String in the data1 variable in Information so the variable in the menu called text is empty too. I get no errors and i can work with the object after serialization.
So why doesnt my serialization work? Please help me. I excuse for my bad english and mistakes, i am new to stackoverflow.
Xml serializer serializes public fields/properties not methods. Change your methods to properties. For ex,
public string Data2
{
set { data2 = value; }
get { return data2; }
}
So your information class can be
public class Information
{
public string Data1 { get; set; }
public string Data2 { get; set; }
public string Data3 { get; set; }
}
BTW: you don't need this Serializable attribute. It is only used by BinaryFormatter
I'm not sure but from what i see you don't have any public fields... Take a look here
And also, why don't you just use auto getter/setter ?
According to this MSDN support article, using XmlSerializer the way you have performs only "shallow" serialization - it only serializes public fields/properties. To serialize private data requires "deep" serialization which appears to be a whole other animal.