Проблема IExtenderProvider с VS2013 дизайнером WinForms

краткое

Я реализую IExtenderProvider, чтобы предоставить свойство элементу управления winforms для хранения коллекции разрешений для каждого элемента управления, которые должны быть предоставлены для того, чтобы элемент управления был доступен.

Эта проблема

Все работает отлично, как и ожидалось, проблема только во время разработки, и только когда:

  1. Я открываю дизайнер форм.
  2. Изменить права (расширенное свойство) для некоторых элементов управления в форме.
  3. Скомпилируйте мой проект, пока открыт дизайнер форм.

затем, после компиляции, если я снова попытаюсь отредактировать разрешения элементов управления, разрешения исчезнут, хотя они все еще присутствуют в файле 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 (Реализация IExtenderProvider, которая обеспечивает Permission имущество).
  • PermissionExtenderProviderSerializer (Сериализатор, который сериализует расширенное свойство в файл конструктора.
  • Form1 (основная форма проекта, с некоторыми кнопками с разрешениями для тестирования).

Вы можете проверить расширитель с помощью Form1, которая имеет несколько кнопок и PermissionExtenderProvider пример.

Скачать пример проекта

Вот некоторый ключевой код из проекта:

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 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 класс, который вызывает службу и проверяет разрешения пользователя на отключение / скрытие любых элементов управления, к которым у пользователя нет доступа (и, конечно, я не полагаюсь на это в целях безопасности).

Что я хотел сделать, это расширить элементы управления Winforms, добавив Permissions свойство, которое является коллекцией разрешений, поэтому я использовал IExtenderProvider,

Я реализовал свой обычай UITypeEditor отредактировать свойство и пользовательский CodeDomSerializer для правильной сериализации коллекции разрешений в файле конструктора.

Я также добавил свойство под названием Host в моем поставщике расширителей, который хранит форму / пользовательский элемент управления, на котором размещен поставщик, и сериализован в файле конструктора моим настраиваемым сериализатором вместе с разрешениями.

Тогда в моем провайдере, когда Host свойство установлено, я подписываюсь на Load событие формы хоста / пользовательского элемента управления, а также проверьте разрешения элементов управления, когда хост загружается во время выполнения, и установите небольшой индикатор, чтобы пользователь знал, что элемент управления отключен из-за недостаточных разрешений.

Я использую VS2013 Community с обновлением 4, Win 8.1 64-bit и компилирую в x86.

0 ответов

Другие вопросы по тегам