InfoPath+Workflow+Moss (二)

要点二:WF和INFOPATH的数据处理
在做InfoPath,WF以及Moss关联的过程中,你必须清楚的了解三者之间是如何配合的,并且了解他们之间是怎么通信,怎么传递,来完成你所需要的流程配置,流程发起,以及流程task处理的问题。在开发Moss里面的InfoPath工作流中,你会关注到如何神奇创建task,响应task,以及处理task,最后清理task,这些都是与Moss本身重扩展了WF以后,新增了更加适合于Moss环境下的流程开发活动密不可分的,主要命名空间为microsoft.sharepoint.WorkflowActions。
问题一,怎么读入读出数据
在INFOPATH的表单当中,如何传入你自己数据呢,或者说,怎样从流程里面传递数据到表单里面去,这里有几种解决办法。
第一,是采用ItemMetadata数据绑定,
第二,是采用DOM模型直接对INFOPATH表单进行操作.
第三,是采用第三者中介来传输数据,比如说临时的列表库,或者临时的sql表,临时的文件,都是可以的。
下面说第一种方法:借助于XML数据源定义文件,INFOPATH可以将自己的数据绑定到数据源里面去,而数据源文件里面提供的只是数据的名字而已,并且,这个数据文件里面的变量的命名,必须带上OWS,这样才能被INFOPATH所识别,并且为MOSS里面的流程所用,,例子如下:
,
在INFOAPTH的表单设计里面选择添加了数据源以后,就可以在辅助数据源里面找到你所定义的变量了,并且在表单的Load以后,都可以从对应的域里面把你绑定的值读出来,因为在绑定的时候,数据已经进去了。
第二种方法比较特殊,实际上对于创建出来的infopath表单,就是一个xml文件而已,注意,这里不是说的xsn表单,xsn表单里面所包含的东西,远比这些要复杂得多,而创建出来的INFOPATH表单,只是一个存储数据的介质而已,所以这里采用DOM模型的话,完全可以对里面的数据进行读和写。

XmlDocument xmldot = new XmlDocument();
Stream strem = newFormTemplateFile.OpenBinaryStream();
xmldot.Load(strem);
strem.Close();
XmlNameTable xmlNameTab = (XmlNameTable)xmldot.NameTable;
XmlNamespaceManager xmldotNs = new XmlNamespaceManager(xmlNameTab);
xmldotNs.AddNamespace("my", "http://schemas.microsoft.com/office/infopath/2003/myXSD/2008-12-15T08:42:02");

//获取相关的节点
XPathNavigator xpdotRoot = xmldot.CreateNavigator();

//给节点DispatchTitle赋值和是否提交的节点的值
XPathNavigator xpDispatchTitle = xpdotRoot.SelectSingleNode("my:HQContentType/my:DispatchTitleNode/my:DispatchTitle", xmldotNs);
XPathNavigator xpIsSubmit = xpdotRoot.SelectSingleNode("my:HQContentType/my:IsSubmit", xmldotNs);
XPathNavigator xpCanModify = xpdotRoot.SelectSingleNode("my:HQContentType/my:CanModify", xmldotNs);
XPathNavigator xpIsExist = xpdotRoot.SelectSingleNode("my:HQContentType/my:StateGroup/my:IsExist", xmldotNs);

第三种方法就不用多说了,地球人都会。
问题二,怎么关联表单和流程,
对于刚开始开发的人来说,都会有一个疑问,Moss是怎么样知道你在什么时候task需要打开哪一个task表单的呢?其实这个问题并不难,关键在于以下几个点,一个是你的流程部署的时候的workflow的xml文件描述里面,这个文件是用来在feature激活的时候,告诉moss你的这个东西是什么并且你的这个东西是长什么样子的。
关键的是在workflow节下面的metadata节,你会看到里面有你的每一步的Task0_FormURN对应的表单的编号,这个编号你可以在你的INFOPATH设计,属性里面修改。这个编号对于每一个表单来说都是唯一的。这里附带说一下,后面的,ExtendedStatusColumnValues节,是用来扩充流程的状态的,在具体的流程代码里面操作如下:
在create_task活动里面,在创建任务的时候,就可以借助任务的属性TaskType来设定需要调用哪一个表单,这个值是一个序号,根据你在xml里面定义的序列来定义的从0开始。这里还要补充的就是,利用任务的ExtendedProperties就可以将你前面绑定了的值给进去了,注意,这里的值是没有ows_的。

Task2_Id = Guid.NewGuid();
Task2_Properties.TaskType = 1;

//给 Task2 的 ItemMetadata 的 历史记录 描述 赋值
Task2_Properties.ExtendedProperties[“HistoryRecord”] = string.IsNullOrEmpty(HistoryRecords) ? string.Empty : HistoryRecords;
Task2_Properties.ExtendedProperties[“Description”] = Task1_Properties.ExtendedProperties[“SubmitDescription1”] == null ? string.Empty : Task1_Properties.ExtendedProperties[“SubmitDescription1”];
Task2_Properties.ExtendedProperties[“ItemLinkUrl”] = string.IsNullOrEmpty(strItemLinkUrl) ? string.Empty : strItemLinkUrl;

