ipark's blog[MVP SharePoint]

SharePoint related...
随笔 - 23, 评论 - 123, 引用 - 3

导航

关于

My Old Blog: http://freepark.cnblogs.com Email:ipark.cn@gmail.com




Locations of visitors to this page



Creative Commons License

标签

每月存档

最新留言

广告

[SharePoint Designer -4]SharePoint 的无代码工作流在备份还原后不能使用的问题

就我的认识,SharePoint Designer的无代码工作流设计器在SharePoint中有两个作用,一个是设计列表的工作流,另一个是给DataForm Web part提供Custom Action(自定义列表操作)的支持(在上一篇文章中我叙述过相关的内容[SharePoint Designer -3]DataForm Web Part中的数据操作)。

不管是哪种使用方法,使用无代码工作流设计器的最终的结果都是:SharePoint会在网站的Workflows文件夹下面创建相应的文件(有XOML文件,工作流配置的XML文件,规则的rules文件,以及很多的asp页面,xoml和xml文件是一定存在的),然后给列表添加一个工作流关联(association)。其中很重要的一个文件是*.wfconfig.xml文件,其形式大致如下:

<WorkflowConfig>
	<Template 
		BaseID="{963BA7F4-2105-4793-85BB-C1D90367D961}" 
		DocLibID="{8a493f4e-22e4-4f34-aefd-55325667ef0f}" 
		XomlHref="Workflows/工作流 1/工作流 1.xoml" 
		XomlVersion="V4.0">
	</Template>
	<Association 
		ListID="{32a8da29-ac60-4e06-86b8-70d4780e563a}" 
		TaskListID="{6822A246-52D5-4BA8-BCBE-826D17620BE7}" 
		StartOnChange="true">
	</Association>
	<ContentTypes>
	</ContentTypes>
	<Initiation URL="Workflows/工作流 1/工作流 1.aspx">
		<Fields />
		<Parameters></Parameters>
	</Initiation>
</WorkflowConfig>

其中DocLibID是Workflows文档库的GUID,ListID是这个工作流绑定到的列表的GUID。

我们可以通过备份还原网站来迁移部署SharePoint Designer设计的工作流。

但是,当我们备份还原网站后,SharePoint Designer设计的工作流在新的网站中不能工作!

问题出在两个地方:

1)新的网站中工作流对应的*.wfconfig.xml中的DocLibID和ListID不能更新到新网站的对应列表的GUID;

2)列表与相应工作流的关联(Association)丢失了。

根据对问题的分析,我们可以找到方法来重建并绑定工作流。

1)首先更新*.wfconfig.xml文件的DocLibID和ListID

2)重新绑定工作流

SharePoint Designer设计的工作流绑定需要利用WebPartPagesWebService的ValidateWorkflowMarkupAndCreateSupportObjectsAssociateWorkflowMarkup方法。

基于以上的分析,我的解决方案是:

1)在Workflows的文件夹下面维护一个Lists.xml文件,形式如下:

<?xml version="1.0" encoding="utf-8" ?>
<Lists>
	<List Name="test" ID="{23d0e43d-b6bf-4670-b69f-871a63f77941}"></List>
	<List Name="custom workflow process" ID="{76ed547a-aa40-4aab-86e2-5e525ea49e1d}"></List>
</Lists>

为什么维护这样一个列表呢?因为网站备份在还原后,列表的名字不会变,但是GUID会变!所以,维护一个存储所有绑定了SharePoint Designer无代码工作流的列表的ID和Name对应关系的XML文件,方便之后更新*.wfconfig.xml文件用。

2)在网站还原以后,通过一下的ReAttachWorkflows方法来更新*.wfconfig.xml文件,并绑定工作流到相应的列表,代码如下:

