Problem Deserializing Classes From Assemblies Loaded at Runtime Implicitly – TFS Build Custom UI Type Editor

1 במרץ 2011

Lately I’ve created a Custom UI Type Editor for the “Process” Tab in the TFS Build Definition Interface, and came across a problem.
Since my object was a bit complex combined with the reason shown here, I decided to use serialization to “Deep Copy” the object.

For an example I will use the Credential editor, made originally by “Ewald Hofman”. This is the modified code:

Credential objet:

   1: [Serializable]

   2: public class Credential

   3: {

   4:     public string Domain { get; set; }

   5:     public string UserName { get; set; }

   6:     public string Password { get; set; }

   7:  

   8:     public override string ToString()

   9:     {

  10:         if (string.IsNullOrEmpty(UserName))

  11:         {

  12:             return "No Credential";

  13:         }

  14:         else

  15:         {

  16:             return Domain + @"\" + UserName;

  17:         }

  18:     }

  19:  

  20:     public Credential DeepClone()

  21:     {

  22:         using (var ms = new MemoryStream())

  23:         {

  24:             var formatter = new BinaryFormatter();

  25:             formatter.Serialize(ms, this);

  26:             ms.Position = 0;

  27:  

  28:             formatter.AssemblyFormat = FormatterAssemblyStyle.Simple;

  29:  

  30:             return (Credential)formatter.Deserialize(ms);

  31:         }

  32:     }

  33: }

Credential editor:

   1: public class CredentialEditor : UITypeEditor

   2: {

   3:     

   4:     public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)

   5:     {

   6:         if (provider != null)

   7:         {

   8:             IWindowsFormsEditorService editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));

   9:  

  10:             if (editorService != null)

  11:             {

  12:                 Credential credential = value as Credential;

  13:                 var editableCredential = credential.DeepClone();

  14:                 using (CredentialDialog dialog = new CredentialDialog(editableCredential))

  15:                 {

  16:                     dialog.Domain = credential.Domain;

  17:                     dialog.UserName = credential.UserName;

  18:                     dialog.Password = credential.Password;

  19:  

  20:                     if (editorService.ShowDialog(dialog) == DialogResult.OK)

  21:                     {

  22:                         return editableCredential;

  23:                     }

  24:                 }

  25:             }

  26:  

  27:         }

  28:  

  29:         return value;

  30:         

  31:     }

  32:  

  33:     public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)

  34:     {

  35:         return UITypeEditorEditStyle.Modal;

  36:     }

  37: }

Happily I ran it for the first time and it (of course) crashed immediately with the following exception:

{"Unable to find assembly 'BuildTasks, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'."}

image

It terns out that due to the fact that for the deserialization  process your objects’ assembly will be loaded at runtime automatically but because it is not strongly typed and in the GAC, it is unable to relate between your objet and your objects’ assembly.

Luckily this is very easy to solve!

1) Add a Serialization Binder class.

   1: public class Binder : SerializationBinder

   2: {

   3:     public override Type BindToType(string assemblyName, string typeName)

   4:     {

   5:         return Type.GetType(String.Format("{0}, {1}", typeName, assemblyName));

   6:     }

   7: }

2) Attach the new Serialization Binder class to the BinaryFormatter instance in the DeepCopy method.

   1: public Credential DeepClone()

   2: {

   3:     using (var ms = new MemoryStream())

   4:     {

   5:         var formatter = new BinaryFormatter();

   6:         formatter.Serialize(ms, this);

   7:         ms.Position = 0;

   8:  

   9:         formatter.AssemblyFormat = FormatterAssemblyStyle.Simple;

  10:         formatter.Binder = new Binder();

  11:         formatter.Binder.BindToType(this.GetType().Assembly.FullName, this.GetType().Name);

  12:  

  13:         return (Credential)formatter.Deserialize(ms);

  14:     }

  15: }

3) Run 🙂

This will bind your class with it’s misguided runtime loaded assembly!

הוסף תגובה
facebook linkedin twitter email

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *

תגובה אחת

  1. Padda27 באפריל 2011 ב 16:21

    Hi,

    I'm having a problem with the example above and with Ewald's example. I've added the credentials custom type. When I enter detail when in the Edit Build Definition screen, it's fine. When queuing a new build, I enter different information in the credentials dialog, but they get ignored and the ones entered in the Edit Build Definition are kept. Are my changes being ignored, not saved, or overridden by the default settings from the Edit Build Definition? If so, how can I over come this as it's proving to be difficult to fix.

    Thanks

    הגב