Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsFree MagazinesWhite PapersSubmit Content
Discussion GroupsASP.NETWindows FormsLanguages.NET FrameworkVisual Studio.NET
Articles.NET FrameworkASP.NETToolsWindows Forms
.NET DirectoryOpen Source ProjectsUser GroupsWeb Resources
Related Topics
Visual Basic 6SQL ServerMS AccessOther DB ProductsMS Server ProductsMore Topics ...

.NET Forum / .NET Framework / CLR / July 2006

Tip: Looking for answers? Try searching our database.

OptionalFieldAttribute not needed with BinaryFormatter in .net v2.

Thread view: 
Enable EMail Alerts  Start New Thread
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.

Rate this thread:







Free Magazines

Get 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 ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.