public static void ReAttachWorkflows(SPWeb objWeb)
{
    string strXOML = string.Empty;
    string strConfig = string.Empty;
    string strRules = string.Empty;
    string strConfigPath = string.Empty;
    string strVersion = string.Empty;
    string strLists = string.Empty;

    SPList objWorkflowList = null;

    try
    {objWorkflowList = objWeb.Lists["Workflows"];}
    catch
    {objWorkflowList = objWeb.Lists["工作流"];}
    System.IO.StreamReader objReader;
    SPFile Filelists = objWorkflowList.RootFolder.Files["Lists.xml"];
    objReader = new System.IO.StreamReader(Filelists.OpenBinaryStream());
    strLists = objReader.ReadToEnd();
    objReader.Dispose();

    System.Xml.XmlDocument xmlLists = new System.Xml.XmlDocument();
    xmlLists.LoadXml(strLists);

    foreach (SPListItem objItem in objWorkflowList.Folders)
    {
	SPFolder objFolder = objItem.Folder;
	foreach (SPFile objFile in objFolder.Files)
	{
	    objFile.CheckOut(false, string.Empty);
	    objReader = new System.IO.StreamReader(objFile.OpenBinaryStream());
	    if (objFile.Name.EndsWith("xoml"))
	    {
		strXOML = objReader.ReadToEnd();
		objReader.Dispose();
	    }
	    if (objFile.Name.EndsWith("rules"))
	    {
		strRules = objReader.ReadToEnd();
		objReader.Dispose();
	    }
	    if (objFile.Name.EndsWith("xml"))
	    {
		strConfig = objReader.ReadToEnd();
		objReader.Dispose();
		strConfigPath = objFile.ServerRelativeUrl.Substring(objFile.ServerRelativeUrl.IndexOf(objWeb.Name) + objWeb.Name.Length + 1);
		System.Xml.XmlDocument xmlConfig = new System.Xml.XmlDocument();
		xmlConfig.LoadXml(strConfig);
		//update GUID of the 'Workflows' document library
		xmlConfig.SelectSingleNode("/WorkflowConfig/Template/@DocLibID").Value = "{" + objWorkflowList.ID.ToString() + "}";
		System.Xml.XmlNode xmlNodeList = xmlConfig.SelectSingleNode("/WorkflowConfig/Association/@ListID");
		System.Xml.XmlNode xmlNodeListOrigin = xmlLists.SelectSingleNode("/Lists/List[@ID='"+xmlNodeList.Value.ToLower()+"']");
		//update GUID of the list to be associated 
		xmlNodeList.Value = "{" + objWeb.Lists[xmlNodeListOrigin.Attributes["Name"].Value].ID.ToString() + "}";
		strConfig = xmlConfig.OuterXml;
		objFile.SaveBinary(System.Text.Encoding.UTF8.GetBytes(strConfig));
	    }
	    objFile.Update();
	    objFile.CheckIn(string.Empty);
	}

	Microsoft.SharePoint.SoapServer.WebPartPagesWebService objWebPartPages = new Microsoft.SharePoint.SoapServer.WebPartPagesWebService(objWeb);
	string strResult;
	strResult = objWebPartPages.ValidateWorkflowMarkupAndCreateSupportObjects(strXOML, strRules, strConfig, "2");

	//associate with list
	string param1 = strConfigPath;
	string param2 = strVersion;
	strResult = objWebPartPages.AssociateWorkflowMarkup(param1, param2);
    }

    xmlLists = null;

}

 

一些想法

1)了解了SharePoint Designer工作流的工作原理了,我们似乎是可以在一定程度上重用一下用SPD设计出来的工作流呢?