如下:
urn:schemas-microsoft-com:office:infopath:Task1-headquarters:-myXSD-2008-12-02T12-38-25
urn:schemas-microsoft-com:office:infopath:Task2-headquarters:-myXSD-2008-12-03T02-24-54
urn:schemas-microsoft-com:office:infopath:Task3-headquarters:-myXSD-2008-12-05T03-10-50
urn:schemas-microsoft-com:office:infopath:Task4-headquarters:-myXSD-2008-12-05T05-41-56
urn:schemas-microsoft-com:office:infopath:Task5-headquarters:-myXSD-2008-12-15T08-42-02

问题三,怎么对Init ,asso,task类型的表单进行操作。
首先要解决的是Init表单里面的数据,asso表单类里面的数据和流程交互的问题,这两类单子比较特殊,Init表单是流程在启动时候初始输入进去的预定义,为流程的开始运行提供额外的初始化数据,asso表单为流程提供全局的辅助变量,只要能够得到当前流程的相关信息,就能取到对应的辅助设置数据。但是他们之间存在有不同。
在流程中,你可以借助于流程this.workflowProperties.InitiationData,或者this.workflowProperties. AssociationData来读取其中的内容,这两个串读出来的话,都是xml形式的,可以用DOM对象来进行处理,但是这里如果仔细的人会发现,在InitiationData其中的数据,会包含AssociationData里面的数据,简而言之,就是InitiationData>=AssociationData,当然,这只是对于编码当中的情况来说的,在设计和规划的时候,两者不能等同或者包含。在理解上是合理的,相当于,在流程依据模板创建的时候,会自动的将模板里面设定的辅助数据加入到流程的初始化数据当中去。而流程的初始化数据就不仅仅是assoc数据。
在表单中,可以借助于列表的绑定相关流程,来取到流程设定的asso的数据,但是要取到流程当中的init数据,就必须采用传值绑定的方法,具体的可以见问题一。下面给一段如何在表单里面读取asso数据的方法,大致的思路是,找到列表对应的asso表单,然后用dom读入表单,之后采用dom来读取对应节的数据,这里还提供另外一种方式。这里在读取的时候采用的是dom对象来对数据进行处理,同样可以采用一种特殊的方式。大家应该都记得,可以把infopath表单的schema保存下来,然后再把schema表单序列化为类对象,这里可以参考柴同学的step by step,英文原版的地址应该在这里,大家可以找找。

 
// 获取要启动工作流的  AssociationData ,并变成  XmlDocument
 
                    XmlDocument xdot = new XmlDocument();
                   foreach (SPWorkflowAssociation wfAssoc in list.WorkflowAssociations)
                    {
                        // search workflowassociation by name
                        if ((wfAssoc.BaseTemplate.Name.ToString().Equals("审批工作流")))
                        {
                            xdot.LoadXml(wfAssoc.AssociationData);
                            break;
                        }
                    }
                    //给 followApproverUsers 绑定的 重复项  添加项和值
                    XmlNameTable ntable = (XmlNameTable)xdot.NameTable;
                    XmlNamespaceManager Outns = new XmlNamespaceManager(ntable);
                    Outns.AddNamespace("my", "http://schemas.microsoft.com/office/infopath/2003/myXSD/2008-12-03T01:36:02");
                    XPathNavigator xdotRoot = xdot.CreateNavigator();
                    XPathNavigator xpdotSecondApprovalUsers = xdotRoot.SelectSingleNode("my:Association/my:SecondApprovalUsers", Outns);
                    if (xpdotSecondApprovalUsers.MoveToChild(XPathNodeType.Element))
                    {
                        do
                        {
                            if (!string.IsNullOrEmpty(xpdotSecondApprovalUsers.SelectSingleNode("my:DisplayName", Outns).Value) && 
                                 !string.IsNullOrEmpty(xpdotSecondApprovalUsers.SelectSingleNode("my:AccountId", Outns).Value) && 
                                   !xpdotSecondApprovalUsers.SelectSingleNode("my:AccountId", Outns).Value.ToLower().Contains("system"))
                            {
                                xpuser.SelectSingleNode("my:DisplayName", ns).SetValue(xpdotSecondApprovalUsers.SelectSingleNode("my:DisplayName", Outns).Value);

                                xpuser.SelectSingleNode("my:AccountId", ns).SetValue(xpdotSecondApprovalUsers.SelectSingleNode("my:AccountId", Outns).Value);

                                xpuser.SelectSingleNode("my:AccountType", ns).SetValue(xpdotSecondApprovalUsers.SelectSingleNode("my:AccountType", Outns).Value);

                                xpfollowApproverUsers.AppendChild(xpuser);
                            }
                        } while (xpdotSecondApprovalUsers.MoveToNext(XPathNodeType.Element));
                    }

