.NET Forum / .NET Framework / CLR / July 2006
OptionalFieldAttribute not needed with BinaryFormatter in .net v2.
|
|
Thread rating:  |
asanford - 06 Jul 2006 22:45 GMT The MSDN documentation, along with various articles online, such as this one
http://msdn.microsoft.com/msdnmag/issues/04/10/AdvancedSerialization/
claim that the BinaryFormatter will throw an exception if you try to deserialize a stream that has missing members in it, and that the new OptionalFieldAttribute allows you to avoid this problem, by marking the new members as optional. Well, it seems that even if you don't specify this attribute, no exceptions are thrown, no matter if you add members, remove members, or change the assembly version. I also did tests with both signed and unsigned assemblies, and it made no difference. Is there some other global setting that could affect the behavior? I thought maybe the BinaryFormatter.AssemblyFormat might be set to simple, but the doc says the default is for Full, not Simple (I'm doing all my tests with the caching application block's isolated storage backing, which I don't think changes the AssemblyFormat from the default.) I also looked into the files containing the serialized data, and I can see the assembly name, version number, public key, etc., so I'm fairly sure AssemblyFormat is set to Full.
Any help?
BTW, I also found the following post indicating someone else already found this problem:
http://groups.google.com/group/microsoft.public.dotnet.framework/browse_thread/t hread/465a8713be2f153/173410a79ee7de1a?lnk=st&q=OptionalFieldAttribute+BinaryFor matter&rnum=1&hl=en#173410a79ee7de1a
Thanks!
"Jeffrey Tan[MSFT]" - 07 Jul 2006 04:22 GMT Hi Asanford2000,
Thanks for your post!
I will try to write a sample project to reproduce this problem. For efficiency's sake, is it convinient for you to provide a full code snippet or a sample project for us to reproduce? This will help us identify the problem more efficiently. Anyway, I will update you once I have reproduced the problem. Thanks.
Best regards, Jeffrey Tan Microsoft Online Community Support ================================================== When responding to posts, please "Reply to Group" via your newsreader so that others may learn and benefit from your issue. ================================================== This posting is provided "AS IS" with no warranties, and confers no rights.
"Jeffrey Tan[MSFT]" - 07 Jul 2006 11:14 GMT Hi,
Sorry for letting you wait.
After performing some modification to the MSDN article project I can reproduce the behavior: the OptionalFieldAttribute is not needed for version tolerance.
Actually, this is caused by BinaryFormatter.AssemblyFormat property as you suspected.
In .Net 1.1, we can set the "BinaryFormatter.AssemblyFormat" to "FormatterAssemblyStyle.Simple" to allow more version tolerance. In this way, no exception will be thrown if a particular field is not found from the stored stream. In .Net 2.0, the property is by default "Simple", not "Full"(You can verify this by watching BinaryFormatter.AssemblyFormat in debugger), so even we do not specify "OptionalFieldAttribute", no exception is thrown. "OptionalFieldAttribute" is needed only when "FormatterAssemblyStyle.Full" needs to work with missing fields.
Hope this helps.
Best regards, Jeffrey Tan Microsoft Online Community Support ================================================== When responding to posts, please "Reply to Group" via your newsreader so that others may learn and benefit from your issue. ================================================== This posting is provided "AS IS" with no warranties, and confers no rights.
asanford - 07 Jul 2006 16:46 GMT Thanks; I'm surprised the AssemblyFormat is set to simple, as the first article I referenced from MSDN says "...The default value of AssemblyFormat is FormatterAssemblyStyle.Full..." and the article discusses v1 and v2 of the framework. I guess they're talking about v1 in that sentence?
Anyhow, I now have two problems:
1) If a serialize non-Dictionary classes, then setting AssemblyFormat to simple seems to cause the OptionalFieldAttribute to not be needed, AND seems to make the deserializer not care if the assembly version changes. However, if I serialize dictionary classes, then the deserializer requires the assembly version to match, even if I set AssemblyFormat to simple. Is there a way to get the rules to apply consistently?
2) If I set AssemblyFormat=full, then I get exceptions if my assembly versions don't match, regardless of the OptionalFieldAttribute. The only way I can find to avoid this exception in that case is to set the formatter's Binder or SurrogateSelectior properties. Is there another way to do this, perhaps some attribute I can attach to my class, rather than having to interact with the formatter? The reason I ask is that I'm using the formatter indirectly, via the Caching Application Block v2.0 (with isolated storage persistence.)
Thanks, -Andy
> Hi, > [quoted text clipped - 27 lines] > ================================================== > This posting is provided "AS IS" with no warranties, and confers no rights. "Jeffrey Tan[MSFT]" - 10 Jul 2006 09:20 GMT Hi Andy,
Thanks for your feedback!
> I guess they're talking about v1 in that sentence? Yes, I assume he is talking about .Net1.1. Also, since this article is written in October 2004, which .Net2.0 is still in beta version, if the author is talking about .Net2.0, I assume that this behavior has changed in RTM.
> 1) Is there a way to get the rules to apply consistently? Sorry, I am not sure I understand you completely. Can you provided more information regarding the "dictionary classes", which classes in FCL do you refer to? Is it possible for you to provide a little sample project for us to reproduce this problem? Thanks
> 2) Is there another way to do this, > perhaps some attribute I can attach to my class, rather than having to > interact with the formatter? I have performed some research in this issue and found the key point lies in the System.Runtime.Serialization.Formatters.Binary.ObjectReader.Bind method and System.Runtime.Serialization.Formatters.Binary.ObjectReader.FastBindToType method.(From Reflector), below is the code snippet:
internal Type Bind(string assemblyString, string typeString) { Type type1 = null; if ((this.m_binder != null) && !this.IsInternalType(assemblyString, typeString)) { type1 = this.m_binder.BindToType(assemblyString, typeString); } if (type1 == null) { type1 = this.FastBindToType(assemblyString, typeString); } return type1; }
internal Type FastBindToType(string assemblyName, string typeName) { .... if (this.bSimpleAssembly) { try { ObjectReader.sfileIOPermission.Assert(); try { StackCrawlMark mark1 = StackCrawlMark.LookForMe; assembly2 = Assembly.LoadWithPartialNameInternal(assemblyName, null, ref mark1); if ((assembly2 == null) && (assemblyName != null)) { assembly2 = Assembly.LoadWithPartialNameInternal(new AssemblyName(assemblyName).Name, null, ref mark1); } } finally { CodeAccessPermission.RevertAssert(); } } catch (Exception) { } } else { try { ObjectReader.sfileIOPermission.Assert(); try { assembly2 = Assembly.Load(assemblyName); } finally { CodeAccessPermission.RevertAssert(); } } catch (Exception) { } } .... }
As we can see, FastBindToType checks bSimpleAssembly field(which maps to FormatterAssemblyStyle.Full/Simple) to use Assembly.Load or Assembly.LoadWithPartialNameInternal. From Applied .net Framwork written by "Jeffrey Richter", we know that Assembly.Load will check the version explicitly, while Assembly.LoadWithPartialNameInternal will ignore the assembly version.
Also, in Bind method, we can see that Binder.BindToType method gives us a hook over the FastBindToType method, which may help us to jmp over FastBindToType method.
OptionalFieldAttribute is used in System.Runtime.Serialization.Formatters.Binary.ReadObjectInfo.GetMemberTypes method:
internal Type[] GetMemberTypes(string[] inMemberNames, Type objectType) { ...... if (!flag2) { object[] objArray1 = this.cache.memberInfos[num2].GetCustomAttributes(typeof(OptionalFieldAttribu te), false); if (((objArray1 == null) || (objArray1.Length == 0)) && !this.bSimpleAssembly) { throw new SerializationException(string.Format(CultureInfo.CurrentCulture, Environment.GetResourceString("Serialization_MissingMember"), new object[] { this.cache.memberNames[num2], objectType, typeof(OptionalFieldAttribute).FullName })); } } } } return typeArray1; }
So FCL uses System.Reflection.MemberInfo.GetCustomAttributes method to access the OptionalFieldAttribute. If you want to determine if there is any additionally attribute can meet your need without changing the formatter itself, you may set a breakpoint in System.Reflection.MemberInfo.GetCustomAttributes method in debugger and monitor if Deserialize method will trigger other breakpoint that is not looking for OptionalFieldAttribute. Based on my research, I can not find a such attribute. So you may still have to touch the formatter itself to hook the process.
Hope this helps. If you have any further concern or need any other help, please feel free to tell me, thanks!
Best regards, Jeffrey Tan Microsoft Online Community Support ================================================== When responding to posts, please "Reply to Group" via your newsreader so that others may learn and benefit from your issue. ================================================== This posting is provided "AS IS" with no warranties, and confers no rights.
asanford - 12 Jul 2006 15:39 GMT Hi, Jeffrey,
1) I've pasted the partial class code for a form class that, if you put into a windows forms project and build, should demo problem (1). I've also sent you the full project as an email attachment.
2) I haven't seen any attribute for the serializee to control versioning serialization; like I said, it seems you have to change properties on the formatter itself. This is a problem for me because I'm trying to use the caching application block. Can you ask that team how they recommend to versioning of objects cached with that block? I'd rather not change that block, but if I do, perhaps I'll change it to use XmlSerializer (not SoapFormatter), which seems cleaner.
Thanks for your help!
-------------------------------------------------------------- using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms;
using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary;
namespace DemoDictionarySerializeIssue { /* This program demonstrates the following problem: * --------------------------------------------------------- If you serialize non-Dictionary classes, then setting AssemblyFormat to simple seems to cause the OptionalFieldAttribute to not be needed, AND seems to make the deserializer not care if the assembly version changes. However, if I serialize dictionary classes, then the deserializer requires the assembly version to match, even if I set AssemblyFormat to simple. Is there a way to get the rules to apply consistently? * ---------------------------------------------------------
If we serialize dictionaries that have items as entries, then we have a dependency on the assembly version; If we serialize itemholder that has an item member, we don't seem to have a dependency on the assembly version: 1) Run the program, click on serialize, then click on deserialize. See that it works properly 2) close the program, change the AssemblyVersion from 1 to 2 in AssemblyInfo.cs, and rebuild 3) Run the program, click on deserialize - see the File Load exception - it appears that the correct assembly version couldn't be found 4) uncheck "use dictionary", click on serialize, then click on deserialize. See that it works properly. 5) close the program, change the AssemblyVersion from 2 to 3 in AssemblyInfo.cs, and rebuild 6) Run the program, uncheck "use dictionary", then click on deserialize - note that it now works fine */
/// </summary> public partial class Form1 : Form { public Form1() { InitializeComponent(); }
private void m_btnSerialize_Click(object sender, EventArgs e) { MyDictionary dictionary = new MyDictionary(); Item item1 = new Item("entry1","one"); dictionary[item1.m_sName] = item1;
Item item2 = new Item("entry2", "two"); dictionary[item2.m_sName] = item2; try { BinaryFormatter bf = new BinaryFormatter(); bf.AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple; using (FileStream bfStream = new FileStream(m_sFileName, FileMode.Create)) { if (m_cbUseDictionary.Checked) { bf.Serialize(bfStream, dictionary); } else { MyItemHolder ih1 = new MyItemHolder(); ih1.m_item = item1; bf.Serialize(bfStream, ih1); }
} } catch (Exception except) { MessageBox.Show(except.ToString()); } }
private void m_btnDeserialize_Click(object sender, EventArgs e) { try { BinaryFormatter bf = new BinaryFormatter(); bf.AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple; using (FileStream bfStream = new FileStream(m_sFileName, FileMode.Open)) { if (m_cbUseDictionary.Checked) { MyDictionary dictionary = (MyDictionary)bf.Deserialize(bfStream); Item item1 = dictionary["entry1"]; Item item2 = dictionary["entry2"]; } else { MyItemHolder ih1 = (MyItemHolder)bf.Deserialize(bfStream); Item item1 = ih1.m_item; } } } catch (Exception except) { MessageBox.Show(except.ToString()); } }
private const string m_sFileName = "serialized.dat"; }
[Serializable] public class Item { public Item(string sName, string sValue) { m_sName = sName; m_sValue = sValue; }
public Item() { }
public string m_sName; public string m_sValue; }
[Serializable] public class MyDictionary : Dictionary<string, Item> { protected MyDictionary(SerializationInfo info, StreamingContext context) : base(info, context) { }
public MyDictionary() { } }
[Serializable] public class MyItemHolder { public Item m_item; }
}
> Hi Andy, > [quoted text clipped - 147 lines] > ================================================== > This posting is provided "AS IS" with no warranties, and confers no rights. "Jeffrey Tan[MSFT]" - 13 Jul 2006 09:35 GMT Hi Andy,
Thanks for your feedback!
Yes, with your sample project, I can reproduce out this strange behavior.
I have performed some deeper debugging regarding this issue. I found that the FileNotFoundException is generated by Assembly.GetType, which is finally invoked by System.Runtime.Serialization.FormatterServices.GetTypeFromAssembly.
By setting breakpoint at System.Runtime.Serialization.FormatterServices.GetTypeFromAssembly and performing some tracing, I found that this method is invoked several times. Each time for a type, so it will loop through the deserialization object and call FormatterServices.GetTypeFromAssembly for each child type in this object.
Also, the exception is generated when using FormatterServices.GetTypeFromAssembly for a strange type name: "System.Collections.Generic.KeyValuePair`2[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[DemoDictionarySerializeIssue.Item, DemoDictionarySerializeIssue, Version=1.0.0.4, Culture=neutral, PublicKeyToken=3379bf4a1aba6400]]"
Since the currently debugging assembly version is 1.0.0.5, while Assembly.GetType is trying to retrieve Version=1.0.0.4, it will attempt to locate assembly with strong name below: "DemoDictionarySerializeIssue, Version=1.0.0.4, Culture=neutral, PublicKeyToken=3379bf4a1aba6400"
However, this assembly is changed to 1.0.0.5, so the loading fails.
So this issue seems to be with Generic of .Net2.0. I am not sure if this issue is a bug or not. I will try to contact our CLR team regarding it. Thanks.
Additionally, I found that without strong name to sign the assembly, it will eliminate this issue, do you have any special request to strong name the assembly?
Regarding the caching application block issue, I will also try to consult internally and hope there is some workaround for it. I will get back to you ASAP. Thanks.
Best regards, Jeffrey Tan Microsoft Online Community Support ================================================== When responding to posts, please "Reply to Group" via your newsreader so that others may learn and benefit from your issue. ================================================== This posting is provided "AS IS" with no warranties, and confers no rights.
"Jeffrey Tan[MSFT]" - 12 Jul 2006 09:04 GMT Hi Andy,
Have you managed to create a sample project for demonstration? Does my reply make sense to you? If you have anything unclear or any concern, please feel free to let me known, thanks!
Best regards, Jeffrey Tan Microsoft Online Community Support ================================================== When responding to posts, please "Reply to Group" via your newsreader so that others may learn and benefit from your issue. ================================================== This posting is provided "AS IS" with no warranties, and confers no rights.
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|