思路可以是这样:两个列表必须具有相同的字段(至少是与工作流相关的字段要一样,然后把Workflows文件夹下面的对应的工作流的文件夹复制一个,修改*.wfconfig.xml文件(更新路径,把绑定的列表的GUID更新,然后用ReAttachWorkflows把工作流绑定好。

从原理上来说这样做是行得通的,当然我并没有试过:)

posted on 2008-01-15 22:48:00 by ipark  评论(3) 阅读(8330)

[SharePoint Designer -3]DataForm Web Part中的数据操作

上次我们介绍了一些DataForm的基本的概念[SharePoint Designer-2]DataForm Web Part解析。在你的SharePoint项目中,不管项目大还是小,有时候可以考虑用DataForm来代替编写Web Part来创建一些页面,特别是显示所有列表以及列表显示的页面。从开发时间上看,使用DataForm不比你单独写一个数据显示交互的Web Part来的时间长,维护与部署甚至会比Web Part来得简单。DataForm利用XSLT进行数据转换展现还是比较灵活的,也易于改变样式,同时DataForm也可以进行各种的数据交互。下面我介绍一下使用DataForm我们可以做哪些数据的交互操作,希望可以给大家一些启发。

1、客户端中对DataForm中数据的操作

说到这个一般是指对列表新建和编辑的DataForm中数据的操作,我们知道在编辑和新建DataForm中,只有形如:

<SharePoint:FormField runat="server" id="ff2{$Pos}" ControlMode="New" FieldName="Title" __designer:bind="{ddwrt:DataBind('i',concat('ff2',$Pos),'Value','ValueChanged','ID',ddwrt:EscapeDelims(string(@ID)),'@Title')}"/>

的FormField类型的SharePoint的WebControl才被最终的数据提交操作识别,并提交,而且,如果在页面上同一个字段有多个FormField Control的话,最终提交的数据是页面自上到下最后一个FormField的值。

所以,我们一般就是利用Javascript来操作FormField在客户端生成的HTML标签,这些标签是有一些规律的,SharePoint Designer的Team Blog里面有一篇文章详细介绍了如何利用Javascript来操作这些字段控件:Using Javascript to Manipulate a List Form Field

如果我们可以操作这些字段控件,对这些字段控件生成的HTML标签值进行赋值修改的话,我们可以隐藏原本生成的控件标签,使用自己的编辑控件,然后,在提交前,把用户编辑的值赋给隐藏的DataForm提交时能识别的字段控件标签,就可以实现比较深入的编辑新建页面的定制了。

其实,在这种状况下,我们相当于只把DataForm作为提交数据的一个载体了。

 

2、提交数据到服务器端

DataForm中与服务器端的数据交互基本是由“http://schemas.microsoft.com/WebParts/v2/DataView/runtime”这个名称空间下(一般在DataForm的XSLT中以ddwrt出现)ddwrt:GenFireServerEvent(string EventString)方法来实现。其实原理很简单ddwrt:GenFireServerEvent这个方法解析出来以后是__dopostback()方法,会触发Postback,把EventString作为Postback的参数,然后DataForm会根据EventString处理相应的数据提交操作。

比如,如果你在DataForm使用了一个<a href="javascript: {ddwrt:GenFireServerEvent('__commit;__refresh;')}">Submit</a>来向服务器端做一个提交操作,在客户端显示的时候这个串就被解析成了<a href="javascript: __doPostBack('ctl00$m$g_18a01f88_4df1_41ef_abef_151d51648f2c','__commit');">Submit</a>,前部分ctl00$m$g_18a01f88_4df1_41ef_abef_151d51648f2c是DataForm Web Part的ID,后面的就是事件的参数,__doPostBack的方法在每个页面你都可以通过查看源文件看到这个方法,它干的事情就是提交页面。

GenFireServerEvent可以通过__dopostback向服务器提交任何参数,DataForm可以识别的一些特定的参数包括:

1)__commit;    所有的数据提交都要包括这个参数。DataForm会根据当前的模式进行提交操作,如果当前是Listform模式,就会把数据提交到列表中去。如果是Table模式,就确认对DataForm的其他操作。说白了,所有的新建,修改,删除都需要加上这个参数才能生效。

2)__refresh;    刷新页面

3)__cancel;    取消这个参数之前的所有操作,即在这个参数之前的所有参数都不起作用

4)__redirectsource;    把页面转向到URL中的Source参数对应的地址,如果URL中没有Source参数则返回列表的默认视图

5)__redirect={URL};    把页面转向到一个制定的URL,这个URL中可以是通过XSLT动态赋值,一个列表编辑DataForm页面提交的例子:<a href="javascript: {ddwrt:GenFireServerEvent(concat('__commit;__refresh;__redirect={DispForm.aspx?ID=',@ID))}">Submit</a> 意思就是编辑提交完成以后跳转到项目显示页面。