最后就是task表单里面的数据了,前面已经说过task表单的数据传递了,这里要说的是一些基本的操作,借助于任务的ExtendedProperties,可以很方便的在流程当中取到所有在表单里面的数据,当然,首先你要指导表单里面的对应域的名称,前面已经说过,就是你的绑定的ItemMetadata.xml

问题四,在外部操作和读取InfoPath表单。
于本质来说,InfoPath表单本身就是一个xml的文件,但是其有别于普通的xml文件,他有其自己的命名空间,以及相关xsd定义,在设计阶段,大家可以看到xsn文件,如果打成一个压缩包的话,大家可以看出里面的一堆的内容,具体包里面的内容在这里就不多说了,我只需要我们自己的哪个空白的xml文件,带有架构,却没有数据,这样我们就可以把这个文件读取出来,然后打成流来进行处理了,代码如下。

XmlDocument xmldot = new XmlDocument();

Stream strem = newFormTemplateFile.OpenBinaryStream();

xmldot.Load(strem);

strem.Close();

XmlNameTable xmlNameTab = (XmlNameTable)xmldot.NameTable;

XmlNamespaceManager xmldotNs = new XmlNamespaceManager(xmlNameTab);

xmldotNs.AddNamespace("my", "http://schemas.microsoft.com/office/infopath/2003/myXSD/2008-12-15T08:42:02");
 
XPathNavigator xpdotRoot = xmldot.CreateNavigator();

这里有两个需要注意的地方,
(1),如果你要想在浏览器当中打开infopath表单,而这个表单不是通过你的鼠标点击创建的,而是通过你的代码创建的,你会发现你无法打开infopath表单,那么你需要知道以下的内容,任何带编码的infoath表单,在moss的文档库中解析,个人认为大致过程是这样的,在你的库中所保存的东西,不过是一个xml的文件,除了xml什么都没有,通过比较,只知道需要这样两个东西。
item[“模板链接”] = 模板库当中的连接地址后缀为.xsn;
item[“HTML 文件链接”] = “InfoPath.Document.2”;
实际上这里infopath的东西还是通过发布到了moss站点里面的模板来进行处理的,因为表单的后台逻辑的编码dll,最终还是在发布的时候打包到了xsn的表单当中,所以最终还是调用了模板来加载处理你的infopath表单。
(2),如果你的infopath表单打开的时候采用了超级连接,虽然连接到了你的xml数据文件,但是却无法在浏览器里面打开,这里需要添加打开的参数,?openin=browser,这样浏览器里面就能加载你的infopath表单了。
问题五,如何手工启动流程。
启动流程,一直是在moss里面比较麻烦的问题,因为流程的执行,在moss当中跨越了站点,跨越了库,所以原有的绑定列表的触发方式变得力不从心,代码要求在其他的地方也能随时的启动流程,在研究moss里面重写的workflow以后,发现对流程的启动是可以通过代码来完全实现的,并且可以不用借助于传统的workflowRuntime,实际上在moss对WF进行二次封装的时候,也屏蔽了对运行时里面的一些东西,具体的因素很多。

/// 

        /// 通过工作流模板名来启动工作流
        /// 
        /// 模板名
        /// 要启动的工作流的项(SPListItem)
        /// 返回是否成功的指示值
        public bool StartWorkFlow(string WFBaseTemplateName, SPListItem ListItem)
        {
             try
            {
                SPList list = ListItem.ParentList;
                string m_wfBaseTemplateName = string.Empty;
                for (int i = 0; i <  ListItem.Workflows.Count; i++)
                {
                    m_wfBaseTemplateName = ListItem.Workflows[i].ParentAssociation.BaseTemplate.Nam                    if (m_wfBaseTemplateName.Equals(WFBaseTemplateName))
                    {
                        //已经被启动就返回
                        return false;
                    }
                }
                //在当前list的WorkflowAssociations中找到要 启动的工作流  SPWorkflowAssociation
                m_wfBaseTemplateName = string.Empty;
                foreach (SPWorkflowAssociation wfAssoc in list.WorkflowAssociations)
                {
                    // search workflowassociation by name
                    m_wfBaseTemplateName = wfAssoc.BaseTemplate.Name;
                    if ((m_wfBaseTemplateName.Equals(WFBaseTemplateName)))
                    {
                        //启动工作流                        
                        ListItem.Web.Site.WorkflowManager.StartWorkflow(ListItem, wfAssoc, wfAssoc.AssociationData, true);
                        break;
                    }
                }
            }
            catch (SPException ex)
            {
                return false;
            }
            return true;
        }

(未完待续)
下一期 ASPX+WF+MOSS

写代码和做技术,不在于你以前在会多少,也不在于你将来想学多少,而在于你今天在多少的时间内能学多少,写多少,想多少。