摘要
AquaLogic User Interaction Development Kit (IDK)(以前称为Enterprise Development Kit)提供了对许多AquaLogic User Interaction Collaboration服务的编程式访问,比如项目管理、文档储存库、分话题(threaded)讨论、任务列表和任务分配以及订阅/通知支持等。本文描述了如何使用IDK Collaboration API将协作服务和功能嵌入到Web应用程序中。
简介
AquaLogic User Interaction Collaboration为工作组和业务单元提供了一个运行平台,它是AquaLogic User Interaction的组成部分。请参见由Jennifer Shipman所撰写的AquaLogic User Interaction开发入门,详细了解该组件在整个AquaLogic User Interaction套件中的作用。ALUI Collaboration提供了大量协作特性,从而带来如下的显著优点:
- 提高了团队之间的生产力。
- 提供了一个用于共享信息的集中区域。
- 使内部团队与外部团队之间的通信变得非常轻松。
- 极大地改善了项目管理。
ALUI Collaboration在员工、客户和合作伙伴之间架起了一座通信桥梁;可以用它来跨多个不同团队和领域(比如工程、销售、营销和客户支持)进行项目管理。
为了使协作服务更可用以便进行定制和嵌入,向开发人员公开了一个远程API。该IDK远程API提供了一种调入ALUI Collaboration的面向对象方式,暴露了Collaboration Server的元素,并提供了一种在Web应用程序中嵌入和定制化协作服务的轻松方式。
协作项目
协作功能集中于一个项目上。项目是一个用于存放相关协作信息的集中区域。它是其它所有功能的容器,比如:
- 文档储存库和管理
- 分话题讨论
- 日程事件,比如会议和里程碑
- 任务列表和任务分配
- 在一个项目中订阅其它所有对象的能力
- 通告和通知
该远程API不仅提供了对Collaboration对象的访问,它还支持查询和管理对象的安全性。
本文余下的部分将演示如何使用该远程API将协作服务嵌入到应用程序中。
构建作业和新闻组Portlet
下面的例子将带您遍历使用Collaboration Task List和Discussion API为大学课程创建一个作业和新闻组portlet的整个过程。本文主要关注如何使用IDK Collaboration API编写portlet,不会对如何使用.NET Web窗体进行详细说明。
背景信息
大多数大学课程都有需要调查、团队协作、各人之间进行通信的包含多个部分的作业。学生们集体讨论新的想法、问题以及相互通信的最常见的方式就是使用新闻组。本例将展示如何使用Collaboration Task List和Discussion API的特性构建一个作业/新闻组portlet。
我们为该portlet设计了两个主视图:Assignment View(作业视图)用于显示作业中的所有问题,Newsgroup View(新闻组视图)用于显示相应的讨论。下面我们来详细介绍这两个视图。
Assignment View
Assignment View显示了作业中的所有问题。作业中的每个问题都对应于一个任务,而每个问题的每一子部分都对应于一个子任务,直接在任务下面以严格的缩进形式显示,如图1所示。

图1: 包含任务和子任务的Assignment View
下拉选项允许用户对任务进行操作,包括:
- 选中一个或多个任务,查看相应的新闻组(讨论)
- 创建一个新任务
- 选中一个任务,为其创建一个新的子任务
Newsgroup View
Newsgroup View显示了前一界面中选中的任务的所有相应讨论。当创建一个任务后,就使用相同的名称创建了相应的讨论。当学生想要检查与作业中特定任务相关的信息时,就可以选中Assignment View中的该任务,选择View Discussions,然后利用产生的Newsgroup View阅读所有相应的讨论。图2显示了Newsgroup View。