6)__delete={ID=3};    删除某个列表项,参数是ID,一般我们可以在显示所有列表的页面给每个项目加一个删除超链接,这个ID的参数就需要在XSLT里面动态附加,形如

<a href="javascript: {ddwrt:GenFireServerEvent(concat('__delete={ID=',@ID,'};__delete={ID=4};__delete={ID=5};__delete={ID=6};__commit;__refresh;'))}">delete</a>

其中concat是用来拼接文本的xslt函数,把@ID与其他的参数拼接。

__delete这个参数在一个GenFireServerEvent中可以存在多个比如:

<a href="javascript: {ddwrt:GenFireServerEvent('__delete={ID=3};__delete={ID=4};__delete={ID=5};__delete={ID=6};__commit;__refresh;')}">delete</a>这个删除链接就是同时删除ID为3,4,5,6的四个列表项。可以看到用了__commit;参数来真正让DataForm去执行删除的动作。

7)__workflowStart={{06D5963E-BE1C-4EC8-9021-3DB29C1E55F0},New,{FCE8E874-EE7D-4475-8F2D-F64CAEB07CE0},Parameters}    这个参数比较特殊,一般用户不会直接去手写这个参数,它是使用“自定义操作”(Custom Actions)后自动生成的。DataForm中的这个自定义操作是巧妙的利用了工作流来满足对数据可能的复杂操作的需求,比如删除项目后,给管理员发邮件提醒等,都可以利用自定义操作来实现。

自定义操作(Custom Actions)的原理也不复杂:每个网站都会有一个叫Custom workflow process的列表,所有自定义操作和其他SharePoint列表的工作流一样都会在Workflows文档库下面创建出来,不同的是所有自定义操作会绑定到Custom workflow process列表上,并允许用户手动启动。__workflowStart中的第一个参数就是网站上Custom workflow process列表的GUID,New是新建一个工作流实例,第三个参数是自定义操作对应的工作流模板的GUID,最后的就是自定义操作里面设置的工作流参数了。

GenFireServerEvent还可以用来Postback任意参数,DataForm中包含一种类型的参数形如:

<ParameterBinding Name="dvt_groupdir" Location="Postback;Connection"/>

这种参数就是从Postback回来的值中取得,做分页的时候需要使用到利用GenFireServerEvent来传值。

 

3、一些补充

1)既然GenFireServerEvent方法其实就是解析成了__dopostback方法,把DataForm的ID和参数给了__dopostback方法。所以如果我们需要,我们可以修改DataForm的ID,不要使用默认生成的GUID,如果你用了自定制的ID以后,使用备份恢复部署后DataForm的ID就不会变了,这样的话我们直接在javascript里面写__dopostback方法了,因为DataForm的ID我们可以确定的知道了。

这个东西有什么用呢?目前我碰到的唯一一个需要用到这个变态方法的地方是,当需要实现可以让用户选择列表中的某几个列表项进行删除的时候,就必须用到这个方法了。

 

2)使用了自定义操作以后,我们通过备份网站,恢复网站进行定制的部署后,我们会发现自定义操作都不能用了,点击带有自定义操作的按钮就报错!这个的解决办法我会在下一篇文章中给出解决方法。

3)我们是用Custom List Form以后,“Attach File”这个功能就失效了!如何即可以定制编辑页面,有可以正常使用附件上传功能呢?我会在后来第5篇文章中谈到;

 

(本文中所有的方法都是定制技术,是不使用任何后端代码下对SharePoint列表数据进行定制操作的方法。

所以,可能有些你感觉用代码开发的时候都不是问题的问题在这里却被当成问题拿出来讨论了。

但是,当你遇到一个完全的定制项目或者你想了解SPD的能力的时候,你会发现这是有价值的)

posted on 2008-01-13 23:23:00 by ipark  评论(6) 阅读(8946)

Powered by: Joycode.MVC引擎 0.5.2.0