译文控制系统权限WPF(控件属性方法添加代码)「wps控件属性翻译」

与 MVC 或 Web 窗体不同,使用 WPF 时,您不会获得预构建的安全系统。
因此,您需要提出自己的方法来保护 WPF 屏幕上的控件,如图 1 所示。
有几种不同的方法可以实现此目标。
例如,可以在 View 模型中创建不同的属性,以根据用户的角色使控件不可见或禁用。
此方法的问题在于,如果需要保护更多控件,则需要更改代码,然后将 WPF 应用程序重新分发给用户。
在本文中,我将采用数据驱动的安全方法,以便您可以在数据库表中进行更改并更新 WPF 应用程序的安全,而无需更改代码。
构建员工项目为了充分利用本文,我建议您在阅读时构建示例。
使用 Visual Studio 创建一个名为 的新 WPF 应用程序。
添加一个新文件夹。
右键单击此文件夹并添加新的用户控件。
您将在此控件中创建如图 1 所示的屏幕。
WPFSecuritySampleUserControlsEmployeeControl图1:员工信息屏幕员工用户控制通过键入清单 1 中所示的代码来创建员工用户控件。
在此屏幕中,你将属性添加到某些控件,并将属性添加到其他控件。
这是为了说明可以使用这些属性中的任何一个来查找要保护的控件。
在本文的后面部分,你将使用 {绑定路径} 表达式来查找要保护的控件。
NameTag清单 1:员工的 XAML 屏幕<UserControl x:Class="WPFSecuritySample.EmployeeControl" ... // NAMESPACES HERE > <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="" /> </Grid.ColumnDefinitions> <Button Grid.Row="0" Grid.Column="1" HorizontalAlignment="Left" Content="New" Name="NewButton" /> <Label Grid.Row="1" Grid.Column="0" Content="Employee ID" /> <TextBox Grid.Row="1" Grid.Column="1" Name="EmployeeID" Text="{Binding Path=EmployeeID}" /> <Label Grid.Row="2" Grid.Column="0" Content="First Name" /> <TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Path=FirstName}" /> <Label Grid.Row="3" Grid.Column="0" Content="Last Name" /> <TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Path=LastName}" /> <Label Grid.Row="4" Grid.Column="0" Content="Salary" /> <TextBox Grid.Row="4" Grid.Column="1" Tag="Salary" Text="{Binding Path=Salary}" /> <Label Grid.Row="5" Grid.Column="0" Content="SSN" /> <TextBox Grid.Row="5" Grid.Column="1" Tag="SSN" Text="{Binding Path=SSN}" /> <Button Grid.Row="6" Grid.Column="1" HorizontalAlignment="Left" Content="Save" Name="SaveButton" /> </Grid></UserControl>安全视图模型基类创建 WPF 应用程序时,应始终使用模型-视图-视图模型 (MVVM) 设计模式。
您将在下一节中创建一个类,但首先,创建一个名为的基类,您可以在其中编写代码来保护控件。
该类继承自该类。
EmployeeViewModelSecurityViewModelBaseEmployeeViewModelSecurityViewModelBase将一个新文件夹添加到名为 的项目。
右键单击此文件夹并添加一个名为 的新类。
将清单 2 中所示的代码添加到这个新文件中。
此代码只是本文稍后将要编写的存根方法,但您现在需要创建它们,以便可以生成员工视图模型。
表 2 描述了清单 1 中属性和方法的简短描述。
SecurityClassesSecurityViewModelBase清单 2:为所有需要安全性继承的视图模型创建一个基类using System.Collections.Generic;using System.Security.Principal;using System.Threading;using System.Windows;using System.Windows.Controls;public class SecurityViewModelBase{ public SecurityViewModelBase() { ControlsToSecure = new List<SecurityControl>(); ControlsInContainer = new List<XAMLControlInfo>(); } public List<SecurityControl> ControlsToSecure { get; set; } public List<XAMLControlInfo> ControlsInContainer { get; set; } public IPrincipal CurrentPrincipal { get; set; } public virtual void SecureControls(object element, string containerName) { } protected virtual void LoadControlsToSecure(string containerName) { } protected virtual void LoadControlsInXAMLContainer(object element) { }}员工视图模型在项目中创建一个名为 的新文件夹。
右键单击该文件夹并添加一个名为 的新类。
将清单 3 中所示的代码添加到这个新文件中。
该类继承自该类,因此它可以利用安全方法。
ViewModelClassesEmployeeViewModelEmployeeViewModelSecurityViewModelBase清单 3:使所有视图模型都继承自 SecurityViewModelBase 类using System.Collections.Generic;public class EmployeeViewModel : SecurityViewModelBase{ public EmployeeViewModel() : base() { EmployeeID = 1; FirstName = "Bruce"; LastName = "Jones"; Salary = 75000; SSN = "555-55-5555"; } public int EmployeeID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public decimal Salary { get; set; } public string SSN { get; set; } protected override void LoadControlsToSecure(string containerName) { }}在类中,添加要绑定到员工屏幕的各个属性。
重写该方法,以便可以选择从何处检索要保护的控件。
例如,您可以对控件进行硬编码,或者从 XML 文件或数据库表中检索它们。
EmployeeViewModelLoadControlsToSecure()主窗口创建视图模型类后,请将用户控件拖到 .请务必重置所有布局属性,以便用户控件占据窗口的整个宽度。
您在此窗口中应如下所示:EmployeeControlMainWindow<Grid> <Grid> <UserControls:EmployeeControl /></Grid>将 XML 命名空间添加到控件,以便可以在窗口控件的资源部分中创建类的实例。
<Window>EmployeeViewModel xmlns:vm="clr-namespace:WPFSecuritySample.ViewModels"添加一个部分,您可以在其中为员工视图模型创建定义,如下面的代码片段所示。
<Window.Resources> <Window.Resources> <vm:EmployeeViewModel x:Key="viewModel" /></Window.Resources>将 的 DataContext 属性分配给此视图模型类的实例,由键“viewModel”标识。
<Grid> <Grid DataContext="{StaticResource viewModel}"> <UserControls:EmployeeControl /></Grid>主窗口代码隐藏转到类的代码隐藏,并添加一个名为 的私有变量。
使用以下代码将此变量连接到 XAML 创建的视图模型的实例。
MainWindow_viewModel public partial class MainWindow : Window{ public MainWindow() { InitializeComponent(); _viewModel = (EmployeeViewModel) this.Resources["viewModel"]; } private readonly EmployeeViewModel _viewModel;}设置安全主体对象为了保护每个窗口上的控件,需要向窗口添加一个事件。
从此事件中,在视图模型类上调用该方法。
打开文件并在 u 中添加以下键/值对以创建事件。
LoadedSecureControls()MainWindow.xaml<Window>Loaded Loaded="Window_Loaded"清单 4 显示了事件过程和方法。
该方法是需要创建或获取对象并更改当前执行线程上的主体策略的位置。
您可以获取当前对象(如此处所示),也可以通过提示用户输入用户名和密码来创建自己的对象。
Window_Loaded()SetSecurityPrincipal()SetSecurityPrincipal()IPrincipalWindowsPrincipalGenericPrincipal清单 4:在尝试保护控件之前在线程上设置安全主体private void Window_Loaded(object sender, RoutedEventArgs e){ // Set your Security Principal SetSecurityPrincipal(); // Secure controls on this WPF window _viewModel.SecureControls(this, "EmployeeControl");}private void SetSecurityPrincipal(){ // Set Principal to a WindowsPrincipal Thread.GetDomain().SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal); // NOTE: You can create a GenericPrincipal here with your own credentials and roles}从事件中调用该方法。
调用此方法后,在视图模型上调用该方法,以字符串形式传入此窗口的实例和用户控件的名称。
SetSecurityPrincipal()Window_Loaded()SecureControls()试一试运行该应用程序,您应该会看到如图 1 所示的屏幕。
现在,你已准备好开始生成保护屏幕上各种控件的代码部分。
安全系统的目标在编写代码以实现安全系统之前,让我们确定安全系统中所需的功能。
最基本的功能是能够根据角色使控件出现或消失。
当然,WPF 中的可见性可能意味着隐藏或折叠,因此您很可能希望能够指定其中之一。
根据角色禁用控件的功能应该是安全系统中的另一个功能。
最后,您可能还希望使输入控件(如文本框)对某些角色变为只读。
准备屏幕以确保安全您不希望在每个单独的屏幕上或每个视图模型类中对安全性进行硬编码。
相反,数据驱动方法是更好的选择。
实现此目的的方法是将对 XAML 元素(如窗口、用户控件、网格或其他容器)的引用传递给该方法。
将读入此容器中包含的所有控件,并找到每个控件的唯一标识符。
此唯一标识符可以是 Name 或 Tag 属性,也可以是表达式中的属性名称。
下面显示的 XAML 突出显示了要在员工屏幕上保护的按钮和文本框。
要保护的每个控件都必须具有唯一的名称、标记或 {Binding} 表达式。
SecureControls(){Binding Path="propertyName"} <Button Content="New" Name="NewButton" /><TextBox Name="EmployeeID" Text="{Binding Path=EmployeeID}" /><TextBox Tag="Salary" Text="{Binding Path=Salary}" /><TextBox Tag="SSN" Text="{Binding Path=SSN}" /><Button Content="Save" Name="SaveButton" />安全对象的集合表 2 显示了存储在对象集合中的一组安全数据示例。
此集合保护图 1 中所示的控件。
对于每个控件,指定希望该控件采用的内容,例如只读、折叠、隐藏或禁用。
属性包含角色的字符串数组。
如果用户不属于这些角色之一,则属性中的值用于更改控件的状态。
例如,如果用户不是“用户”或“主管”角色,则标识为 NewButton 的控件的可见性属性将设置为“折叠”。
SecurityControlSecurityControlModeMode对于图 1 中所示的员工屏幕,您不希望允许系统的普通用户能够保存对员工数据所做的任何更改。
因此,隐藏“新建”按钮并禁用屏幕上的“保存”按钮。
您也不希望普通用户看到其他员工的薪水,因此请使薪水文本框不可见。
应用程序中的管理员有权保存员工数据并查看工资数据。
若要将此数据集与 WPF 窗口或用户控件上的元素匹配,该属性可以是要保护的控件的 or 属性,也可以是 {Binding} 表达式属性中的值。
属性(在本文后面使用)是要保护的 XAML 容器的名称。
ElementIdentifierNameTagPath为简单起见,您将创建一个具有元素标识符的硬编码对象集合,以匹配项目中用户控件中的控件,如图 2 所示。
现在,您还将只使用 or 属性来标识每个控件。
SecurityControlEmployeeControlNameTag图2:将安全控件映射到 XAML 中的控件WPF 安全类您已经为该类创建了存根,以及从该类继承的类。
您需要创建两个额外的类(图 3),以便能够保护任何 WPF 屏幕上的控件。
第一个类已命名,用于保存有关要保护的 WPF 容器中的控件的信息。
第二个控件是类,它是保存表 2 中所述信息的类。
SecurityViewModelBaseEmployeeViewModelXAMLControlInfoSecurityControl这两个类都作为类型属性添加到类中。
方法和负责加载这些泛型集合类中的每一个。
List<T>SecurityViewModelLoadControlsToSecure()LoadControlsInXAMLContainer()图3:用于保护员工屏幕上控件的类安全控制类该类(清单 5)保存数据以保护窗口、用户控件或其他 WPF 容器控件上的单个控件。
表 2 中显示的每行数据都是通过将安全数据放入类的新实例来创建的。
该属性是一个字符串数组,但如果需要,还有一个属性用于将该数组表示为逗号分隔的列表。
SecurityControlSecurityControlRolesRolesAsStringRoles清单 5:SecurityControl 类保存有关单个控件的安全信息public class SecurityControl{ public string ContainerName { get; set; } public string ElementIdentifier { get; set; } public string Mode { get; set; } public string[] Roles { get; set; } private string _RolesAsString = string.Empty; public string RolesAsString { get { return _RolesAsString; } set { _RolesAsString = value; Roles = _RolesAsString.Split(','); } }}XAMLControlInfo Class获得要保护的控件列表后,需要收集要保护的 WPF 元素上的控件列表。
WPF 元素可以是窗口、用户控件或 XAML 容器控件,如网格或堆栈面板。
该类(清单 6)是保存有关 WPF 元素中每个控件的信息的类。
XAMLControlInfo清单 6:XAMLControlInfo 类保存有关 WPF 容器中控件的信息public class XAMLControlInfo{ public object TheControl { get; set; } public string ControlName { get; set; } public string Tag { get; set; } public string ControlType { get; set; } public bool HasIsReadOnlyProperty { get; set; } public bool ConsiderForSecurity() { return !string.IsNullOrEmpty(ControlName) || !string.IsNullOrEmpty(Tag); }}该类包含对控件本身的引用 ()、控件的名称 ()、属性中的值、控件的类型 () 和布尔标志,用于标识控件是否具有属性 ()。
XAMLControlInfoTheControlControlNameTagControlTypeIsReadOnlyHasIsReadOnlyProperty此类中还包含一个名为 .如果 或 属性包含值,则此方法返回 True 值。
对于要保护的控件,这些属性中的一个或另一个必须包含一个值,否则没有任何内容与集合中的值匹配。
ConsiderForSecurity()ControlNameTagControlsToSecure安全视图模型基类之前,您创建了类的存根。
现在是时候为您存根的各种方法编写代码了。
在编写这些方法之前,请查看图 4,了解从 调用的每个方法的概述。
SecurityViewModelBaseSecureControls()图4:安全方法概述该方法是从要保护的 WPF 元素/容器的代码隐藏中调用的。
或者,如果您使用的是命令,则从视图模型中的命令。
该方法调用 和 分别填充两个属性和 。
重写该方法,以便可以决定从哪个位置加载控件以保护。
例如,您可以对控件进行硬编码,从 XML 文件中检索它们,或从数据库表中加载它们。
该方法循环遍历传入的 XAML 元素中的所有控件,并加载集合。
SecureControls()SecureControls()LoadControlsToSecure()LoadControlsInXAMLContainer()ControlsToSecureControlsInContainerLoadControlsToSecure()LoadControlsInXAMLContainer()ControlsInContainer加载两个集合后,循环访问集合并尝试在集合中查找控件。
如果找到匹配项,请检查用户是否属于安全控件中标识的角色之一。
如果它们不是角色的一部分,则 WPF 控件的状态将修改为只读、禁用或设置为不可见。
ControlsToSecureControlsInContainerLoadControlsToSecure Method该方法构建表 2 中所示的数据。
此代码编写在类中,因为它将根据您希望存储安全控制信息的位置而更改。
对于第一个示例,数据将在方法中进行硬编码,如清单 7 所示。
LoadControlsToSecure()EmployeeViewModelLoadControlsToSecure()清单 7:LoadControlsToSecure() 方法的硬编码版本protected override void LoadControlsToSecure(string containerName){ base.LoadControlsToSecure(containerName); ControlsToSecure = new List<SecurityControl> { new SecurityControl { ContainerName = "EmployeeControl", ElementIdentifier = "NewButton", Mode = "collapsed", RolesAsString = "Users123,Supervisor" }, new SecurityControl { ContainerName = "EmployeeControl", ElementIdentifier = "EmployeeID", Mode = "readonly", RolesAsString = "Admin,Supervisor" }, new SecurityControl { ContainerName = "EmployeeControl", ElementIdentifier = "Salary", Mode = "hidden", RolesAsString = "Admin" }, new SecurityControl { ContainerName = "EmployeeControl", ElementIdentifier = "SSN", Mode = "disabled", RolesAsString = "Supervisor" }, new SecurityControl { ContainerName = "EmployeeControl", ElementIdentifier = "SaveButton", Mode = "disabled", RolesAsString = "Admin,Supervisor" } };}LoadControlsInXAMLContainer Method在清单 4 中,您将对当前窗口的引用传递给了该方法,如下面代码段中的第一个参数所示。
SecureControls() private void Window_Loaded(object sender, RoutedEventArgs e){ // Set your Security Principal SetSecurityPrincipal(); // Secure controls on this WPF window _viewModel.SecureControls(this, "EmployeeControl");}此引用被传递到方法中,如清单 8 所示,作为参数。
如果引用是 DependencyObject,则您知道您有一个可以保护的控件。
生成类的新实例并填写 和 属性。
尝试将控件强制转换为 FrameworkElement 对象。
如果强制转换成功,请提取 and 属性。
如果填充了这些属性中的任何一个,该方法将返回 True 值。
LoadControlsInXAMLContainer()elementXAMLControlInfoTheControlControlTypeNameTagConsiderForSecurity()清单 8:LoadControlsInXAMLContainer() 方法protected virtual void LoadControlsInXAMLContainer(object element){ XAMLControlInfo ctl; FrameworkElement fe; if (element is DependencyObject dep) { ctl = new XAMLControlInfo { TheControl = element, ControlType = element.GetType().Name }; // Cast to 'FrameworkElement' so we can get the Name and Tag properties fe = element as FrameworkElement; if (fe != null) { ctl.ControlName = fe.Name; if (fe.Tag != null) { ctl.Tag = fe.Tag.ToString(); } } if (ctl.ConsiderForSecurity()) { // Is there a ReadOnly property? ctl.HasIsReadOnlyProperty = element.GetType().GetProperty("IsReadOnly") == null ? false : true; // Make sure there is not a null in ControlName or Tag ctl.ControlName = ctl.ControlName ?? string.Empty; ctl.Tag = ctl.Tag ?? string.Empty; // Add control to be considered for security ControlsInContainer.Add(ctl); } // Look for Child objects foreach (object child in LogicalTreeHelper.GetChildren(dep)) { // Make recursive call LoadControlsInXAMLContainer(child); } }}如果要将此控件添加到集合中,则首先确定该控件是否具有属性。
接下来,确保 and 属性不包含 null,而是具有空字符串。
这只是避免了稍后在代码中进行一些额外的检查。
然后将此控件添加到属性中。
任何没有 or 的控件都不会添加到集合中,因为无法将其与要保护的控件之一匹配。
IsReadOnlyNameTagControlsInContainerNameTag每个控件可以包含其他控件,因此循环遍历任何子控件,并对方法进行递归调用。
这样,您就可以在对传递给该方法的控件的引用中遍历整个控件树。
LoadControlsInXAMLContainer()SecureControls()安全控制方法清单 9 中的方法是从 WPF 窗口或用户控件的代码隐藏中调用的(也可以挂接到命令)。
调用 和 后,代码现在循环遍历集合,对于每个控件,它会在 中搜索 作为 或 中的 。
如果找到该控件,它将遍历所有角色,以查看用户是否属于其中一个角色。
如果找到其中一个角色,则会跳过该控件。
如果未找到任何角色,则根据属性使控件不可见、只读或禁用。
SecureControls()LoadControlsToSecure()LoadControlsInXAMLContainer()ControlsToSecureElementIdentifierControlNameTagControlsInContainerMode清单 9:SecureControls() 方法public virtual void SecureControls(object element, string containerName){ XAMLControlInfo ctl = null; CurrentPrincipal = Thread.CurrentPrincipal; // Get Controls to Secure from Data Store LoadControlsToSecure(containerName); // Build List of Controls to be Secured LoadControlsInXAMLContainer(element); // Loop through controls foreach (SecurityControl secCtl in ControlsToSecure) { secCtl.ElementIdentifier = secCtl.ElementIdentifier.ToLower(); // Search for Name property ctl = ControlsInContainer.Find(c => c.ControlName.ToLower() == secCtl.ElementIdentifier); if (ctl == null) { // Search for Tag property ctl = ControlsInContainer.Find(c => c.Tag.ToLower() == secCtl.ElementIdentifier); } if (ctl != null && !string.IsNullOrWhiteSpace(secCtl.Mode)) { // Loop through roles and see if user is NOT in one of the roles // If not, change the state of the control foreach (string role in secCtl.Roles) { if (CurrentPrincipal.IsInRole(role)) { // They are in a role, so break out of loop break; } else { // They are NOT in a role so change the control state ChangeState(ctl, secCtl.Mode); // Break out of loop because we have already modified the control state break; } } } }}ChangeState MethodIf the user isn't in one of the roles for the current control, pass in the reference to the object and the value in the property to the method shown in Listing 10. Extract the reference to the control from property. The switch statement decides what to do to the control to secure based on the mode parameter passed in. If the value is “disabled”, for example, then the visibility of the control is turned back on and the property is set to . If the value is “readonly”, and the control has an property, that property is set to True, otherwise the IsEnabled property is set to True.XAMLControlInfoModeChangeState()TheControlIsEnabledFalseIsReadOnlyListing 10: The ChangeState() methodprotected virtual void ChangeState(XAMLControlInfo control, string mode){ Control ctl = (Control)control.TheControl; switch (mode.ToLower()) { case "disabled": ctl.Visibility = Visibility.Visible; ctl.IsEnabled = false; break; case "readonly": case "read only": case "read-only": ctl.Visibility = Visibility.Visible; ctl.IsEnabled = true; if (control.HasIsReadOnlyProperty) { // Turn on IsReadOnly property ctl.GetType().GetProperty("IsReadOnly").SetValue(control.TheControl, true, null); } else { ctl.IsEnabled = false; } break; case "collapsed": case "collapse": ctl.Visibility = Visibility.Collapsed; break; case "hidden": case "invisible": ctl.Visibility = Visibility.Hidden; break; }}Try It OutRun the application and you should see a screen that looks like Figure 5.Figure 5: Employee information screen after applying securityIn the method in Listing 7, you have a value of “Users123”. Replace that with “Users”. If you're a member of the “Users” group, the New button shows up when you rerun the WPF application.LoadControlsToSecure()Secure Controls Using Database TableInstead of hard coding the controls to secure as you did in Listing 7, create a database table where you can store the controls to secure in your WPF application.SecurityControl Table下面是用于在 SQL Server 中创建表的 T-SQL 脚本。
添加可以是主键值的列。
使此列成为使用 IDENTITY 属性自动递增的整数数据类型。
打开 SQL Server 的实例并运行此脚本以创建此表。
SecurityControlSecurityControlId CREATE TABLE SecurityControl ( SecurityControlId int IDENTITY(1,1) NOT NULL PRIMARY KEY NONCLUSTERED, ContainerName nvarchar(100) NOT NULL, ElementIdentifier nvarchar(100) NOT NULL, Mode nvarchar(20) NOT NULL, RolesAsString nvarchar(1024) NOT NULL)创建表后,将图 6 中所示的数据输入到此表中。
SecurityControl图6:对每个窗口或用户控件使用不同的容器名称以确保安全。
WpfSecurityDbContext Class使用 NuGet 包管理器将实体框架添加到项目中。
打开该文件并添加连接字符串,如以下代码片段所示。
根据您的环境修改连接字符串中的 and 属性。
App.configServerDatabase <connectionStrings> <add name="Sandbox" connectionString="Server=Localhost; Database=Sandbox; Trusted_Connection=Yes;" providerName="System.Data.SqlClient" /></connectionStrings>向项目中添加一个名为 的新类,如清单 11 所示。
修改构造函数以使用在文件中添加的连接字符串的名称。
WpfSecurityDbContextmApp.config清单 11:EF 用于从数据库表中获取安全数据的 WpfSecurityDbContext 类public class WpfSecurityDbContext : DbContext{ public WpfSecurityDbContext() : base("name=Sandbox") { } public virtual DbSet<SecurityControl> ControlsToSecure { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { // Do NOT let EF create migrations or check database for model consistency Database.SetInitializer<WpfSecurityDbContext>(null); base.OnModelCreating(modelBuilder); }}修改安全控制类使用实体框架 (EF) 时,需要使用 [Table] 属性修饰映射到表的类。
打开该文件并添加一个 using 语句以引用找到此属性的命名空间。
SecurityControl.cs using System.ComponentModel.DataAnnotations.Schema;在类定义中添加 [Table] 属性,如下面的代码所示。
还需要添加新属性 ,以映射到在表中添加的主键。
SecurityControlIdSecurityControl [Table("SecurityControl")]public class SecurityControl { public int SecurityControlId { get; set; } // REST OF THE PROPERTIES ARE HERE}安全控制管理器类无需编写 EF 代码来访问视图模型类中的表,而是添加一个名为项目的新类。
在此类中,创建一个名为 的方法,如清单 12 所示,您将容器名称传递给该方法。
如果 WPF 应用程序中有两个或多个要保护的窗口或用户控件对象,则使用容器名称。
向表中的每个控件添加唯一的容器名称,如图 6 所示。
SecurityControlSecurityControlManagerGetSecurityControls()SecurityControl清单 12:SecurityControlManager 类从表中获取要保护的控件public class SecurityControlManager{ public List<SecurityControl> GetSecurityControls(string containerName) { List<SecurityControl> ret = new List<SecurityControl>(); using (WpfSecurityDbContext db = new WpfSecurityDbContext()) { ret.AddRange(db.ControlsToSecure.Where(s => s.ContainerName == containerName).ToList()); } return ret; }}LoadControlsToSecure Method打开类并修改方法以创建类的实例。
从主窗口中的事件过程调用传入方法的容器名称。
EmployeeViewModelLoadControlsToSecure()SecurityControlManagerGetSecurityControls()SecureControls()Window_Loaded() protected override void LoadControlsToSecure(string containerName){ SecurityControlManager mgr = new SecurityControlManager(); ControlsToSecure = mgr.GetSecurityControls(containerName);}试一试按 F5 运行 WPF 应用程序,您应该会看到与以前硬编码时关闭的控件相同的控件。
检查绑定路径通常,不必在 WPF 中命名控件,因为不会从代码隐藏引用它们。
要通过安全性影响的 TextBox 控件具有包含 {Binding} 表达式的属性。
可以查询附加到控件的绑定类并检索属性的值。
此属性可用作类中属性中的值。
请考虑以下具有绑定表达式的 TextBox 控件。
TextPathElementIdentifierSecurityControl <TextBox Grid.Row="5" Grid.Column="1" Tag="SSN" Text="{Binding Path=SSN}" />和属性都具有可用于保护此控件的值“SSN”。
文件中的其他控件也具有绑定和 or 属性集。
编写一个方法来检索值,以便可以在适当的情况下从 XAML 中删除 和属性。
打开该文件,并从“雇员 ID”文本框控件中删除该属性。
从“薪水”文本框控件中删除该属性。
并从“SSN”文本框控件中删除该属性。
TagTextEmployeeControl.xamlNameTagPathNameTagEmployeeControl.xamlNameTagTag添加 GetBindingName 方法打开该文件并添加一个新方法,以从控件上使用的 Binding 表达式中检索属性的值。
清单 13 中所示的方法使用 switch 语句来确定传递给此方法的控件类型。
对于文本框,您希望从属性中获取绑定表达式,因此在文本框的 case 语句中,将变量设置为依赖项属性 TextBox.TextProperty。
每个控件根据最常绑定到的属性使用不同的依赖项属性。
SecurityViewModelBase.csPathGetBindingName()Textprop清单 13:GetBindingName() 方法protected virtual string GetBindingName(Control ctl){ string ret = string.Empty; BindingExpression exp; DependencyProperty prop; if (ctl != null) { // Get the unique Dependency Property for each control that can be used to get a {Binding Path=xxx} expression switch (ctl.GetType().Name.ToLower()) { case "textbox": prop = TextBox.TextProperty; break; case "label": prop = Label.ContentProperty; break; case "menuitem": prop = MenuItem.HeaderProperty; break; ... // MORE CONTROLS GO HERE default: // Add your own custom/third-party controls prop = GetBindingNameCustom(ctl); break; } if (prop != null) { // Get Binding Expression exp = ctl.GetBindingExpression(prop); // If we have a valid binding attempt to get the binding path if (exp != null && exp.ParentBinding != null && exp.ParentBinding.Path != null) { if (!string.IsNullOrEmpty(exp.ParentBinding.Path.Path)) { ret = exp.ParentBinding.Path.Path; } } } } if (!string.IsNullOrEmpty(ret)) { if (ret.Contains(".")) { ret = ret.Substring(ret.LastIndexOf(".") + 1); } } return ret;}在控件上调用该方法,传入 Dependency 属性。
如果从此方法获取值,请检查属性中是否有值。
如果是,则为传递给 Binding 表达式的属性的名称。
GetBindingExpression()ParentBinding.Path.PathPath从 XAML 容器加载控件时,可以通过调用方法来设置属性。
添加下面代码段中显示的代码。
BindingPathGetBindingName() protected virtual void LoadControlsInXAMLContainer(object element){ // REST OF THE CODE HERE // See if there are any data bindings ctl.BindingPath = GetBindingName(element as Control); // REST OF THE CODE HERE }GetBindingNameCustom() 方法如果您使用的是任何第三方控件,则需要一种可以输入这些控件的方法。
在 switch 语句的“default”中,调用如下所示的方法。
在此方法中,您可以添加自己的自定义控件并获取该控件的相应 Dependency 属性。
GetBindingNameCustom() protected virtual DependencyProperty GetBindingNameCustom(Control control) { DependencyProperty prop = null; // Add custom control binding expressions here switch (control.GetType().Name.ToLower()) { case "my_custom_control": // prop = ControlType.BindingProperty; break; } return prop;}修改安全控制方法由于现在可以将属性填充到类中,因此需要检查该属性以查看它是否与集合中的属性匹配。
打开类并找到方法。
在按名称或标记搜索控件的循环中,插入清单 14 中以粗体显示的代码。
BindingPathXAMLControlInfoElementIdentifierControlsToSecureSecurityViewModelBaseSecureControls()清单 14:添加代码以查找与要保护的控件匹配的绑定路径public virtual void SecureControls(object element, string containerName){ // REST OF THE CODE HERE // Loop through controls foreach (SecurityControl secCtl in ControlsToSecure) { secCtl.ElementIdentifier = secCtl.ElementIdentifier.ToLower(); // Search for Name property ctl = ControlsInContainer.Find(c => c.ControlName.ToLower() == secCtl.ElementIdentifier); if (ctl == null) { // Search for BindingPath ctl = ControlsInContainer.Find(c => c.BindingPath.ToLower() == secCtl.ElementIdentifier); } // REST OF THE CODE HERE }}修改 ConsiderForSecurity() 方法为支持绑定表达式而需要对代码进行的最后一次更改是在类中。
打开 XAMLControlInfo.cs 文件并找到该方法。
添加以粗体显示的行以检查属性是否具有值。
XAMLControlInfoConsiderForSecurity()BindingPath public bool ConsiderForSecurity(){ return !string.IsNullOrEmpty(BindingPath) || !string.IsNullOrEmpty(ControlName) || !string.IsNullOrEmpty(Tag);}试一试运行 WPF 应用程序,如果已正确执行所有操作,则应会看到相同的控件像以前一样关闭,即使删除了 and 属性也是如此。
NameTag总结在本文中,你将为 WPF 组合一个数据驱动的安全系统。
使用数据驱动的方法可以更改要保护的控件,通常无需更改任何代码。
本文中的代码确实使用反射,因此运行速度会慢一些;但是,如果您正在加载包含数据的屏幕,大多数用户甚至不会注意到加载安全信息可能需要额外的半秒。
还可以向此代码添加一些缓存,以确保仅检索一次安全数据。
多年来,我一直在 WPF 应用程序中使用这样的代码,并且它总是运行良好。
我希望当您需要安全系统时,可以在 WPF 应用程序中使用此代码。
获取示例代码可以通过访问问题和文章下的 www.CODEMag.com 或访问 resources.pdsa.com/downloads 来下载本文的示例代码。
从类别下拉列表中选择“航道/PDSA 文章”。
表 1:安全视图模型基类中的属性/方法列表属性/方法描述控制到安全要在 WPF 容器中保护的控件列表控件在容器中WPF 容器中的控件列表当前校长登录用户的当前 IPrincipal 对象安全控制()调用此方法以保护 WPF 容器中的所有控件。
LoadControlsToSecure()此方法加载要保护的控件列表。
LoadControlsInXAMLContainer()此方法加载 WPF 容器中的所有控件。
表 2:存储在 SecurityControl 对象中的示例安全数据集容器名称元素标识符模式角色员工控制新建按钮倒塌用户,主管员工控制员工编号只读管理员,主管员工控制工资隐藏管理员工控制SSN禁用主管员工控制保存按钮禁用管理员,主管
译文控制系统权限WPF(控件属性方法添加代码)
(图片来源网络,侵删)

联系我们

在线咨询:点击这里给我发消息