こんにちは。
- WCF の Transport レベルの Custom Channel Sample
- Rehosting を使ったエンドユーザへのワークフロー公開
- WF による Rule Base のアプリケーション
- Custom の SharePoint Workflow Editor
製品開発者向けということで実施した Tech Ed の T2-401 セッションですが、お約束通り、デモを添付しておきます。
ここまでの内容を元に、これを SharePoint に応用した、さいごにお見せしたサンプルです。(ライセンスの問題もお約束通り修正しておきました . . .)
download sample
セッションの中で 1 つ言い忘れてしまったのですが、xoml の作成部分については、これまでの説明とまったく同様、普通にワークフローを構築して、いつものように WorkflowMarkupSerializer にお任せで OK です。(その部分のコードは、ソースをじっくりとみておいてください。) 違っている点とすれば、SharePoint 用のアクティビティを使っているという点だけです。
配置方法ですが、セッションでご説明したように、製品向けなどで SharePoint のワークフローを配置する際は、stsadm による 「ワークフローテンプレート」ではなく、 SharePoint Designer が使用している「ワークフロー」のデプロイのほうがさまざまな点で優れていました。
SharePoint では、WorkflowRuntime を直接起動して操作することは禁止されおり、ここでのポイントは SharePoint がワークフロー構築のために用意している (主に SharePoint Designer で使用されている)、ValidateWorkflowMarkupAndCreateSupportObjects、AssociateWorkflowMarkup などのメソッドが使えることでした。(http://msdn.microsoft.com/en-us/library/bb417436.aspx)
このため、SharePoint 展開用の「どのタスクリストを使うの ?」、「どのワークフローフォルダに入れるの ?」、「どのリストに関連付けるの ?」などの設定情報を記載したワークフロー構成ファイル (.xoml.wfconfig.xml) が必要で、この作成もおこなう必要があったわけです。(ただ、この定義は非常にシンプルなものですので、サンプルコードのように、XmlDocument などを使って自作すれば OK です)
あとは WorkflowMarkupSerializer などを使ってこれまでと同じように処理をおこなえば良いのですが、ご説明したように、SharePoint 固有の他のさまざまな課題もクリアしなければなりません。
例えば、ワークフローを関連付けるリストは、リストの表示名ではなく GUID が必要となります。また、列についても、表示名ではなく内部名が必要となります。また、ワークフローも、ただのドキュメントライブラリではなく、ご説明した ServerTemplate ID が 117 番のワークフロー用のフォルダに入れておく必要があります。つまり、このフォルダがない場合には、このフォルダを作るという作業も必要になるわけです。
(さらに、ここではサンプルを簡単にするためにワークフローフォームも使っていませんが、カスタムの初期化フォームや、タスクフォームが必要な場合には、こうしたフォームも aspx などで構築しておき、配置する必要もあります。)
コードでは、これら事前の処理を実施するため、リスト Web サービス (Lists.asmx) を使用した事前のやりとりをおこっています。またファイルの配置などは、WebRequest オブジェクトを使って WebDAV フォルダにアクセスしています。
さいごに、セッションの終わりでご説明した点ですが、時間が迫っていて簡単にしか説明できませんでしたので、ここはちゃんと記載しておきましょう。SharePoint Designer が作成した xoml ですが、セッションでお見せしたように、参照しているアクティビティのライブラリ名が、
Microsoft.SharePoint.WorkflowActions.dll, Version=12.0.0.0, Culture=neutral, PublicKeyToken=null
となっていました。
これは、SharePoint Designer が、リモートからも登録されているアクティビティの dll を扱ってワークフローの作成と配置ができるために必要な仕様で、Sharepoint Designer は %userprofile%\AppData\Roaming\Microsoft\SharePoint Designer\ProxyAssemblyCache\12.0.0.6219 の中に、サーバ上の dll と同じクラスやメソッドを持った dll の Proxy を内部で生成しています。このため、プロフェッショナル開発者が自作したカスタムの dll (SharePoint Designer のアクティビティ) なども、ちゃんと Proxy がキャッシュされ、リモートからも正しく動作するようになっています。(よって、公開キーまではコピーできませんので、PublicKeyToken を null にして配置する必要があります。)
では、この処理と同じことを皆さんの製品の中でも実施したくなるかもしれませんが、セッションでご紹介した FetchLegalWorkflowActions などの Web サービスのメソッドは、ご説明したように製品が内部で使用しているもので、一般の開発者に公開されたメソッドではありません。そこで上記のダウンロードサンプルでは、サーバ側の Microsoft.SharePoint.WorkflowActions.dll と同じクラスやメソッドを持つスタブ用のコードを作成し、これをワークフロー構築で使用するようにしています。(xoml 構築のためのクラスですので、内部処理は必要ありません。)
よって、WebPart の開発などでサーバ側で動作するモジュールの場合にはこうした面倒なスタブコードは必要ありませんし、リモートで処理されるワークフローであっても、初期化処理 (OnWorkflowActivated など) 以降はカスタムアクティビティだけで構成するようにしておけば、(初期化部分のみ xoml のテンプレートなどを用意しておき) Microsoft のライブラリを真似てスタブを構成しておく必要もありません。一般的には、こうした方法で開発するほうが望ましいでしょう。
以下にこのスタブコード以外の処理コードの部分を掲載しておきます。
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
ComboBox[] Conditions = new ComboBox[3];
ComboBox[] Tasks = new ComboBox[3];
TextBox[] MailAddresses = new TextBox[3];
TextBox[] FieldNames = new TextBox[3];
TextBox[] FieldValues = new TextBox[3];
TextBox[] Approvers = new TextBox[3];
XmlDocument xomlDoc;
XmlDocument configDoc;
private void Form1_Load(object sender, EventArgs e)
{
Tasks[0] = Task0;
Tasks[1] = Task1;
Tasks[2] = Task2;
MailAddresses[0] = MailAddress0;
MailAddresses[1] = MailAddress1;
MailAddresses[2] = MailAddress2;
FieldNames[0] = FieldName0;
FieldNames[1] = FieldName1;
FieldNames[2] = FieldName2;
FieldValues[0] = FieldValue0;
FieldValues[1] = FieldValue1;
FieldValues[2] = FieldValue2;
}
private void ExecuteButton_Click(object sender, EventArgs e)
{
// Step 0 : Workflow Location などの設定
Step_SetupObjects();
// Step 1 : Workflow Config を作成
Step_CreateWorkflowConfig();
// Step 2 : XOML を作成
Step_CreateXoml();
// Step 3 : Upload !
Step_UploadFiles();
// Step 4 : ワークフローのコンパイル
WebPartPagesSvc.WebPartPagesWebService sv = new WebPartPagesSvc.WebPartPagesWebService();
sv.Url = string.Format("{0}/{1}", SiteLoc.Text.TrimEnd(new char[] { '/' }), "_vti_bin/WebPartPages.asmx");
sv.UseDefaultCredentials = true;
sv.Credentials = System.Net.CredentialCache.DefaultCredentials;
sv.PreAuthenticate = true;
sv.ValidateWorkflowMarkupAndCreateSupportObjects(xomlDoc.InnerXml, "", configDoc.InnerXml, "2");
sv.AssociateWorkflowMarkup(string.Format("Workflows/{0}/{0}.xoml.wfconfig.xml", WorkflowName.Text), "V1.0");
MessageBox.Show("ワークフローを配置しました");
}
private void Step_SetupObjects()
{
string taskListGUID = null;
string workflowLibGUID = null;
ListsSvc.Lists sv = new ListsSvc.Lists();
sv.Url = string.Format("{0}/{1}", SiteLoc.Text.TrimEnd(new char[] { '/' }), "_vti_bin/Lists.asmx");
sv.UseDefaultCredentials = true;
sv.Credentials = System.Net.CredentialCache.DefaultCredentials;
sv.PreAuthenticate = true;
XmlNode listCol = sv.GetListCollection();
foreach (XmlNode list in listCol)
{
// Get Task List ID
if (list.Attributes["ServerTemplate"].Value == "107")
taskListGUID = list.Attributes["ID"].Value;
// Get No-Code Workflow Location ID
else if (list.Attributes["ServerTemplate"].Value == "117")
workflowLibGUID = list.Attributes["ID"].Value;
}
if (string.IsNullOrEmpty(taskListGUID))
sv.AddList("Tasks", "Tasks", 107);
if (string.IsNullOrEmpty(workflowLibGUID))
sv.AddList("Workflows", "Workflows", 117);
}
private void Step_CreateWorkflowConfig()
{
string docListGUID = GetListGUIDFromName(SiteLoc.Text, AssocDocLib.Text);
string taskListGUID, workflowLibGUID;
GetWorkflowMetadata(SiteLoc.Text, out taskListGUID, out workflowLibGUID);
// Root
configDoc = new XmlDocument();
XmlElement elemRoot = configDoc.CreateElement("WorkflowConfig");
configDoc.AppendChild(elemRoot);
// Template 要素
XmlElement elemTemplate = configDoc.CreateElement("Template");
XmlAttribute attBaseID = configDoc.CreateAttribute("BaseID");
attBaseID.Value = "{" + Guid.NewGuid().ToString() + "}";
elemTemplate.Attributes.Append(attBaseID);
XmlAttribute attDocLibID = configDoc.CreateAttribute("DocLibID");
attDocLibID.Value = workflowLibGUID;
elemTemplate.Attributes.Append(attDocLibID);
XmlAttribute attXomlHref = configDoc.CreateAttribute("XomlHref");
attXomlHref.Value = string.Format("Workflows/{0}/{0}.xoml", WorkflowName.Text);
elemTemplate.Attributes.Append(attXomlHref);
XmlAttribute attXomlVersion = configDoc.CreateAttribute("XomlVersion");
attXomlVersion.Value = "V1.0";
elemTemplate.Attributes.Append(attXomlVersion);
elemRoot.AppendChild(elemTemplate);
// Association 要素
XmlElement elemAssociation = configDoc.CreateElement("Association");
XmlAttribute attListID = configDoc.CreateAttribute("ListID");
attListID.Value = docListGUID;
elemAssociation.Attributes.Append(attListID);
XmlAttribute attTaskListID = configDoc.CreateAttribute("TaskListID");
attTaskListID.Value = taskListGUID;
elemAssociation.Attributes.Append(attTaskListID);
XmlAttribute attStartOnCreate = configDoc.CreateAttribute("StartOnCreate");
attStartOnCreate.Value = "true";
elemAssociation.Attributes.Append(attStartOnCreate);
elemRoot.AppendChild(elemAssociation);
// ContentTypes 要素
XmlElement elemContentTypes = configDoc.CreateElement("ContentTypes");
elemRoot.AppendChild(elemContentTypes);
// Initiation 要素
XmlElement elemInitiation = configDoc.CreateElement("Initiation");
XmlAttribute attURL = configDoc.CreateAttribute("URL");
attURL.Value = "None"; // もしある場合は, .aspx を作成して相対パスを記載する
elemInitiation.Attributes.Append(attURL);
{
// Fields 要素
XmlElement elemFields = configDoc.CreateElement("Fields");
elemInitiation.AppendChild(elemFields);
// Parameters 要素
XmlElement elemParameters = configDoc.CreateElement("Parameters");
elemInitiation.AppendChild(elemParameters);
}
elemRoot.AppendChild(elemInitiation);
}
private void Step_CreateXoml()
{
// Root アクティビティ
RootWorkflowActivityWithData rootActivity = new RootWorkflowActivityWithData();
rootActivity.Name = "ROOT";
rootActivity.WorkflowFields.Add(new WorkflowDataField("__list", "System.String"));
rootActivity.WorkflowFields.Add(new WorkflowDataField("__item", "System.Int32"));
rootActivity.WorkflowFields.Add(new WorkflowDataField("__context", "Microsoft.SharePoint.WorkflowActions.WorkflowContext"));
rootActivity.WorkflowFields.Add(new WorkflowDataField("__initParams", "Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties"));
rootActivity.WorkflowFields.Add(new WorkflowDataField("__workflowId", "System.Guid"));
// OnWorkflowActivated アクティビティ
OnWorkflowActivated onWorkflowActivatedActivity = new OnWorkflowActivated();
CorrelationToken onWorkflowActivatedColToken = new CorrelationToken("refObject");
onWorkflowActivatedColToken.OwnerActivityName = "ROOT";
onWorkflowActivatedActivity.CorrelationToken = onWorkflowActivatedColToken;
ActivityBind onWorkflowActivatedBind = new ActivityBind();
onWorkflowActivatedBind.Name = "ROOT";
onWorkflowActivatedBind.Path = "__initParams";