商信互联
考虑以下情况。您有一个不安全的XAF应用程序,其业务模型包括Employee业务类。此类公开了诸如个人数据,相关部门和分配的任务之类的信息。启用安全系统时,会将用户对象添加到业务模型中,但是登录到您的应用程序的用户是雇员。本主题说明如何将“用户”和“员工”合并到一个实体中。为此,将支持Employee类中的几个与安全性相关的接口,结果,安全系统会将Employee类型识别为可能的User类型之一。您将把Employee类型分配给应用程序设计器中的SecurityStrategy.UserType属性。另一个好处是,可以使用CurrentUserId() 函数标准运算符来获取当前Employee的标识符(例如,定义“分配给我的任务”列表视图过滤器)。
DevExpress代码示例数据库(http://www.devexpress.com/example=E4160)中提供了完整的示例项目。
从新的XAF解决方案开始。将以下Employee和Task业务类添加到模块项目中。
[DefaultClassOptions]
public class Employee : Person {
public Employee(Session session)
: base(session) { }
[Association("Employee-Task")]
public XPCollection<EmployeeTask> OwnTasks {
get { return GetCollection<EmployeeTask>(nameof(OwnTasks)); }
}
}
[DefaultClassOptions, ImageName("BO_Task")]
public class EmployeeTask : Task {
public EmployeeTask(Session session)
: base(session) { }
private Employee owner;
[Association("Employee-Task")]
public Employee Owner {
get { return owner; }
set { SetPropertyValue(nameof(Owner), ref owner, value); }
}
}
<DefaultClassOptions> _
Public Class Employee
Inherits Person
Public Sub New(ByVal session As Session)
MyBase.New(session)
End Sub
<Association("Employee-Task")> _
Public ReadOnly Property OwnTasks() As XPCollection(Of EmployeeTask)
Get
Return GetCollection(Of EmployeeTask)(NameOf(OwnTasks))
End Get
End Property
End Class
<DefaultClassOptions, ImageName("BO_Task")> _
Public Class EmployeeTask
Inherits Task
Public Sub New(ByVal session As Session)
MyBase.New(session)
End Sub
Private _owner As Employee
<Association("Employee-Task")> _
Public Property Owner() As Employee
Get
Return _owner
End Get
Set(ByVal value As Employee)
SetPropertyValue(NameOf(Owner), _owner, value)
End Set
End Property
End Class
为包含Employee类的项目添加对DevExpress.ExpressApp.Security.v 19.2 .dll程序集的引用。用以下代码扩展Employee类。
using DevExpress.ExpressApp.Security;
using DevExpress.Persistent.Validation;
// ...
public class Employee : Person, ISecurityUser {
// ...
#region ISecurityUser Members
private bool isActive = true;
public bool IsActive {
get { return isActive; }
set { SetPropertyValue(nameof(IsActive), ref isActive, value); }
}
private string userName = String.Empty;
[RuleRequiredField("EmployeeUserNameRequired", DefaultContexts.Save)]
[RuleUniqueValue("EmployeeUserNameIsUnique", DefaultContexts.Save,
"The login with the entered user name was already registered within the system.")]
public string UserName {
get { return userName; }
set { SetPropertyValue(nameof(UserName), ref userName, value); }
}
#endregion
}
Imports DevExpress.ExpressApp.Security
Imports DevExpress.Persistent.Validation
' ...
Public Class Employee
Inherits Person
Implements ISecurityUser
' ...
#Region "ISecurityUser Members"
Private _userName As String = String.Empty
Private ReadOnly Property UserName_() As String Implements ISecurityUser.UserName, IAuthenticationStandardUser.UserName
Get
Return _userName
End Get
End Property
<RuleRequiredField("EmployeeUserNameRequired", DefaultContexts.Save), _
RuleUniqueValue("EmployeeUserNameIsUnique", DefaultContexts.Save, _
"The login with the entered user name was already registered within the system.")> _
Public Property UserName() As String Implements IAuthenticationActiveDirectoryUser.UserName
Get
Return _userName
End Get
Set(ByVal value As String)
SetPropertyValue(NameOf(UserName), _userName, value)
End Set
End Property
Private _isActive As Boolean = True
Private ReadOnly Property IsActive_() As Boolean Implements ISecurityUser.IsActive
Get
Return _isActive
End Get
End Property
Public Property IsActive() As Boolean
Get
Return _isActive
End Get
Set(ByVal value As Boolean)
SetPropertyValue(NameOf(IsActive), _isActive, value)
End Set
End Property
#End Region
End Class
有关此接口及其成员的详细信息,请参考ISecurityUser接口说明。
如果您不打算使用AuthenticationStandard身份验证类型,请跳过此部分。
用以下代码扩展Employee类。
using System.ComponentModel;
using DevExpress.ExpressApp.Security;
using DevExpress.Persistent.Base;
using DevExpress.Persistent.Base.Security;
// ...
public class Employee : Person, ISecurityUser, IAuthenticationStandardUser {
// ...
#region IAuthenticationStandardUser Members
private bool changePasswordOnFirstLogon;
public bool ChangePasswordOnFirstLogon {
get { return changePasswordOnFirstLogon; }
set {
SetPropertyValue(nameof(ChangePasswordOnFirstLogon), ref changePasswordOnFirstLogon, value);
}
}
private string storedPassword;
[Browsable(false), Size(SizeAttribute.Unlimited), Persistent, SecurityBrowsable]
protected string StoredPassword {
get { return storedPassword; }
set { storedPassword = value; }
}
public bool ComparePassword(string password) {
return PasswordCryptographer.VerifyHashedPasswordDelegate(this.storedPassword, password);
}
public void SetPassword(string password) {
this.storedPassword = PasswordCryptographer.HashPasswordDelegate(password);
OnChanged(nameof(StoredPassword));
}
#endregion
}
Imports System.ComponentModel
Imports DevExpress.ExpressApp.Security
Imports DevExpress.Persistent.Base
Imports DevExpress.Persistent.Base.Security
' ...
Public Class Employee
Inherits Person
Implements ISecurityUser, IAuthenticationStandardUser
' ...
#Region "IAuthenticationStandardUser Members"
Private _changePasswordOnFirstLogon As Boolean
Public Property ChangePasswordOnFirstLogon() As Boolean _
Implements IAuthenticationStandardUser.ChangePasswordOnFirstLogon
Get
Return _changePasswordOnFirstLogon
End Get
Set(ByVal value As Boolean)
SetPropertyValue(NameOf(ChangePasswordOnFirstLogon), _changePasswordOnFirstLogon, value)
End Set
End Property
Private _storedPassword As String
<Browsable(False), Size(SizeAttribute.Unlimited), Persistent(), SecurityBrowsable()> _
Protected Property StoredPassword() As String
Get
Return _storedPassword
End Get
Set(ByVal value As String)
_storedPassword = value
End Set
End Property
Public Function ComparePassword(ByVal password As String) As Boolean _
Implements IAuthenticationStandardUser.ComparePassword
Return PasswordCryptographer.VerifyHashedPasswordDelegate(Me.storedPassword, password)
End Function
Public Sub SetPassword(ByVal password As String) _
Implements IAuthenticationStandardUser.SetPassword
Me.storedPassword = PasswordCryptographer.HashPasswordDelegate(password)
OnChanged(NameOf(StoredPassword))
End Sub
#End Region
End Class
有关此接口及其成员的详细信息,请参考IAuthenticationStandardUser接口说明。
如果您不打算使用AuthenticationActiveDirectory身份验证类型,请跳过此部分。
将IAuthenticationActiveDirectoryUser接口添加到Employee类的受支持接口列表中。
using DevExpress.ExpressApp.Security;
using DevExpress.Persistent.Base.Security;
// ...
public class Employee : Person, ISecurityUser,
IAuthenticationStandardUser, IAuthenticationActiveDirectoryUser {
// ...
}
Imports DevExpress.ExpressApp.Security
Imports DevExpress.Persistent.Base.Security
' ...
Public Class Employee
Inherits Person
Implements ISecurityUser, IAuthenticationStandardUser, IAuthenticationActiveDirectoryUser
' ...
Public Property UserName() As String Implements IAuthenticationActiveDirectoryUser.UserName
' ...
End Property
End Class
此接口声明的IAuthenticationActiveDirectoryUser.UserName属性已作为ISecurityUser接口的一部分在您的代码中实现。
用以下代码扩展Employee类。
using DevExpress.ExpressApp.Security;
using DevExpress.Persistent.Base.Security;
using DevExpress.Persistent.Validation;
//...
[DefaultClassOptions]
public class Employee : Person, ISecurityUser,
IAuthenticationStandardUser, IAuthenticationActiveDirectoryUser,
ISecurityUserWithRoles {
// ...
#region ISecurityUserWithRoles Members
IList<ISecurityRole> ISecurityUserWithRoles.Roles {
get {
IList<ISecurityRole> result = new List<ISecurityRole>();
foreach (EmployeeRole role in EmployeeRoles) {
result.Add(role);
}
return result;
}
}
#endregion
[Association("Employees-EmployeeRoles")]
[RuleRequiredField("EmployeeRoleIsRequired", DefaultContexts.Save,
TargetCriteria = "IsActive",
CustomMessageTemplate = "An active employee must have at least one role assigned")]
public XPCollection<EmployeeRole> EmployeeRoles {
get {
return GetCollection<EmployeeRole>(nameof(EmployeeRoles));
}
}
}
Imports DevExpress.ExpressApp.Security
Imports DevExpress.Persistent.Base.Security
Imports DevExpress.Persistent.Validation
' ...
<DefaultClassOptions> _
Public Class Employee
Inherits Person
Implements ISecurityUser, IAuthenticationStandardUser, IAuthenticationActiveDirectoryUser, ISecurityUserWithRoles
' ...
#Region "ISecurityUserWithRoles Members"
Private ReadOnly Property ISecurityUserWithRoles_Roles() As IList(Of ISecurityRole) Implements ISecurityUserWithRoles.Roles
Get
Dim result As IList(Of ISecurityRole) = New List(Of ISecurityRole)()
For Each role As EmployeeRole In EmployeeRoles
result.Add(role)
Next role
Return result
End Get
End Property
#End Region
<Association("Employees-EmployeeRoles"), RuleRequiredField("EmployeeRoleIsRequired", DefaultContexts.Save, TargetCriteria := "IsActive", CustomMessageTemplate := "An active employee must have at least one role assigned")> _
Public ReadOnly Property EmployeeRoles() As XPCollection(Of EmployeeRole)
Get
Return GetCollection(Of EmployeeRole)(NameOf(EmployeeRoles))
End Get
End Property
End Class
有关此接口及其成员的详细信息,请参考ISecurityUserWithRoles接口说明。
无法定义与内置PermissionPolicyRole类的多对多关联(此类已经与PermissionPolicyUser关联),这就是为什么应在模块项目中实现以下自定义Role类的原因。
using System.Linq;
using DevExpress.Persistent.Base;
using DevExpress.Persistent.BaseImpl.PermissionPolicy;
// ...
[ImageName("BO_Role")]
public class EmployeeRole : PermissionPolicyRoleBase, IPermissionPolicyRoleWithUsers {
public EmployeeRole(Session session)
: base(session) {
}
[Association("Employees-EmployeeRoles")]
public XPCollection<Employee> Employees {
get {
return GetCollection<Employee>(nameof(Employees));
}
}
IEnumerable<IPermissionPolicyUser> IPermissionPolicyRoleWithUsers.Users {
get { return Employees.OfType<IPermissionPolicyUser>(); }
}
}
Imports System.Linq
Imports DevExpress.Persistent.Base
Imports DevExpress.Persistent.BaseImpl.PermissionPolicy
' ...
<ImageName("BO_Role")> _
Public Class EmployeeRole
Inherits PermissionPolicyRoleBase
Implements IPermissionPolicyRoleWithUsers
Public Sub New(ByVal session As Session)
MyBase.New(session)
End Sub
<Association("Employees-EmployeeRoles")> _
Public ReadOnly Property Employees() As XPCollection(Of Employee)
Get
Return GetCollection(Of Employee)(NameOf(Employees))
End Get
End Property
Private ReadOnly Property IPermissionPolicyRoleWithUsers_Users() As IEnumerable(Of IPermissionPolicyUser) Implements IPermissionPolicyRoleWithUsers.Users
Get
Return Employees.OfType(Of IPermissionPolicyUser)()
End Get
End Property
End Class
用以下代码扩展Employee类。
using System.Linq;
using DevExpress.ExpressApp.Security;
using DevExpress.Persistent.Base;
using DevExpress.Persistent.Base.Security;
// ...
[DefaultClassOptions]
public class Employee : Person, ISecurityUser,
IAuthenticationStandardUser, IAuthenticationActiveDirectoryUser,
IPermissionPolicyUser {
// ...
#region IPermissionPolicyUser Members
IEnumerable<IPermissionPolicyRole> IPermissionPolicyUser.Roles {
get { return EmployeeRoles.OfType<IPermissionPolicyRole>(); }
}
#endregion
}
Imports System.Linq
Imports DevExpress.ExpressApp.Security
Imports DevExpress.Persistent.Base
Imports DevExpress.Persistent.Base.Security
' ...
<DefaultClassOptions> _
Public Class Employee
Inherits Person
Implements ISecurityUser, IAuthenticationStandardUser, IAuthenticationActiveDirectoryUser, IPermissionPolicyUser
' ...
#Region "IPermissionPolicyUser Members"
Private ReadOnly Property IPermissionPolicyUser_Roles() As IEnumerable(Of IPermissionPolicyRole) Implements IPermissionPolicyUser.Roles
Get
Return EmployeeRoles.OfType(Of IPermissionPolicyRole)()
End Get
End Property
#End Region
End Class
有关此接口及其成员的详细信息,请参考IPermissionPolicyUser接口说明。
该ICanInitialize.Initialize方法用于分配默认角色,当您使用AuthenticationActiveDirectory认证和设置AuthenticationActiveDirectory.CreateUserAutomatically为true。如果不需要支持用户自动创建,请跳过此步骤。否则,使用以下代码扩展Employee类。
using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Security;
using DevExpress.Data.Filtering;
using DevExpress.Persistent.Base;
using DevExpress.Persistent.Base.Security;
// ...
[DefaultClassOptions]
public class Employee : Person, ISecurityUser,
IAuthenticationStandardUser, IAuthenticationActiveDirectoryUser,
IPermissionPolicyUser, ICanInitialize {
// ...
#region ICanInitialize Members
void ICanInitialize.Initialize(IObjectSpace objectSpace, SecurityStrategyComplex security) {
EmployeeRole newUserRole = (EmployeeRole)objectSpace.FindObject<EmployeeRole>(
new BinaryOperator("Name", security.NewUserRoleName));
if (newUserRole == null) {
newUserRole = objectSpace.CreateObject<EmployeeRole>();
newUserRole.Name = security.NewUserRoleName;
newUserRole.IsAdministrative = true;
newUserRole.Employees.Add(this);
}
}
#endregion
}
Imports DevExpress.ExpressApp
Imports DevExpress.ExpressApp.Security
Imports DevExpress.Data.Filtering
Imports DevExpress.Persistent.Base
Imports DevExpress.Persistent.Base.Security
' ...
<DefaultClassOptions> _
Public Class Employee
Inherits Person
Implements ISecurityUser, IAuthenticationStandardUser, IAuthenticationActiveDirectoryUser, IPermissionPolicyUser, ICanInitialize
' ...
#Region "ICanInitialize Members"
Private Sub ICanInitialize_Initialize(ByVal objectSpace As IObjectSpace, ByVal security As SecurityStrategyComplex) Implements ICanInitialize.Initialize
Dim newUserRole As EmployeeRole = CType(objectSpace.FindObject(Of EmployeeRole)(New BinaryOperator("Name", security.NewUserRoleName)), EmployeeRole)
If newUserRole Is Nothing Then
newUserRole = objectSpace.CreateObject(Of EmployeeRole)()
newUserRole.Name = security.NewUserRoleName
newUserRole.IsAdministrative = True
newUserRole.Employees.Add(Me)
End If
End Sub
#End Region
End Sub
调用Application Designer,然后将SecurityStrategyComplex组件从“工具箱”拖到“安全性”窗格中。然后,将AuthenticationStandard或AuthenticationActiveDirectory组件放在SecurityStrategyComplex附近。要使用自定义EmployeeRole角色和自定义Employee用户而不是默认角色和用户,请在“属性”窗口中修改SecurityStrategyComplex.RoleType和SecurityStrategy.UserType值。此步骤应在WinForms,ASP.NET和Mobile应用程序中执行。
如果决定使用Active Directory身份验证,则可以跳过本节。从标准身份验证开始的最低要求是管理员角色以及与此角色相关联的管理员用户。要添加这些对象,请编辑位于模块项目的DatabaseUpdate文件夹中的Updater.cs(Updater.vb)文件。以以下方式重写ModuleUpdater.UpdateDatabaseAfterUpdateSchema方法。
using DevExpress.ExpressApp.Security.Strategy;
// ...
public override void UpdateDatabaseAfterUpdateSchema() {
base.UpdateDatabaseAfterUpdateSchema();
EmployeeRole adminEmployeeRole = ObjectSpace.FindObject<EmployeeRole>(
new BinaryOperator("Name", SecurityStrategy.AdministratorRoleName));
if (adminEmployeeRole == null) {
adminEmployeeRole = ObjectSpace.CreateObject<EmployeeRole>();
adminEmployeeRole.Name = SecurityStrategy.AdministratorRoleName;
adminEmployeeRole.IsAdministrative = true;
adminEmployeeRole.Save();
}
Employee adminEmployee = ObjectSpace.FindObject<Employee>(
new BinaryOperator("UserName", "Administrator"));
if (adminEmployee == null) {
adminEmployee = ObjectSpace.CreateObject<Employee>();
adminEmployee.UserName = "Administrator";
adminEmployee.SetPassword("");
adminEmployee.EmployeeRoles.Add(adminEmployeeRole);
}
ObjectSpace.CommitChanges();
}
Imports DevExpress.ExpressApp.Security.Strategy
' ...
Public Overrides Sub UpdateDatabaseAfterUpdateSchema()
MyBase.UpdateDatabaseAfterUpdateSchema()
Dim adminEmployeeRole As EmployeeRole = ObjectSpace.FindObject(Of EmployeeRole)(New BinaryOperator("Name", SecurityStrategy.AdministratorRoleName))
If adminEmployeeRole Is Nothing Then
adminEmployeeRole = ObjectSpace.CreateObject(Of EmployeeRole)()
adminEmployeeRole.Name = SecurityStrategy.AdministratorRoleName
adminEmployeeRole.IsAdministrative = True
adminEmployeeRole.Save()
End If
Dim adminEmployee As Employee = ObjectSpace.FindObject(Of Employee)(New BinaryOperator("UserName", "Administrator"))
If adminEmployee Is Nothing Then
adminEmployee = ObjectSpace.CreateObject(Of Employee)()
adminEmployee.UserName = "Administrator"
adminEmployee.SetPassword("")
adminEmployee.EmployeeRoles.Add(adminEmployeeRole)
End If
ObjectSpace.CommitChanges()
End Sub
现在,您可以运行Windows窗体或ASP.NET应用程序以查看结果。您将看到Employee对象用作自定义用户类型。
将ListViewFilterAttribute属性应用于EmployeeTask类,以定义“列表视图”过滤器。若要引用当前的Employee标识符,请使用CurrentUserId()函数。
using DevExpress.ExpressApp.SystemModule;
// ...
[ListViewFilter("All Tasks", "")]
[ListViewFilter("My Tasks", "[Owner.Oid] = CurrentUserId()")]
public class EmployeeTask : Task {
// ...
}
Imports DevExpress.ExpressApp.SystemModule
' ...
<ListViewFilter("All Tasks", "")> _
<ListViewFilter("My Tasks", "[Owner.Oid] = CurrentUserId()")> _
Public Class EmployeeTask
Inherits Task
' ...
End Class
下图显示了结果。