图2: Newsgroup View(单击图像查看大图)
该视图的选项包括:
- 选中一个讨论,创建一个新帖子
- 选中一则消息,为其创建回复
- 返回到Assignment View
该portlet中的其它功能包括创建新任务、子任务或新帖子,或者贴出回复。现在我们来看一下代码。
Portlet代码:使用IDK Collaboration API编写
现在我们来看一些该portlet的代码。首先需要进行以下安装:Plumtree Portal 5.0.2、EDK 5.1、Collaboration 4.0、.NET Framework 1.1和.NET Web Controls 2.1。这里所列出的版本是最低要求,也可以使用更高版本。在本例中我们将使用C#。
获取协作工厂和管理器
要使用Collaboration远程API进行开发,需要建立与门户的远程会话,并获取IRemoteSesson。然后可以从IRemoteSession获取ICollaborationFactory,后者是一个用于获取各种Collaboration对象管理器的工厂类。管理器用于对不同种类的Collaboration对象进行操纵和管理。
下面的代码设置了portlet上下文,从上下文获取IRemoteSession,并对需要在此portlet中运行的各种Collaboration管理器进行初始化:
// Retrieve the Portlet Context and Remote Session objects
IPortletContext portletContext =
PortletContextFactory.CreatePortletContext(Request, Response);
remoteSession = portletContext.GetRemotePortalSession();
// Initialize the different Collaboration managers
collabFactory = remoteSession.GetCollaborationFactory();
projectManager = collabFactory.GetProjectManager();
tasklistManager = collabFactory.GetTaskListManager();
discussionManager = collabFactory.GetDiscussionManager();
// Helper to retrieve an existing project or to populate a project
// with sample tasks and their corresponding discussions
sampleProject = GetSampleProject();
// Initialize the DataTable for the Task DataGrid
TaskTableData = new DataTable();
// Method to populate the Task Tree to generate
// the Assignment Page
CreateTree(sampleProject);
获取协作项目
如前所述,所有的Collaboration项目都存在于项目的上下文中,因此,要使用任何Collaboration特性,首先需要创建一个项目。在本例中,我使用了一个示例项目PROJECT_NAME来容纳所有将创建的Collaboration对象。
下面的GetSampleProject()方法执行下面两种操作中的一种:创建一个新项目,并使用适当的内容(比如任务列表或任务及相应的讨论)对其进行填充;或者检索一个现有项目。我们希望每次只创建一个新项目,此后重用该项目。
下面的代码说明了如何使用ProjectFilter查询项目。项目过滤器允许您随意设置一个字符串作为搜索字符串,这样就可以使用包含搜索字符串的名称查询项目。如果不提供搜索字符串,那么会返回所有的项目。所以一般而言,总是应该设置一个搜索字符串以限定搜索结果。
private IProject GetSampleProject()
{
// First try to retrieve an existing project with the right name
IProjectFilter projectFilter =
projectManager.CreateProjectFilter();
projectFilter.NameSearchText = PROJECT_NAME;
IProject[] sampleProjects =
projectManager.QueryProjects(projectFilter);
IProject sampleProject = null;
// Existing project(s) found, use the first one from the array.
if (sampleProjects.Length > 0) {
for (int i = 0; i < sampleProjects.Length; i++) {
if (PROJECT_NAME.Equals(sampleProjects[i].Name)) {
sampleProject = sampleProjects[i];
break;
}
}
}
// Otherwise create a new project and populate with content
// (for example, task list, tasks, and their corresponding discussions)
if (sampleProject == null)
sampleProject = CreateAndPopulateSampleProject();
return sampleProject;
}
创建新项目
如果没有找到名为PROJECT_NAME的项目,代码就会使用工厂方法IProjectManager.CreateProject()创建一个新项目。使用该方法创建的项目还没有保存。可以在使用IProjectManager.Store()保存它之前,在新项目上设置附加属性:
private IProject CreateAndPopulateSampleProject() {
IProject sampleProject =
projectManager.CreateProject(PROJECT_NAME, PROJECT_DESCRIPTION);
sampleProject.SetStatus(ProjectStatuses.NotStarted);
// Set additional properties on the project here
sampleProject.Store();
}
填充任务树
项目一旦保存,就可以在该项目中创建其它协作对象。对于本例,作业中的每个问题都可以被表示为独立的任务,而问题中的每个子部分都可以被表示为独立的子任务。这样就允许我们构建一个树形结构,以便在任务和子任务创建之后就可以查看它们,如图1所示。
下面的三个帮助器方法分别遍历了创建一个任务列表、一个顶级任务和一个子任务以及为其设置属性并进行保存的过程和代码。
1.创建任务列表
任务列表是一组任务及其子任务的组合。下面的代码展示了如何创建任务列表。ITaskListManager.CreateTaskList()方法创建了一个还没有保存的新任务列表。可以在使用Store()方法保存它之前对其设置附加属性:
public ITaskList CreateAndStoreTaskList(IProject project, String name, String description)
{
ITaskList tasklist = tasklistManager.CreateTaskList(project, name, description);
//Additional tasklist properties can be set before calling
//Store(), such as security information.
tasklist.Store();
return tasklist;
}
2. 创建顶级任务
任务就像一个被指派给一个用户(不一定是创建该任务的用户)的“To Do”项。一个任务可以有一个或多个子任务。每个子任务还可以有子任务。可以使用API中所提供的两个任务关联方法轻松地从不同级别访问任务:
- ITask.GetSubTasks()——检索一个任务的所有子任务
- ITask.GetParentTask()——检索一个任务的父任务
下面的代码展示了如何使用notes创建顶级任务。在创建任务后就必须设置一个启动时间和结束时间。还应该在使用ITask.Store()方法保存它之前设置该任务的附加属性:
public ITask CreateAndStoreTopLevelTask(ITaskList tasklist,
String name, String description, DateTime startTime,
DateTime endTime, String notes) {
ITask task = tasklist.CreateTask(name, description, startTime, endTime);
//Additional task properties can be set before calling Store(),
//such as notes, risk, status, and security information.
task.Notes = notes;
task.Store();
return task;
}
3. 创建子任务
子任务本身是一个从现有ITask创建的ITask。ITask.CreateSubTask()方法用于创建一个子任务。像顶级任务一样,子任务创建时也包括启动时间和结束时间,还可以包括ITask的其它可选属性。下面的代码示例展示了如何从一个现有任务创建子任务:
public ITask CreateAndStoreSubTask(ITask parentTask, String name, String description, DateTime startTime, DateTime endTime, String notes) {
//CreateSubTask() will create a persisted task, so Store()
//does not need to be called to persist the task unless
// additional properties are set.
ITask subtask = parentTask.CreateSubTask(name, description, startTime, endTime);
subtask.Notes = notes;
subtask.Store();
return subtask;
}
呈现任务树
现在我们转向呈现任务列表,创建一些如图1中所示的东西。在创建了所有的任务和子任务之后,还需要两步来呈现任务树:
- 在一个任务列表中检索所有的任务和子任务。
- 设计显示任务树的外观。
任务树是使用DataGrid呈现的,它允许对数据的显示方式实施更多控制(参见ASP.NET文档,了解如何使用DataGrid控件)。
<asp:DataGrid id="TasksDataGrid" runat="server" Width="100%"
BorderWidth="0px" ShowHeader="False"/>
1. 创建任务树显示
CreateTree()方法用于创建任务树显示。它使用三个列来设置Data Grid:一个复选列、一个任务名称列和一个不可见的id列。然后它调用ITaskListManager.GetTaskLists()方法来检索所有的任务列表,并对每个任务列表调用呈现方法。
private void CreateTree(IProject project) {
TaskTableData.Columns.Add(new DataColumn("Name", typeof(string)));
TaskTableData.Columns.Add(new DataColumn("ID", typeof(int)));
ITaskList[] tasklists = GetTaskLists(sampleProject);
for (int i = 0; i < tasklists.Length; i++) {
AppendTasklist(tasklists[i]);
}
TasksDataGrid.Columns.Add(new CheckBoxColumn());
TasksDataGrid.DataSource = TaskTableData;
TasksDataGrid.DataBind();
}
2. 检索任务列表和任务
ITaskListManager.GetTaskLists()方法检索一个项目中的所有任务列表。可对任务列表过滤器和任务过滤器设置附加选项来缩小检索范围,比如Task Completion Type(用于返回挂起任务、已完成任务或过期任务)和Assigned-to Type(用于返回分配的任务)。
public ITaskList[] GetTaskLists(IProject project)
{
ITaskListFilter filter = tasklistManager.CreateTaskListFilter();
ITaskList[] children = tasklistManager.QueryTaskLists(project, filter);
return children;
}
类似地,ITaskList.GetTasks()方法检索一个任务列表中的所有任务:
public ITask[] GetTasks(ITaskList tasklist)
{
ITaskFilter filter = tasklistManager.CreateTaskFilter();
ITask[] tasks = tasklistManager.QueryTasks(tasklist, filter);
return tasks;
}
3. 显示任务条目
在本例中,我将使用单独的方法来呈现每个任务列表和任务。
对于每个任务列表,我使用下面展示的AppendTaskList()方法来查找它所有的任务和子任务。然后对每个任务调用AppendTask()方法,以便将每个任务呈现为Task DataGrid中的一行:
public void AppendTasklist(ITaskList tasklist) {
ITask[] childTasks = GetTasks(tasklist);
for(int i = 0; i < childTasks.Length; i++)
AppendTask(childTasks[i]);
}
AppendTask()方法用于呈现单个任务。每个任务由DataGrid中的一个DataRow表示。NAME列包含了任务名称和任务详情链接,ID列包含了任务ID。信息绑定是通过以下两步完成的:
- 首先,通过调用DataTable.Rows.Add(),将DataRow信息绑定到DataTable。
- 接下来,在添加了所有任务之后,设置DataGrid.DataSource=DataTable,随后调用DataGrid.Bind(),如上述CreateTree()方法的最后两行所示。
4. 使用对象详情URL
远程API提供了对所有对象的详情URL的检索。当需要链接回Collaboration UI查看对象的更多详情时,对象详情URL就显得非常方便。例如,如果希望在更新特定对象时向用户发送通知,就可以在电子邮件通知中使用详情URL。在下面的AppendTask()实现中,我在每个任务的末尾追加了其详情URL作为点进(click-through)链接。
public void AppendTask(ITask task) {
DataRow taskRow = TaskTableData.NewRow();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < task.Level; i++) {
//For look & feel
//indent subtasks according to their levels
sb.Append(" ");
}
sb.Append(task.Name);
sb.Append("<a href=\"" + task.DetailsURL + "\">");
if (task.Level == 0)
sb.Append(" [view task details]");
else
sb.Append(" [view subtask details]");
sb.Append("</a>");
sb.Append("<br/>");
taskRow["Name"] = sb.ToString();
taskRow["ID"] = task.ID;
TaskTableData.Rows.Add(taskRow);
}
查找匹配的讨论:查询讨论
每个作业问题(顶级任务)都是与一个收集与该问题相关的想法和对话的讨论一起创建的。下一步是要找出用户所选择的任务并检索对应的讨论。
使用.NET CheckBox和DataRow控件,就可以确定用户选择了哪个问题(顶级任务)。请参见ASP.NET文档,了解如何使用CheckBox和DataRow控件。
用户使用复选框标记出一个问题并选择了希望执行的操作之后,接下来就是要找出与该任务对应的讨论了。在EDK 5.1中,不支持用于查询讨论的文本搜索功能,因此这里使用字符串比较来查找具有匹配名称的讨论:
private IDiscussion GetMatchingDiscussion(String taskName) {
IDiscussion matchingDiscussion = null;
IDiscussionFilter discussionFilter =
discussionManager.CreateDiscussionFilter();
IDiscussion[] discussions =
discussionManager.QueryDiscussions(sampleProject, discussionFilter);
for (int i = 0; i < discussions.Length; i++) {
if (taskName.Equals(discussions[i].Name)) {
matchingDiscussion = discussions[i];
break;
}
}
return matchingDiscussion;
}
该方法可被用于为选中的任务检索匹配讨论。接下来我们要构造讨论树。
呈现讨论树
要呈现讨论树,我们可以再次使用呈现任务树的方法。主要的步骤是创建一个具有复选框、消息名称和ID等列的DataGrid。详情请参考上面对CreateTree()方法的解释。
除了CreateTree()方法,其他要重用的方法包括AppendTaskList()和AppendTask()。AppendDiscussion()和AppendMessage()方法可被定制以完成以下功能:
- 创建一个讨论/消息DataRow。
- 使用相应的详情填充列,比如讨论/消息名称、详情URL和ID。
- 将行连接到Discussion DataTable。
下面我们来看另一种更有趣的Discussion API方法,该方法是构造和呈现创建Newsgroup View portlet所需的讨论树所必需的。
检索消息
可以使用重载方法IDiscussionManager.QueryDiscussionMessages()对一个讨论或项目中的所有消息进行搜索。可对消息过滤器设置附加选项来缩小检索范围,比如Status Type(用于返回所有的、已批准的或未批准的消息)和Moderator Type(用于返回一个中级讨论中的消息)。
public IDiscussionMessage[] GetMessages(IDiscussion discussion) {
IDiscussionMessageFilter messageFilter =
discussionManager.CreateDiscussionMessageFilter();
DiscussionMessageQueryOrder queryOrder =
new DiscussionMessageQueryOrder(DiscussionMessageAttributes.Modified, true);
IDiscussionMessage[] messages =
discussionManager.QueryDiscussionMessages(discussion, messageFilter);
return messages;
}
创建讨论
当在作业页面中创建一个新任务时,就会为该任务创建一个对应的讨论。下面的帮助器方法可用于此目的,它将讨论的名称设置为与任务名称相同:
public IDiscussion CreateAndStoreDiscussion(IProject project, String name, String description) {
IDiscussion discussion = discussionManager.CreateDiscussion(project, name, description);
//Additional task properties can be set before calling Store(),
//such as notes, risk, status, and security information.
discussion.Store();
return discussion;
}
创建讨论消息
下面的代码展示了如何从一个现有讨论创建一个顶级讨论消息(即,一个主题消息)。IDiscussion.CreateDiscussionMessage()方法将创建一个新的讨论消息,可以在使用IDiscussionMessage.Store()保存消息对象之前对消息设置可选字段:
public static IDiscussionMessage CreateAndStoreMessage(IDiscussion discussion, String subject, String body) {
IDiscussionMessage message = discussion.CreateDiscussionMessage(subject, body);
//Optional discussion message properties
message.Approved = true;
message.Description = "Option description for discussion message";
message.Store();
return message;
}
创建对讨论消息的回复
下面的代码展示了如何创建回复消息。回复消息也是一个IDiscussionMessage,但是它不是从IDiscussion对象而是从一个现有IDiscussionMessage对象创建的。
另一件要注意的事情是,与创建顶级消息不同的是,创建回复消息的调用将创建一个持久化消息,即,在调用创建后,不要求调用Store()方法来保存消息,除非设置了附加的属性,如下所示:
public static IDiscussionMessage CreateAndStoreReply(IDiscussionMessage parentMessage, String subject, String body) {
IDiscussionMessage reply = parentMessage.CreateDiscussionReplyMessage(subject, body);
//Store() needs to be called to persist additional properties
//for a reply message
reply.Description = "Optional description for reply message.";
reply.Approved = true;
reply.Store();
return reply;
}
结束语
IDK远程API提供了一组丰富的功能以便与ALI Application套件中的各种产品和服务进行远程交互。具体来说,Collaboration API主要关注使用ALUI Collaboration Service公开信息收集和共享方面的协作特性。可以使用该API在应用程序中嵌入一个或多个此类特性,比如讨论、任务列表、文档和订阅,从而扩展应用程序。
使用IDK远程API的其它优点包括可配置的日志记录支持(请参见IDK Logging,了解更多信息)以及与其他ALUI应用程序(比如门户、Knowledge Directory服务、Publisher服务和Search服务)的轻松集成。 |