c# - IExtenderProvider issue with VS2013 WinForms designer -


brief

i'm implementing iextenderprovider provide property winforms control store collection of permissions per control must granted control available.

the problem

everything works great expected, the problem in design time, , when:

  1. i open form designer.
  2. edit permissions (extended property) controls on form.
  3. compile project while form designer open.

then, after compilation, if try edit permissions of controls again, permissions gone, though still present in form's desiger.cs file.

also, if i, in state, perform designer operation causes regeneration of designer file e.g. move button, permissions lost form's designer file.

so, long don't compile while designer open, ok , can edit permissions, save , re-edit again.

to recover state: (that is, permissions loaded again designer.cs file)

  • either close form designer , re-open it.
  • or switch designer.cs file (while designer still open), , edit undo it, return form designer again. gets refreshed , permissions loaded again.

update 1

thanks @hans passant hint, duh, debugging designer without checking clr exceptions, thus, exceptions silently suppressed.

the issue resides in serializer's serialize method (refined bit):

public override object serialize(idesignerserializationmanager manager, object value) {     codedomserializer baseclassserializer = manager.getserializer(typeof(permissionextenderprovider).basetype, typeof(codedomserializer)) codedomserializer;     codestatementcollection statements = baseclassserializer.serialize(manager, value) codestatementcollection;      try     {         permissionextenderprovider provider = (permissionextenderprovider)value;         idesignerhost host = (idesignerhost)manager.getservice(typeof(idesignerhost));         var components = host.container.components.cast<icomponent>().where(x => provider.canextend(x));         this.serializeextender(manager, provider, host, components, statements);     }     catch(exception ex)     {         messagebox.show(ex.tostring());     }      return statements; } 

more specifically, @ line:

permissionextenderprovider provider = (permissionextenderprovider)value; 

the value property of same type, permissionextenderprovider different origin (assembly), throws following exception:

system.invalidcastexception:  [a]vsdesignerextenderproviderissue.permissionextenderprovider cannot cast  [b]vsdesignerextenderproviderissue.permissionextenderprovider. type originates 'vsdesignerextenderproviderissue, version=1.0.0.0, culture=neutral, publickeytoken=null' in context 'loadneither' @ location 'c:\users\administrator\appdata\local\microsoft\visualstudio\12.0\projectassemblies\elo5dzxu01\vsdesignerextenderproviderissue.exe'.  type b originates 'vsdesignerextenderproviderissue, version=1.0.0.0, culture=neutral, publickeytoken=null' in context 'loadneither' @ location 'c:\users\administrator\appdata\local\microsoft\visualstudio\12.0\projectassemblies\5_6og8t_01\vsdesignerextenderproviderissue.exe'. 

since problem occurs after compilation, think makes since now.

i'm not sure designer process, interpretation value passed compiled assembly, try cast same type reside in "active" assembly designer uses, hence casting cannot done since types 2 different assemblies.

why send value different assembly? there anyway work around without manually mapping/serializing objects types of active assembly?


the code

i've tried debugging in design mode (in state after compile when problem occurs), can come permissionextenderprovider.getpermissions(...) method in extender provider returns null, meaning permissionextenderprovider.setpermission(...) has not been yet called, hence permissioncollectioneditor.editvalue(...) in custom uitypeeditor receives null value , passes permissioncollectioneditorform, why permissions not there.

