给您的 C# 代码增添色彩






4.95/5 (11投票s)
管理具有复杂权限的代码可能很困难,因为代码可能会分布在多个类中。通过将权限直接嵌入类中的方法和属性,可以减少代码量并简化可维护性。
引言
管理具有复杂权限的代码可能很困难,因为代码可能会分布在多个类中。通过将权限直接嵌入类中的方法和属性,可以减少代码量并简化可维护性。
目录
什么是 Jazz?
Jazz 是一个开源项目,可在 codeplex 上获取。它是一个紧凑、模块化的框架,可以轻松添加到您的开发项目中以构建安全的应用程序。Jazz 允许在少量 C# 或 VB 代码中实现完整的业务流程。
Jazz 可用作项目的附加组件,用于实现对对象、属性和方法的安全访问。
使用 Jazz 有多容易?
将 Jazz 添加到项目中的步骤如下:
- 确保
targetObject
类继承自 jObject,其中MyFirstJazzClass
是您的类名。/// <summary> /// This is a minimalist 'Jazz Class'. /// The '[Jazz]' attribute and inheriting from jObject are required. /// </summary> [Jazz] public class MyFirstJazzClass : jObject { // insert standard C# code here }
- 使用任何 Grant 构造函数属性来装饰您的代码,其中:“Started”是
MyFirstJazzClass
中某个状态的名称,“Manager
”是MyFirstJazzClass
中某个角色的名称,“IsValid
”是MyFirstJazzClass
中某个属性的名称。/// <summary> /// The ‘Complete’ command is only allowed to be executed in the /// ‘Started’ state by the Manager when the jObject is valid. /// </summary> [GrantStates("Started"), GrantRoles("Manager"), GrantProperty("IsValid")] publicvoid Complete() { this.EnterState("Completed"); }
- 创建 Client Nexus 来对 myObject 执行安全操作,例如方法调用和属性访问。其中“
myUser
”是用户的名称。NexusConfig config = newNexusConfig() { LoginProfile = jUser.Create("myUser"), BindUnits = newjObject[] { new MyFirstJazzClass() } }; // Instantiate the Client Nexus where secure operations are performed using (ClientNexus nexus = ClientNexus.Create(config)) { // Perform your secure operations on the jObjects }
谁会使用 Jazz?
Jazz 面向以下应用程序:- 安全权限是关键要求
- 允许特定用户和/或角色访问业务对象中的特定成员很重要。
- 安全规则是动态的——即规则是不断变化的数据的函数。
- 开发人员希望通过将权限语句直接嵌入属性和方法来提高代码的可维护性。
- 应用程序有很多用户、角色和复杂的操作。
- 应用程序具有工作流属性。要求某些方法和属性仅在特定状态下可用。
- 用户界面与业务对象清晰分离。
名称:它来自哪里?
John Hansen 和 Charles Wiebe 开发了 Jetfire,一种持久化脚本语言,并在其基础上构建了许多应用程序(从 2007 年至今)。“我们希望基于 .NET 4.0 构建 Jetfire 的演进版本,并将其在过去两年中遇到的所有‘假设’变成一个更轻量、更灵活的平台。”这产生了大量的‘爵士乐’。
有关更多信息,请查看 wiki:http://wiki.jazzfire.net。
架构
架构包括:
- jClass 和 jObject
- jObject 的内置成员
- 一流对象
- Client Nexus
- 权限属性
jClass
jClass 是一个对象,提供有关 jObject 类型的架构信息。
jObject,也称为 Jazz 对象,是 Jazz 中提供许多功能的通用构建块对象。所有 一流对象都继承自 jObject。
jObject 的内置成员
jObject
类包含以下公共成员:- Class: 返回提供 Jazz 工作流类型操作和数据的 Jazz 工作流类。Class 在绑定过程中设置。
- ClientNexus: 返回 nexus。
object.EnterState
:使“this”jObject
进入由“stateName
”指定的 状态。. - Flags:以下标志提供有关
jObject
的信息。IsAccessible
:当此实例可访问时返回 true。如果由于 ACL 不允许访问,或者不存在具有此 ID 的 jObject,则可能返回 false。当IsAccessible
返回 false 时尝试任何访问,将生成 'AccessViolationException
'。IsBound
:如果此对象已绑定(“Bind”操作)到客户端 nexus,则返回 true。请参阅 http://wiki.jazzfire.net/Bind.ashx。IsEmpty
:如果这是“jObject
”类的空对象,则返回 true。空对象是“ReadOnly
”。IsReadOnly
:当此实例为只读时返回 true。当实例为只读时,可以读取公共属性和字段。无法设置字段和属性。无法执行方法。尝试写入或执行访问时将生成 'AccessViolationException
'。IsRoot
:如果此对象是“根”对象,则返回 true。'IsRoot
' 仅适用于持久化 Jazz 对象。根对象即使没有引用,除非被删除,否则不会被服务器 GC 移除。
- FullName:以“服务器名称”。“工作区名称”。“此名称”的格式返回名称。
- ID:从 nexus 获取 jObjects 的 Guid 值。ID 在“Bind”过程中分配。在初始绑定之前,ID 为空。
IsNameUnique
:表示名称对于此“Class”的所有实例都是唯一的。例如,所有 'jRoleBase
' 实例都将具有唯一的名称。- Name:返回此 '
jObject
' 的名称。如果您的 'jObject
' 类需要唯一名称,请使用 'jNameUnique
' 作为基类。 - StorageStatus:指示对象数据是待处理还是已确认。“Pending”表示对象已更改,但服务器。Nexus 尚未“
Confirmed
”该操作。 - Status:指示此
jObject
实例是否“Valid
”、“Deleted
”、“Broken”或“Disconnected”。IsDeleted
:表示 jObject 已删除。IsValid
:表示 jObject 有效。
- StorageType:存储单元的类型。
- Timestamp:返回服务器 nexus 发出的
timeStamp
。当为空时,表示对象已创建但尚未首次写入。时间戳仅在 'StorageStatus
' 已确认时有效。时间戳在单个服务器 nexus 内是唯一的。 - Workspace:返回此‘
jObject
’所在的工作区。所有jObject
实例必须存在于 1 个工作区中,且只能存在于 1 个工作区中。
受保护成员包括:
- _ACL:ACL(访问控制列表)控制如何访问此
jObject
实例。注意:如果 ACL 为空,则没有访问限制。 - _Add(jAclItem):将“
jAclItem
”添加到此 jObject 的 ACL。 - _Delete:删除 jObject。
- _Remove(jAclItem):从此
jObject
的 ACL 中删除“jAclItem
”。 - OnAssign:允许分配预定义的 ID。这通常用于定义类的空对象。
OnBind
:在此实例绑定到客户端 nexus 之前执行。此方法将在每次将此实例绑定到客户端 nexus 时执行。执行此方法时,“Grant”属性无效。OnInitialBind
:在此实例首次绑定到客户端 nexus 之前执行。执行此方法时,基类 Jazz 对象字段已设置(除了 Flag 'Bound' 和 'Initialized' 位)。执行此方法时,“Grant”构造函数无效,ACL 也无效。
Jazz 的一流对象
一流对象可以由开发人员完全自定义。在 Jazz 中,一流对象包括:
- jRole
- jUser
- jProfile
- jWorkspace
角色为登录用户提供访问工作流、工作区内容、执行方法、访问属性和更改权限的权限。角色可以包含其他角色。
- jRole 与 .NET 中的角色(如 Forms Authentication 和 Active Directory)是独立的。但是,开发人员可以自定义 jRole 以包装 .NET 角色。
角色可以分配给 Jazz 用户。可以使用 “GrantRole 构造函数”基于角色来允许访问方法和属性。
jObject 可以以编程方式分配角色(请参阅 ACL)。角色在 ACL 项中使用,通过限制对 jObject 的访问。在 客户端 nexus 中,对用户不可访问的 jObject 会被标记为不可访问。
角色可以赋予 用户某些特殊能力,这些能力可能被其他用户拒绝,例如:
- 执行已用 'GrantRole' 构造函数声明的方法的能力。
- 设置和获取已用 'GrantRole' 构造函数声明的属性的能力。
- 设置具有私有 getter 和公共 setter 的属性的能力。
- 限制对 工作区(及其对象)的访问,前提是 工作区的“访问控制列表”已设置。
- 限制对 '访问控制列表' 已设置的 jObject 的访问。
角色由创建时分配的唯一名称标识。创建后,角色名称不能更改。
Jazz 用户类 jUser 描述一个物理用户,通常是人。Jazz 用户包含一个唯一名称和一组 配置文件。Jazz 用户必须至少有一个配置文件。Jazz 用户不能直接包含 角色;配置文件包含角色。
jUser 与 .NET 中的用户(如 Forms Authentication 和 Active Directory)是独立的。但是,开发人员可以自定义 jUser 类以包装 .NET 用户。
jProfile
一个 jUser 可以有一个或多个配置文件。这些配置文件用于以用户生活的不同方面进行登录。
- 例如:乔,水管工;乔,足球教练;乔,爸爸。
- 例如:简,教练;简,球员;简,裁判。
jWorkspace 是一个 Jazz 目录,允许用户组织对象。当 jWorkspace 分配了访问控制列表 (ACL) 时,分配给此工作区的任何对象都将像工作区本身一样受到保护。
如果用户无法访问工作区,则用户无法访问工作区中的对象。
如果用户只能读取工作区,则用户只能读取工作区中的对象。
ClientNexus
维基百科定义 Nexus 为连接,通常是多个元素相遇的地方,例如辐条在轮毂处,最初源自一个表示“连接,绑定”的拉丁动词。
Client Nexus 封装了用户的对象,并对绑定的对象执行安全操作。
“完全定制”是什么意思?
开发人员可以继承基类来定制所有或任何一流对象。
这是通过继承 ClientNexusGeneric
类并将继承的一流类传递进去来实现的。
注意:jProfile
、jUser
、jRole
和 jWorkspace
是开发人员设计的类。开发人员可以选择这些类的任何名称,只要它们符合每个接口即可。
一流对象包含实现安全所需的功能,并且使用的代码符合开发项目中使用的标准类。自定义允许开发人员编写基于 Windows 或 Forms 身份验证的用户和角色类。
Jazz 功能
状态
状态内置于 jObject 中。
jObjects 可以定义一组状态,允许 jObject 实例处于一种给定状态。jObject 通过执行 'EnterState
' 方法来改变状态。可以通过在状态方法中插入代码来在进入状态时执行操作。通过使用 'GrantState
' 构造函数,可以根据 jObject
的状态变化来更改实例方法和属性的可访问性。
定义状态
Jazz 中将状态声明为具有 void 返回类型的方法。例如:
/// <summary>
/// Using the 'State' attribute the 'Completed' method is declared a 'State'.
/// </summary>
[State]
void Completed()
{
}
当使用 'StartState
' 属性时,jObject 在实例化时处于该状态。
/// <summary>
/// The state named 'Started' is entered automatically upon instantiation.
/// </summary>
[StartState]
void Started()
{
}
进入状态
要进入状态,请使用“EnterState
”方法。
this.EnterState("Completed");
每个 jObject 都有一个访问控制列表 (ACL)。如果 ACL 为空,则 jObject 没有访问限制。可以以编程方式添加 ACL 项,例如:
- 某些角色对 jObject 具有只读访问权限。
- 某些角色对 jObject 具有完全访问权限。
- jObject 对某些角色完全不可访问。
- 某些用户对 jObject 具有只读访问权限。
- 某些用户对 jObject 具有完全访问权限。
- jObject 对某些用户完全不可访问。
ACL 由 Jazz 中的内部机制 强制执行。
ACL 示例
在此示例中,“Manager”和“Boss”是代码中定义的角色。
已登录用户角色 | 对象 ACL 项 | 允许的操作 |
“Manager” | 空 ACL | 允许对对象进行完全访问。 |
无角色 | 空 ACL | 允许对对象进行完全访问。 |
“Manager” | [1] “Manager” - ‘可访问’ | 允许对对象进行完全访问。 |
无角色 | [1] “Manager” - ‘可访问’ | 不允许访问对象。 |
“Manager” | [1] “Manager” - ‘只读’ | 对象的只读访问。 |
无角色 | [1] “Manager” - ‘只读’ | 不允许访问对象。 |
“Manager” | [1] “Manager” - ‘只读’ [2] “Boss” - ‘可访问’ |
对象的只读访问。 |
“Manager”、“Boss” | [1] “Manager” - ‘只读’ [2] “Boss” - ‘可访问’ |
允许对对象进行完全访问。 |
访问强制执行
Grant 构造函数由三个 C# 属性组成:
GrantProperty
当“Property
”值为布尔类型且返回“true”时,或者当值为已登录用户拥有的角色时,或者当返回值为当前状态时,‘GrantProperty
’属性授予对实例方法、属性或属性 setter 的访问权限。
GrantState
‘GrantState
’属性仅在 jObject 的当前 状态 是属性指定的其中一个状态时,授予对实例方法、属性或属性 setter 的访问权限。
GrantRole
‘GrantRole
’属性仅在登录的配置文件拥有属性指定的 角色之一时,授予对实例方法、属性或属性 setter 的访问权限。
当使用多个 Grant 构造函数时,授予的访问权限将是“grant 构造函数”的逻辑 **And**。例如,如果对成员同时使用了“GrantState
”和“GrantRole
”,则当 jObject 的当前状态是“GrantState
”属性指定的状态,并且用户的登录角色是“GrantRole
”属性指定时,授予访问权限,否则不允许访问。
访问冲突
适用范围 | 当发生以下情况时,将出现访问冲突异常: | |
GrantState | 实例方法 实例属性 实例设置器 |
尝试在 jObject 的当前状态不是“GrantState”声明的 状态时执行操作。 |
GrantRole | 实例方法 实例属性 实例设置器 |
尝试在登录用户/配置文件不拥有“GrantRole”指定的 角色时执行操作。 |
GrantProperty | 实例方法 实例属性 实例设置器 |
尝试在“GrantProperty”值为 “false”时,或者为登录用户不拥有的角色时,或者返回的值不是当前状态时,执行操作。 |
只读 ACL | Jazz 对象的任何方法 | 尝试执行方法,但登录的配置文件仅拥有支持“只读”ACL 访问的角色。 |
无访问 ACL | Jazz 对象的任何方法、字段和属性。 | 当登录的配置文件不拥有支持访问的角色(即“可访问”ACL 项)时,尝试任何访问。 |
区别
Jazz 与 .NET 安全有何不同?
让我们看看 Jazz 安全与 .NET 安全有何不同。
函数 |
.NET 基于角色的安全 |
Jazz 安全 |
访问方法 |
对方法的未经授权访问将导致异常。 |
使用 Grant 构造函数。 对方法的未经授权访问将导致异常。 |
内联断言 |
.NET 命令式模式实现对方法特定部分的内联断言。 |
使用“IsAccessible”来访问对象。 使用“IsExecutable”属性来查看方法是否可访问。 使用“CanRead”和/或“CanWrite”来访问属性。 |
访问属性 |
PrincipalPermission 支持 角色的成员;命名的用户;任何已登录用户。 |
GrantRoles 支持 角色的成员。GrantStates 支持 对象中的任何状态。GrantProperty 支持 对象中的任何实例属性;命名的用户;任何已登录用户。 |
访问构造函数 |
不支持 |
未来 |
访问属性 |
不支持 |
使用 Grant 构造函数来装饰属性。 |
访问属性设置器 |
不支持 |
使用 Grant 构造函数来装饰属性设置器。 |
二级安全
Jazz 中包含的第二级安全是访问控制列表。
函数 |
.NET 基于角色的安全 |
Jazz 安全 |
对象的访问控制列表 |
不支持 |
jObject 具有允许角色、只读角色、用户和只读用户的访问控制列表。 |
工作区的访问控制列表 |
不支持 |
jWorkspace 具有允许角色、只读角色、用户和只读用户的访问控制列表。jWorkspace 是 jObjects 的容器。 |
Nexus 绑定
jObjects 可与 Nexus 一起使用,也可不与 Nexus 一起使用。在不使用 Nexus 时,不执行任何安全操作。
我们来看一下jObject
的生命周期:jObject
的实例化发生在创建新对象或从数据库检索数据并将其转换为jObjects
时。该对象是典型的 .NET 对象。ToDoItem item = new ToDoItem();
- 将
jObject
绑定到 Nexus。该对象现在属于 nexus。现在可以对jObject
执行安全操作并存储更改。 nexus.Bind(item);
jObject 的功能在“Bind”操作将其绑定到客户端 nexus 之前一直处于休眠状态。单个物理 'jObject
' 只能包含在单个 Client Nexus 中;然而,通过序列化复制的 jObject
实例可能同时存在于多个客户端 nexuses 中。
jObjects 是标准的 .NET 对象。jObjects 在“Bind”操作将其绑定到 client nexus 之前不会被激活。步骤 1 和 2 可以合并:
ToDoItem item = new ToDoItem(nexus);
jObjects 可以在任何时候绑定到 nexus。另一种方法是在需要时创建 nexus——这在引言中的代码片段中显示,其中配置文件定义为登录配置文件,并将角色和目标对象传递到 BindUnits 数组中。一旦实例化了 Nexus,就可以对目标对象执行安全操作。
Postsharp 属性
Postsharp 用于实现 Jazz 属性。要使用源代码,需要“Postsharp”。- 它可在以下网址获得:http://www.sharpcrafters.com/postsharp/download
将 Jazz 应用到你的项目
要在项目中使用 Jazz,Jazz 目标对象必须继承自 jObject,并具有装饰类的 Jazz 属性。为此,请确保引用的项目包括:
TrackerRealm.Jazz.ClientTrackerRealm.Jazz.CommonPostsharp
注意:TrackerRealm.Jazz.Client.Custom
出现在 using 语句中,因为 jUser、jRole、jProfile、jWorkspace 和 ClientNexus 在此示例中为此命名空间中的这些类使用。
using TrackerRealm.Jazz.Client.Custom;
using TrackerRealm.Jazz.Client;
using TrackerRealm.Jazz.Common;
/// <summary>
/// This is a minimalist 'Jazz Class'.
/// The '[Jazz]' attribute and inheriting from jObject are required.
/// </summary>
[Jazz]
publicclass MyFirstJazzClass : jObject
{
// standard C# code can go here
}
创建角色
jRole 可以像 .NET 对象一样以编程方式创建。以下工厂方法创建一个绑定到 nexus 的 jRole。
jRole managerRole = jRole.Create(nexus, "Manager");
创建用户
jUser 可以像 .net 对象一样以编程方式创建。以下代码片段
- 创建 jUser 和默认 jProfile(隐式地作为 jUser 工厂方法的一部分)。
- 将“Manager”角色添加到用户的配置文件。
- 将 jUser 和 jProfile 对象绑定到 nexus。
jUser user = jUser.Create(nexus, "Fred", "Manager");
创建 jObject
控制实例化的最简单方法是使用包装对象创建过程的单例。
为方法或属性分配角色
可以使用 Grant 构造函数将角色分配给方法或属性。
/// <summary>
/// The approve method is only allowed to be executed when
/// the logged in user(profile) has the role 'Manager'.
/// </summary>
[GrantRoles("Manager")]
public void Approve()
{
this.IsApproved = true;
}
访问冲突示例
例如,给定以下 jObject 属性:
/// <summary>
/// Make a request by setting the data.
/// The 'Request' property may only be set when this 'jObject' instance
/// is in the 'Start' state.
/// </summary>
public string Request
{
get { return this.data; }
[GrantStates("Started")]
set
{
this.data = value;
// prevent the data from being changed.
this.EnterState("Approved");
}
}
第一组 Request 演示了对属性的有效访问。随后的 Request 属性集演示了访问冲突。
// Enter some data - this will change the current state to Approval state.
myFlow.Request = "May I leave early on Friday?";
try
{
// Enter some more data. This will cause an exception.
myFlow.Request = "I changed my mind, I want to leave Friday at noon.";
Assert.Fail(“should not reach this line!");
}
catch (Exception ex)
{
Assert.IsTrue(ex is AccessViolationException);
}
使用“Grant State”
使用“GrantState”构造函数,方法或属性的可访问性可以随着工作流状态的变化而动态更改。
/// <summary>
/// The approve method is only allowed to be executed when this instance is the 'Approved State'.
/// </summary>
[GrantStates("Approved")]
public void Approve()
{
this.IsApproved = true;
// prevent the workflow from being approved again.
this.EnterState("Completed");
}
在此示例中,仅属性设置器的可访问性由“State”构造函数控制。
/// <summary>
/// Make a request by setting the data.
/// The 'Request' property may only be set when this 'jObject' instance
/// is in the 'Started' state.
/// </summary>
public string Request
{
get { return this.data; }
[GrantStates("Started")]
set
{
this.data = value;
// prevent the data from being changed.
this.EnterState("Approved");
}
}
添加 ACL 项
此示例显示了 new jWorkspace 和 jRole 的实例化。当使用“AclItem.Accessible
”将角色添加到工作区时,该工作区现在是安全的,只允许具有“Manager
”jRole 的用户访问。
代码片段的最后一行是如何将 ACL 添加到任何 jObect 的语法。
// Create a workspace
jWorkspace mySpace = jWorkspace.Create(nexus, "MySpace");
jRole manager = jRole.Create(nexus, "Manager");
// set 'mySpace' to be accessible only if the user possesses the role named 'Manager'.
mySpace.Add(jAclItem.Accessible(manager));
代码演练示例
一组用户共同创建待办事项,其中任何用户都可以自愿成为特定待办事项的“AssignedTo”工作者,并在项目完成后由经理审批。

- 蓝色椭圆表示状态。
- 连接线上的文本显示命令和 Grant Role 或 Grant Property。
使用 Windows 窗体项目来演示该示例。窗体代码显示了如何设计简单的窗体,而无需了解实际的 jObject。更改“GroupToDo”属性和方法,并在不更改窗体代码的情况下在窗体上看到这些更改。
用户、角色和工作区
演示中包含许多用户和角色。
角色包括:
- “ToDo”:此角色允许用户查看任何公共待办事项。
- “UserAdmin”:此角色允许用户查看定义的那些用户和角色。
用户包括:
- “Guest”(无角色)。Guest 对任何内容都没有访问权限。
- “George”(角色:“ToDo”)– George 可以访问待办事项。
- “Sarah”(角色:“ToDo”)– Sarah 可以访问待办事项。
- “Admin”(角色:“UserAdmin”)– Admin 可以访问用户和角色。
- “Manager”(角色:“ToDo”和“Manager”)– Manager 可以访问待办事项。
注意:用户和角色是任何人都可以访问的公共 jObjects。演示使用 UI 逻辑来阻止除“Admin”用户外的任何人显示用户和角色。
工作区包括:
- “
Public
”:此工作区包含类、用户、角色和空对象。“Public
”表示此工作区中的对象对所有用户都可访问。属性和方法可以装饰 Grant 构造函数。 - “To Do Workspace”:此工作区包含待办事项。“ToDo”和“Manager”角色已分配给“To Do Workspace”ACL。这意味着用户必须拥有“ToDo”或“Manager”角色才能访问待办事项。
注意:用户只能看到不可访问 jObjects 的 ID。要查看此项,请在登录事件中设置断点。登录 Guest。查看待办事项。ToDoItemExample 类中的每个属性都显示“工作流 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' 不可访问。”。
待办事项规范大纲
以下是文章中使用的待办事项的要求:
- 待办事项在待办事项工作区中由所有用户创建和查看。
- 用户必须拥有“ToDo”角色才能查看待办事项工作区。
- 经理拥有“Manager”角色。
- Guest 和 Admin 用户无法访问待办事项工作区。
- 当且仅当用户可以写入属性时,属性才显示为编辑字段。
- 用户可以为自己分配为待办事项的“AssignedTo”人。这允许该用户和“Manager”角色编辑待办事项,而其他人只能读取它。
- 拥有“ToDo”角色的用户可以创建待办事项。
- 有一个名称和描述。
- 有一个“Assigned To Comment”,只有“AssignedTo”人可以编辑。
- 有一个“Approver Comment”,只有“Manager”角色可以编辑。
- 跟踪处于“Started”、“Defined”、“Finished”、“Approved”和“Deleted”状态的待办事项。
- “Manager”应能执行与项目“AssignedTo”相同的任务。
- 只有“Manager”才能“Accept”或“Reject”一个“Finished”待办事项。
- 所有者可以“Delete”待办事项。
- 如果待办事项已“Completed”,则无法更改任何内容。
- 如果待办事项已“Deleted”,则无法更改任何内容。
构建待办事项
继承的属性包括:
- Class:jObject 所属的类对象。
- Workspace:jObject 所在的目录。
IsValid
:告知用户 jObject 有效。IsDeleted
:告知用户 jObject 已被删除。
访问相关属性包括:
CanDelete
– 状态和属性检查的组合,用于判断何时可以删除待办事项。
命令包括:
Definition_Complete
:允许被授权的用户将待办事项标记为已定义。Claim_Task
:允许被授权的用户将待办事项分配给登录用户。- Finish:允许被授权的用户将待办事项标记为“Finished”。
- Approve:“Manager”允许批准待办事项。
- Reject:“Manager”允许将待办事项返回到“Assigned”状态。
- Delete:允许被授权的用户删除 jObject。
状态包括:
- Started:这是指定的起始状态。
- Defined:待办事项定义完成。Finished:已分配用户将其设置为表示待办事项已完成。
- Approved:Manager 设置此状态。这是 jObject 的最终状态。
- Deleted:被授权的用户删除待办事项。
ToDoItemExample 的实例化会产生一个 jObject,其属性包括:
- Name = 添加的待办事项的名称。
- Description:由“Creator”添加的其他注释。
- Assigned To Comment:由“AssignTo”人添加的注释。
- Approver Comment:由“Manager”添加的注释。
注意:只有被授权的用户才能查看和/或设置这些属性的值。
用“UI”属性装饰的属性和方法易于确定该成员是否应显示在用户界面上。
Jazz 代码示例
/// <summary>
/// GroupToDo implements a simple to do item.
/// </summary>
[Jazz]
public class GroupToDo : jName
{
# region Constructor
/// <summary>
/// Instantiate a to do item
/// </summary>
/// <param name="nexus"></param>
/// <param name="name">name of the to do item</param>
public GroupToDo(ClientNexus nexus, string name) : base(null, name)
{
this.Initialize(nexus);
nexus.Bind(this);
}
/// <summary>
/// Initialize properties
/// </summary>
void Initialize(ClientNexus nexus)
{
if (!this.IsEmpty)
{
this.Creator = nexus.LoginProfile;
// Move item to To Do Workspace
jWorkspace ws = nexus.Cache.OfType<jWorkspace>().SingleOrDefault(s => s.Name == "To Do Workspace");
if (ws != null)
ws.Move(this);
}
}
# endregion
# region Properties
/// <summary>
/// Cast the ClientNexus
/// </summary>
private ClientNexus Nexus
{
get { return (ClientNexus)this.ClientNexus; }
}
/// <summary>
/// The person who creates the to do item and defines it
/// </summary>
publicjProfile Creator
{
get;
privateset;
}
/// <summary>
/// The person assigned to the to do item
/// </summary>
publicjProfile AssignedTo
{
get;
privateset;
}
/// <summary>
/// The name of the to do item
/// </summary>
publicoverride string Name
{
get { return base.Name; }
protectedset { base.Name = value; }
}
/// <summary>
/// The 'what' of the to do item
/// </summary>
publicstring Description
{
get;
[GrantStates("Started"), GrantProperty("Creator")]
set;
}
/// <summary>
/// Comments from the assigned to person
/// </summary>
publicstring AssignedTo_Comments
{
get;
[GrantStates("Assigned"), GrantProperty("AssignedTo")]
set;
}
publicstring Approver_Comments
{
get;
[GrantStates("Finished"), GrantRoles("Manager")]
set;
}
# endregion
# region Properties used in Grant Constructs
/// <summary>
/// Identifies if the logged in user is the Creator
/// </summary>
bool IsCreator
{
get { return this.Creator == this.Nexus.LoginProfile; }
}
/// <summary>
/// Identifies if the logged user is the person assgined to work the to do item
/// </summary>
bool IsMyItem
{
get
{ // if no one is assigned to the item, then return true
if (this.AssignedTo == this.Nexus.LoginProfile.Class.EmptyInstance)
returntrue;
// OR logged in user must be assigned to the item
returnthis.AssignedTo == this.Nexus.LoginProfile;
}
}
/// <summary>
/// Identifies when and whom can delete the to do item
/// </summary>
bool CanDelete
{
get
{
if (!this.IsValid)
returnfalse;
if (this.Nexus.LoginRoles.Contains("Manager"))
returnthis.CurrentState.Name != "Approved";
switch (this.CurrentState.Name)
{
case"Started":
if (this.Nexus.LoginProfile == this.Creator)
return true;
break;
case"Defined":
if (this.Nexus.LoginProfile == this.AssignedTo)
return true;
break;
}
returnfalse;
}
}
# endregion
# region Public Methods
/// <summary>
/// The creator can mark the definition complete
/// </summary>
[GrantStates("Started"), GrantProperty("IsCreator")]
publicvoid Definition_Complete()
{
this.EnterState("Defined");
}
/// <summary>
/// Someone chooses to own the to do item
/// </summary>
[GrantStates("Defined"), GrantProperty("IsMyItem")]
publicvoid Claim_Task()
{
this.EnterState("Assigned");
this.AssignedTo = this.Nexus.LoginProfile;
}
/// <summary>
/// The assigned person marks the to do item as finished
/// </summary>
[GrantStates("Assigned"), GrantProperty("IsMyItem")]
publicvoid Finish()
{
this.EnterState("Finished");
}
/// <summary>
/// The Manager approves the to do item
/// </summary>
[GrantStates("Finished"), GrantRoles("Manager")]
publicvoid Approve()
{
this.EnterState("Approved");
}
/// <summary>
/// The Manager rejects the finish action
/// </summary>
[GrantStates("Finished"), GrantRoles("Manager")]
publicvoid Reject()
{
this.EnterState("Assigned");
}
/// <summary>
/// Delete the to do item
/// </summary>
[GrantProperty("CanDelete")]
publicvoid Delete()
{
this.EnterState("Deleted");
}
# endregion
# region States
/// <summary>
/// Enters the Started state when the to do item is created
/// </summary>
[StartState]
void Started() { }
/// <summary>
/// Is Defined when the creator finished the description
/// </summary>
[State]
void Defined() { }
/// <summary>
/// Is assigned when someone chooses to own the to do item
/// </summary>
[State]
void Assigned() { }
/// <summary>
/// Is finished when the assigned to person marks it finished
/// </summary>
[State]
void Finished() { }
/// <summary>
/// Is approved when the manager marks the to do item approved
/// </summary>
[FinalState]
void Approved() { }
/// <summary>
/// Is deleted when someone deletes the to to item
/// </summary>
[FinalState]
void Deleted()
{
base._Delete();
}
# endregion
}
Windows 窗体项目
Windows 窗体项目包含 Jazz 代码文件和三个窗体:
- Form Jazz:用于显示导航和选定对象的驱动窗体。
- Form Name Prompt:提示输入命名对象所需的名称。
- Form Help:显示项目的帮助。
显示 jObject
图 2(如下)显示了一个“GroupToDo”jObject 的窗体格式。该窗体允许用户更改属性的内容并保存更改。
窗体的导航窗格显示窗体打开时创建的默认数据。没有数据库,因此该演示作为独立的单用户演示工作,更新 RAM 中的数据。默认数据包括:
Roles:角色用于 Grant 构造函数,分配给用户。
Users:每个用户都可以登录以获取独特的数据视图。只需点击工具栏中的“Login”按钮并选择一个用户。
GroupToDo:待办事项用于探索 Jazz 的安全方面。
单击角色、用户或 ToDoItem 以查看以窗体格式显示的 jObject。
要创建新的角色或用户,请以 Admin 身份登录,单击工具栏菜单中的“New”,然后选择您希望创建的项目。要创建新的待办事项,请以 George、Sarah 或 Manager 身份登录,单击工具栏菜单中的“New”,然后选择您希望创建的项目。
正式用例
用例名称:完成待办事项
参与者
- 待办事项创建者:创建待办事项的人。
- 待办事项分配者:分配给待办事项的人。
- 待办事项经理:拥有“Manager”角色的人。
- Guest:未与待办事项相关联的人。
触发器
- “George”想要完成“George 的待办事项”。
前置条件
- “George”创建了“George 的待办事项”。
- Visual Studio 已启动,JazzWindowsForm 项目被选为启动项目。
- 在 Visual Studio 中,按 F5 开始调试。窗体将打开。
后置条件
- “George 的待办事项”已完成。
正常流程 [A]
- 单击窗体顶部的“Login”并选择“George”作为登录名。
- “George”现在已登录(参见窗体右上角)。
- 在导航面板中选择“[Assigned] George 的待办事项”。
- 作为“Creator”,“George”仍然可以编辑“Description”。在“Description”字段中添加“Enter additional notes to complete the description”并单击“Save”。
- 现在定义已完成,“George”单击“Definition_Complete”。
- 待办事项将提升到“Defined”状态,窗体将显示待办事项的只读视图。
- 让我们将此项分配给 George。
- 单击“Claim_Task”。
- 在“AssignedTo_Comments”字段中添加注释——“This is the task that I have finished.”并单击“Save”。这将显示如图 1 所示的 jObject。
- 单击“Finish”按钮。
- 待办事项将提升到“Finished”状态,窗体将显示待办事项的只读视图。
- 单击窗体顶部的“Login As”并选择“Manager”作为登录名。
- Manager 可以输入注释并单击“Accept”或“Reject”。
- 在“Approver_Comments”中添加注释——“this is good”并单击“Save”。
- 单击“Accept”按钮。待办事项现在“Completed”,窗体恢复到数据的只读视图。
备选流程 [A]
3A1:待办事项的定义未完成。
3. 单击窗体顶部的“Login As”并选择“Sarah”作为登录名。
- 当“Sarah”登录时,您将看到待办事项的只读视图。
4. 单击窗体顶部的“Login As”并选择“Manager”作为登录名。
- 当“Manager”登录时,您将看到待办事项的只读视图。
5. 单击窗体顶部的“Login As”并选择“Guest”作为登录名。
- “Guest”无法访问待办事项。
5A1:待办事项的定义已完成。
5. 单击窗体顶部的“Login As”并选择“Sarah”作为登录名。
- 当“Sarah”登录时,“Sarah”会看到“Claim_Task”按钮,并可以领取该待办事项。
6. 单击窗体顶部的“Login As”并选择“Manager”作为登录名。
- 当“Manager”登录时,“Manager”会看到“Claim_Task”按钮,并可以领取该待办事项。
8A1:“George”已完成待办事项。
8. 单击窗体顶部的“Login As”并选择“Sarah”作为登录名。- 当“Sarah”登录时,您将看到待办事项的只读视图。
用例名称:创建待办事项
参与者
- 待办事项创建者:创建待办事项的人。
触发器
- “George”想要创建“一个新待办事项”。
前置条件
- Visual Studio 已启动,JazzWindowsForm 项目被选为启动项目。
- 在 Visual Studio 中,按 F5 开始调试。窗体将打开。
后置条件
- “一个新待办事项”已创建。
正常流程 [B]
- 单击窗体顶部的“New”并选择“Create_To_Do_Item”。
- 将弹出一个提示您输入待办事项名称的窗体。(名称显示在导航窗格中。)
- 输入“A new to do item”并单击“Ok”或按 Enter 键。
- 一个新的待办事项已添加到导航窗格中。
- 选择刚刚添加的“A new to do item”。
- 它显示在编辑器窗格中。请注意,创建者与登录用户名匹配。
显示架构
图 3(如下)显示了一个 GroupToDo jObject 架构。
数据架构视图显示待办事项在其当前状态下的属性和方法。这对于获取已登录用户看到的 jObject 的快照非常有用。
属性显示属性名称、值、“Can Read”标志、“CanWrite”以及 States、Roles 和 Properties 的 Grant 构造函数。请注意,setter 的 Grant 构造函数前缀为“[Setter]”。
方法显示方法名称、“IsExecutable”标志以及 States、Roles 和 Properties 的 Grant 构造函数。
演示中使用的代码片段
该演示基于编程接口构建。让我们探索 Jazz API。
获取 jObject 的成员
对于编写安全应用程序的开发人员来说,并不总是清楚用户有哪些属性和方法可用。Jazz 包含一个 jPropertyInfo 和 jMethodInfo,它们镜像 PropertyInfo 和 MethodInfo。这两个类提供的属性考虑了代码中使用的 Grant 构造函数。
要获取 jObject 中使用的所有属性,请调用 GetWrappedMembers()
并使用 Linq 的‘OfType<jPropertyInfo>()
’方法进行筛选。
通过将 jPropertyInfo
替换为 jMethodInfo
来检索方法。
IEnumerable<jPropertyInfo> properties = obj.GetWrappedMembers().OfType<jPropertyInfo>();
要仅获取 jObject 子类的成员,请使用以下代码。区别在于 GetMembers 如何过滤 jObject 的成员。注意“ClassMethods
”是一个扩展方法。
IEnumerable<jMethodInfo> methods = obj.ClassMethods();
读取属性值
使用 jPropertyInfo
检查属性是否可以读取。虽然 PropertyInfo.CanRead 指示属性是否有 getter,但 jPropertyInfo.CanRead
会考虑 getter 上的 Grant 构造函数。
jPropertyInfo pi = properties.Single(p => p.Name == "Subject");
...if (pi.CanRead) {
object value = pi.Read(this.obj);
其中 pi 是属性的 jPropertyInfo
。
写入属性值
使用 jPropertyInfo
检查属性是否可以写入以及如何写入。虽然 PropertyInfo.CanWrite
指示属性是否有 setter,但 jPropertyInfo.CanWrite
会考虑 setter 上的 Grant 构造函数。
jPropertyInfo pi = properties.Single(p => p.Name == "Subject");
...if (pi.CanWrite)
pi.Write(this.obj, value);
其中 pi 是属性的 jPropertyInfo
。
执行命令
使用 jMethodInfo
对象检查方法是否可执行。
注意:检查正确签名的代码未显示。
jMethodInfo mi = methods.FirstOrDefault(m => m.Name == command);
if (mi.IsExecutable)
mi.Execute(this.obj);
其中 command 是要调用的方法的名称。
创建 jObject
jObject 的实例化与创建 .NET 对象完全相同。通过将 nexus 传递到构造函数中,jObject 会被绑定到 nexus 并准备好进行安全保护。
GroupToDo.Create(nexus);
该演示包含一个模式,用于向被授权的用户提供对用户、角色和待办事项实例化的访问。该类是 Creator,包含每个实例化的方法。例如,要创建待办事项,用户必须拥有“ToDo
”角色。
[UI, GrantRoles("ToDo")]
publicGroupToDo Create_To_Do_Item(string subject)
{
returnGroupToDo.Create(this.nexus, subject);
}
关注点
当使用像“Grant 构造函数”这样强大的技术时,工作流可以变得简单。