简短的
我正在实现 IExtenderProvider 以向 winforms 控件提供一个属性,以存储每个控件的权限集合,必须授予这些权限才能使控件可用。
问题
一切都按预期工作,问题仅在设计时,并且仅在以下情况下:
- 我打开表单设计器。
- 编辑表单上某些控件的权限(扩展属性)。
- 在表单设计器打开时编译我的项目。
然后,编译后,如果我再次尝试编辑控件的权限,权限就会消失,尽管它们仍然存在于表单的 desiger.cs 文件中。
此外,如果我在这种状态下执行任何导致设计器文件重新生成的设计器操作,例如移动按钮,则表单的设计器文件中的权限将丢失。
所以,只要我在设计器打开时不编译,一切都很好,我可以编辑权限,保存并再次重新编辑。
从此状态恢复:( 即从designer.cs文件再次加载权限)
- 关闭表单设计器并重新打开它。
- 或切换到designer.cs 文件(当设计器仍然打开时),编辑任何内容然后撤消它,然后再次返回到表单设计器。它被刷新并再次加载权限。
更新 1
感谢@Hans Passant 的提示,嗯,我正在调试设计器而不检查 CLR 异常,因此,异常被静默抑制。
问题在于序列化程序的 Serialize 方法(稍微改进):
public override object Serialize(IDesignerSerializationManager manager, object value)
{
CodeDomSerializer baseClassSerializer = manager.GetSerializer(typeof(PermissionExtenderProvider).BaseType, typeof(CodeDomSerializer)) as CodeDomSerializer;
CodeStatementCollection statements = baseClassSerializer.Serialize(manager, value) as 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;
}
更具体地说,在这一行:
PermissionExtenderProvider provider = (PermissionExtenderProvider)value;
该value
属性属于同一类型,PermissionExtenderProvider
但来自不同的来源(程序集),因此引发以下异常:
System.InvalidCastException:
[A]VSDesignerExtenderProviderIssue.PermissionExtenderProvider cannot be cast to
[B]VSDesignerExtenderProviderIssue.PermissionExtenderProvider.
Type A originates from 'VSDesignerExtenderProviderIssue, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'LoadNeither' at location 'C:\Users\Administrator\AppData\Local\Microsoft\VisualStudio\12.0\ProjectAssemblies\elo5dzxu01\VSDesignerExtenderProviderIssue.exe'.
Type B originates from 'VSDesignerExtenderProviderIssue, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'LoadNeither' at location 'C:\Users\Administrator\AppData\Local\Microsoft\VisualStudio\12.0\ProjectAssemblies\5_6og8t_01\VSDesignerExtenderProviderIssue.exe'.
由于问题仅在编译后出现,我认为它是从现在开始的。
我不太确定设计器的过程,但我的解释是该值是从先前编译的程序集中传递的,然后我尝试转换为与设计器使用的“活动”程序集中相同的类型,因此转换不能完成,因为类型来自两个不同的程序集。
为什么它从不同的程序集发送值?有没有办法在不手动将对象映射/序列化到活动程序集的类型的情况下解决这个问题?
编码
我已经尝试在设计模式下调试它(在编译后的状态下,即问题发生时),但我能想到的只是PermissionExtenderProvider.GetPermissions(...)
我的扩展器提供程序中的方法返回null
,这意味着PermissionExtenderProvider.SetPermission(...)
尚未调用,因此我的PermissionCollectionEditor.EditValue(...)
自定义 UITypeEditor 中的 接收空值并将其传递给PermissionCollectionEditorForm
,这就是权限不存在的原因。
我提供了一个演示该问题的小型示例项目,它包括:
Permissions
(包含示例权限的文件夹)。PermissionCollection
PermissionCollectionEditor
(UITypeEditor 实现)。PermissionCollectionEditorForm
(编辑权限集合属性的表单)。PermissionExtenderProvider
Permission
(提供属性的 IExtenderProvider 实现)。PermissionExtenderProviderSerializer
(将扩展属性序列化为设计器文件的序列化程序。Form1
(项目的主要形式,带有一些具有测试权限的按钮)。
您可以使用具有一些按钮和一个PermissionExtenderProvider
实例的 Form1 来测试扩展器。
以下是该项目的一些关键代码:
权限集合编辑器
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 as 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 is Control ||
extendee is TabPage ||
extendee is ToolStripItem) && !(extendee is 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 is TabPage)
{
((TabPage)component).Enabled = false;
((TabControl)((TabPage)component).Parent).Invalidate();
}
else if(component is ToolStripItem)
{
((ToolStripItem)component).Enabled = false;
}
else if(component is Control)
{
((Control)component).Enabled = false;
}
}
// Add/Remove to the list of disabled components
if(hasPermission && _DisabledComponents.Contains(component))
{
_DisabledComponents.Remove(component);
}
else if(!hasPermission && !_DisabledComponents.Contains(component))
{
_DisabledComponents.Add(component);
}
}
/// <summary>
/// Verify the permissions associated with the control provider. It hides/disables the controls
/// associated if the all or any of the permissions are not granted (depending on options specified for the control)
/// </summary>
public virtual void VerifyPermissions()
{
_DisabledComponents.Clear();
// Verify the permissions
foreach (IComponent comp in _Permissions.Keys)
{
AssertPermissions(comp);
}
}
//================================================
// Wiring the host container's load event
//================================================
private ContainerControl m_Host;
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public ContainerControl Host
{
get { return m_Host; }
set
{
if (m_Host == null && value as Form != null && !DesignMode)
{
(value as Form).Load += Initialize;
}
else if (m_Host == null && value as UserControl != null && !DesignMode)
{
(value as 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 as PermissionExtenderProvider;
CodeDomSerializer baseClassSerializer = manager.GetSerializer(typeof(PermissionExtenderProvider).BaseType, typeof(CodeDomSerializer)) as CodeDomSerializer;
CodeStatementCollection statements = baseClassSerializer.Serialize(manager, value) as 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 the Host property of the provider, this is where we wire the Load event to assert permissions
statements.Add(new CodeSnippetStatement(string.Format("this.{0}.Host = this;", manager.GetName(provider))));
// Serialize permissions for components
foreach (IComponent component in components)
{
if (component is 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 and supply permissions array to 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 from 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);
}
}
}
}
初始帖子的背景信息
我有一个 winforms 应用程序,具有与数据库交互的中间服务层。
在数据库中,我有定义和映射权限到用户和用户角色的表。这些权限在我的应用程序的类中表示,并实现IPermission
.
在我的应用程序中,我有一个PermissionProvider
类调用服务并检查用户的权限以禁用/隐藏用户不必访问的任何控件(当然,我不依赖它来确保安全)。
我想做的是,通过添加Permissions
属性来扩展 Winforms 控件,这是一个权限集合,所以我使用了IExtenderProvider
.
我已经实现了我的自定义UITypeEditor
来编辑属性,并自定义CodeDomSerializer以在设计器文件中正确序列化权限集合。
我还在Host
扩展器提供程序中添加了一个名为的属性,该属性存储托管提供程序的表单/用户控件,并且它由我的自定义序列化程序与权限一起在设计器文件中序列化。
然后在我的提供程序中,当设置Host
属性时,我订阅Load
主机表单/用户控件的事件,并在运行时加载主机时验证控件的权限,并放置一个小指示器,以便用户知道由于权限不足,控制已被禁用。
我正在运行带有更新 4、Win 8.1 64 位并在 x86 中编译的 VS2013 社区。