i've provided small sample project demonstrates issue, consists of:

  • permissions (folder contains sample permissions).
  • permissioncollection
  • permissioncollectioneditor (uitypeeditor implementation).
  • permissioncollectioneditorform (the form edits permission collection property).
  • permissionextenderprovider (the iextenderprovider implementation provides permission property).
  • permissionextenderproviderserializer (the serializer serializes extended property designer file.
  • form1 (the main form of project, buttons permissions testing).

you can test extender form1 has buttons, , permissionextenderprovider instance.

download sample project

here key code project:

permissioncollectioneditor

public override object editvalue(itypedescriptorcontext context, iserviceprovider provider, object value)  {     if (context != null && context.instance != null && provider != null)      {         var editorservice = (iwindowsformseditorservice)provider.getservice(typeof(iwindowsformseditorservice));         if (editorservice != null)         {             using (permissioncollectioneditorform colleditorfrm = new permissioncollectioneditorform(provider, value permissioncollection))             {                 if (editorservice.showdialog(colleditorfrm) == dialogresult.ok)                 {                     value = colleditorfrm.collection;                 }             }         }     }      return value; } 

permissionextenderprovider

[provideproperty("permissions", typeof(icomponent))] [designerserializer(typeof(permissionextenderproviderserializer), typeof(codedomserializer))] public class permissionextenderprovider : component, iextenderprovider  {     private hashtable _permissions;     private bool _initialverification = false;     private list<icomponent> _disabledcomponents;      public permissionextenderprovider()         : base()     {         _permissions = new hashtable();         _disabledcomponents = new list<icomponent>();     }      //=====================================     // iextenderprovider     //=====================================     public bool canextend(object extendee)     {         return (extendee control ||             extendee tabpage ||             extendee toolstripitem) && !(extendee permissionextenderprovider);     }      //=====================================     // providedproperties     //=====================================     [category("behavior")]     [editor(typeof(permissioncollectioneditor), typeof(uitypeeditor))]     [designerserializationvisibility(designerserializationvisibility.hidden)]     public permissioncollection getpermissions(icomponent component)     {         permissioncollection result = null;         if (_permissions.containskey(component))             result = (permissioncollection)_permissions[component];          return result;     }      public void setpermissions(icomponent component, permissioncollection value)     {         if (value == null)         {             value = new permissioncollection();         }          if (value.count == 0)         {             _permissions.remove(component);         }         else         {             _permissions[component] = value;         }     }      //=====================================     // permission verification     //=====================================     public bool checkpermissions(icomponent component)     {         permissioncollection permissions = _permissions.containskey(component) ? (permissioncollection)_permissions[component] : null;         if (permissions == null)             return false;          return permissionprovider.checkpermissions(permissions.toarray());     }              public virtual void assertpermissions(icomponent component)     {         bool haspermission = checkpermissions(component);          if(!haspermission)         {             if(component tabpage)             {                 ((tabpage)component).enabled = false;                 ((tabcontrol)((tabpage)component).parent).invalidate();             }             else if(component toolstripitem)             {                 ((toolstripitem)component).enabled = false;             }             else if(component control)             {                 ((control)component).enabled = false;             }         }          // add/remove list of disabled components         if(haspermission && _disabledcomponents.contains(component))         {             _disabledcomponents.remove(component);         }         else if(!haspermission && !_disabledcomponents.contains(component))         {             _disabledcomponents.add(component);         }     }      /// <summary>     /// verify permissions associated control provider. hides/disables controls     /// associated if or of permissions not granted (depending on options specified control)     /// </summary>     public virtual void verifypermissions()     {         _disabledcomponents.clear();          // verify permissions         foreach (icomponent comp in _permissions.keys)         {             assertpermissions(comp);         }     }      //================================================     // wiring host container's load event     //================================================     private containercontrol m_host;      [browsable(false)]     [designerserializationvisibility(designerserializationvisibility.hidden)]     public containercontrol host     {         { return m_host; }         set         {             if (m_host == null && value form != null && !designmode)             {                 (value form).load += initialize;             }             else if (m_host == null && value usercontrol != null && !designmode)             {                 (value usercontrol).load += initialize;             }             m_host = value;         }     }      private void initialize(object sender, eventargs e)     {         if (!designmode)         {             verifypermissions();             _initialverification = true;         }     } } 

permissionextenderproviderserializer

    public class permissionextenderproviderserializer : codedomserializer {     public override object serialize(idesignerserializationmanager manager, object value)     {         permissionextenderprovider provider = value permissionextenderprovider;          codedomserializer baseclassserializer = manager.getserializer(typeof(permissionextenderprovider).basetype, typeof(codedomserializer)) codedomserializer;         codestatementcollection statements = baseclassserializer.serialize(manager, value) codestatementcollection;          idesignerhost host = (idesignerhost)manager.getservice(typeof(idesignerhost));         componentcollection components = host.container.components;          this.serializeextender(manager, provider, host, components, statements);          return statements;     }      private void serializeextender(idesignerserializationmanager manager, permissionextenderprovider provider, idesignerhost host, componentcollection components, codestatementcollection statements)     {         if (components.count > 0)         {             statements.add(new codecommentstatement(" "));             statements.add(new codecommentstatement(manager.getname(provider)));             statements.add(new codecommentstatement(" "));         }          // set host property of provider, wire load event assert permissions         statements.add(new codesnippetstatement(string.format("this.{0}.host = this;", manager.getname(provider))));          // serialize permissions components         foreach (icomponent component in components)         {             if (component permissionextenderprovider                 || component == host.rootcomponent)             {                 continue;             }              propertydescriptor descriptor = typedescriptor.getproperties(component)["name"];             if (descriptor != null)             {                 codemethodinvokeexpression methodcall = new codemethodinvokeexpression(base.serializetoexpression(manager, provider), "setpermissions");                 string extendeename = descriptor.getvalue(component).tostring();                 methodcall.parameters.add(new codefieldreferenceexpression(new codethisreferenceexpression(), extendeename));                  permissioncollection permissions = provider.getpermissions(component);                 if (permissions != null && permissions.count > 0)                 {                     stringbuilder sb = new stringbuilder();                      // create new permissioncollection instance , supply permissions array constructor                     sb.append("new permissioncollection(");                     sb.append("new ");                     sb.append(typeof(ipermission).fullname);                     sb.append("[] {");                      foreach (ipermission perm in permissions)                     {                         propertyinfo operationenumproperty = perm.gettype()                             .getproperty("operation");                          object operationtypeval = operationenumproperty.getvalue(perm, null);                         string operationtypeenumvalname = enum.getname(operationenumproperty.propertytype, operationtypeval);                          sb.appendline();                         sb.append("new ");                         sb.append(perm.gettype().fullname);                         sb.append("(");                         sb.append(perm.gettype().fullname);                         sb.append(".operationtype.");                         sb.append(operationtypeenumvalname);                         sb.append(")");                         sb.append(", ");                     }                      // remove trailing comma last item                     if (permissions.count > 0)                     {                         sb.remove(sb.length - 2, 2);                     }                      sb.append(" })");                      methodcall.parameters.add(new codesnippetexpression(sb.tostring()));                  }                 else                 {                     methodcall.parameters.add(new codeprimitiveexpression(null));                 }                  statements.add(methodcall);             }         }     } } 

background info initial post

i have winforms app, intermediate service layer interact database.

in database, have tables define , map permissions users , user roles. permissions represented in classes in application, , implement ipermission.

in application, have permissionprovider class calls service , checks permissions of user disable/hide controls user not have access (and of course, not rely on security).

what wanted is, extend winforms controls adding permissions property, permission collection, used iextenderprovider.

i've implemented custom uitypeeditor edit property, , custom codedomserializer serialize permission collection in designer file.

i have added property called host in extender provider stores form/user control hosting provider, , it's serialized in designer file custom serializer along permissions.

then in provider, when thehost property set, subscribe load event of host form/user control, , verify permissions of controls when host loaded @ runtime, , put small indicator user knows control has been disabled due insufficient permissions.

i'm running vs2013 community update 4, win 8.1 64-bit, , compiling in x86.