Dynamics CRM 2011 アクティビティ フィード その 4
みなさん、こんにちは。
今回はアクティビティ フィードの SDK 対応を紹介します。
すでに SDK 5.0.7 にてサンプルが提供されています。
サンプル
サンプルは以下のパスにあります。
sdk\samplecode\cs\businessdatamodel\activityfeeds
サンプルの流れとしては、以下の通りです。
アクティビティ フィードの構成
テストレコードの作成、フォロー、ウォールへの投稿作成
個人ウォールへの投稿作成
投稿の表示
データの削除
以下では主なメソッドの解説をおこないます。
アクティビティ フィードの構成
[ConfigureActivityFeeds メソッド]
ソースコード WorkingWithActivityFeeds.cs の 109 行目から
アクティビティ フィードの構成のメソッドです。コメントを日本語にしつつ
必要な箇所は追記しています。
private void ConfigureActivityFeeds()
{
Console.WriteLine("== Configuring Activity Feeds ==");
// 現在のユーザーエンティティに対するアクティビティ フィード構成を取得
// 構成情報は msdyn_PostConfig エンティティで保持している
// 組織コンテキストと LINQ を利用して、必要な情報を取得
// 構成されていない場合は、この変数は null を保持
_originalSystemUserConfig =
(from c in _serviceContext.msdyn_PostConfigSet
where c.msdyn_EntityName == SystemUser.EntityLogicalName
select new msdyn_PostConfig
{
// 構成情報の ID、ウォールの有無、エンティティ名を取得
msdyn_PostConfigId = c.msdyn_PostConfigId,
msdyn_ConfigureWall = c.msdyn_ConfigureWall,
msdyn_EntityName = c.msdyn_EntityName
}).FirstOrDefault();
// 現在の潜在顧客に対するアクティビティ フィード構成を取得
// 存在しない場合には構成情報を作成して、アクティビティ フィードをアクティブ化
// ユーザーエンティティのアクティビティ フィードが構成されていない場合は
// 潜在顧客を構成したタイミングで自動で構成される
// 組織コンテキストと LINQ を利用して、必要な情報を取得
_leadConfig =
(from c in _serviceContext.msdyn_PostConfigSet
where c.msdyn_EntityName == Lead.EntityLogicalName
select new msdyn_PostConfig
{
// 構成情報の ID、ウォールの有無、エンティティ名を取得
msdyn_PostConfigId = c.msdyn_PostConfigId,
msdyn_EntityName = c.msdyn_EntityName,
msdyn_ConfigureWall = c.msdyn_ConfigureWall
}).FirstOrDefault();
// 構成情報がない場合
if (_leadConfig == null)
{
// 構成情報を作成して、アクティビティフィードをアクティブ化
_leadConfig = new msdyn_PostConfig
{
// エンティティ名とウォールの有効化を指定
msdyn_EntityName = Lead.EntityLogicalName,
msdyn_ConfigureWall = true
};
_serviceContext.AddObject(_leadConfig);
_serviceContext.SaveChanges();
Console.WriteLine(
" The lead activity feed wall configuration was created.");
}
// 構成情報がある場合
else
{
// 後で設定を戻すため、取得した構成情報を保持
_originalLeadConfig = CloneRelevantConfiguration(_leadConfig);
// 構成情報からウォールが有効か確認
// 無効の場合は有効化
if (!_leadConfig.msdyn_ConfigureWall.HasValue
|| !_leadConfig.msdyn_ConfigureWall.Value)
{
// ウォールを有効にする
_leadConfig.msdyn_ConfigureWall = true;
_serviceContext.UpdateObject(_leadConfig);
_serviceContext.SaveChanges();
Console.WriteLine(
" The lead activity feed wall was enabled.");
}
}
// 潜在顧客のアクティビティ フィードが構成されている場合は、
// 必ずユーザーエンティティのアクティビティ フィードも構成済み
// 後で変更を戻すために設定を保持
_systemuserConfig =
(from c in _serviceContext.msdyn_PostConfigSet
where c.msdyn_EntityName == SystemUser.EntityLogicalName
select new msdyn_PostConfig
{
msdyn_PostConfigId = c.msdyn_PostConfigId,
msdyn_ConfigureWall = c.msdyn_ConfigureWall,
msdyn_EntityName = c.msdyn_EntityName
}).FirstOrDefault();
// ユーザーエンティティのウォールが有効か確認
// 無効の場合は有効化
if (_systemuserConfig != null &&
(!_systemuserConfig.msdyn_ConfigureWall.HasValue
|| !_systemuserConfig.msdyn_ConfigureWall.Value))
{
_systemuserConfig.msdyn_ConfigureWall = true;
_serviceContext.UpdateObject(_systemuserConfig);
_serviceContext.SaveChanges();
Console.WriteLine(" The systemuser activity feed wall was enabled.");
}
// アクティビティ フィードを構成したので、変更を公開
// ウォールを有効にした場合、フォームが変更されるため、公開が必須
PublishSystemUserAndLead();
// 潜在顧客のアクティビティ フィード規則をアクティブ化
// 初期設定ではレコード作成時の投稿が非アクティブになっている
// 規則は msdyn_PostRuleConfig エンティティで保持
// 組織コンテキストと LINQ を利用して、規則を取得
var leadRules =
(from r in _serviceContext.msdyn_PostRuleConfigSet
where r.msdyn_RuleId == "LeadQualify.Yes.Rule"
|| r.msdyn_RuleId == "LeadCreate.Rule"
select r).ToList();
// 既定でルールが 2 つあるため、ルールの数を確認
// ルールの数が 2 つでは無い場合はエラーで終了
if (leadRules.Count() != 2)
{
throw new InvalidSampleExecutionException(
" One or both of the lead config rules do not exist. This can be fixed by deleting the lead post config.");
}
// 取得した規則をアクティブ化
foreach (var configRule in leadRules)
{
_postRuleConfigs.Add(configRule);
ActivateRuleConfig(configRule);
}
}
[ActivateRuleConfig メソッド]
上記メソッドで利用しているアクティビティ フィード規則をアクティブにするメソッドです。
private void ActivateRuleConfig(msdyn_PostRuleConfig qualifyLeadRule)
{
// SetStateRequest を利用して規則をアクティブ化
_serviceProxy.Execute(new SetStateRequest
{
EntityMoniker = qualifyLeadRule.ToEntityReference(),
State = new OptionSetValue((int)msdyn_PostRuleConfigState.Active),
Status = new OptionSetValue((int)msdyn_postruleconfig_statuscode.Active)
});
}
[PublishSystemUserAndLead メソッド]
潜在顧客とユーザーエンティティの公開を行います。
private void PublishSystemUserAndLead()
{
// フォームにウォールを追加したので、エンティティの公開が必要
// PublishXmlRequest を利用して指定したエンティティを公開
_serviceProxy.Execute(new PublishXmlRequest
{
ParameterXml = @"
<importexportxml>
<entities>
<entity>systemuser</entity>
<entity>lead</entity>
</entities>
</importexportxml>"
});
Console.WriteLine(" The systemuser and lead entities were published.");
}
投稿の作成とレコードのフォロー
[PostToRecordWalls メソッド]
メソッド名からはレコードウォールに投稿するだけに見えますが、実際は
レコードの作成やフォローを行い、それから投稿を行います。
private void PostToRecordWalls()
{
Console.WriteLine("\r\n== Working with Record Walls ==");
// テストレコードの作成
// メソッド内で 3 件の潜在顧客を作成
// アクティビティ フィードの規則があるため、レコードの作成時に
//それぞれ投稿が自動作成される
CreateRequiredRecords();
// 作成したレコードをフォロー
// フォローは PostFollow エンティティで管理しているため
// 必要な PostFollow レコードを作成
_follow1 = new PostFollow
{
// フォローするレコードを RegardingObject に設定
RegardingObjectId = _lead1.ToEntityReference()
};
_serviceContext.AddObject(_follow1);
_follow2 = new PostFollow
{
RegardingObjectId = _lead2.ToEntityReference()
};
_serviceContext.AddObject(_follow2);
_follow3 = new PostFollow
{
RegardingObjectId = _lead3.ToEntityReference()
};
_serviceContext.AddObject(_follow3);
_serviceContext.SaveChanges();
Console.WriteLine(" The 3 leads are now followed.");
// 潜在顧客レコードに対してメンションを含んだ投稿、コメントを作成
// 投稿は Post エンティティで管理しているため、必要なレコード作成
_leadPost1 = new Post
{
// 投稿先レコードを RegardingObject に設定
RegardingObjectId = _lead1.ToEntityReference(),
// 投稿元を Source に設定
// 以下では自動投稿を設定
Source = new OptionSetValue((int)PostSource.AutoPost),
// 投稿にメンションを追加
// @[] 部分がメンションとなる
Text = String.Format("This lead is similar to @[{0},{1},\"{2}\"]",
Lead.EntityTypeCode, _lead2.Id, _lead2.FullName)
};
_serviceContext.AddObject(_leadPost1);
_serviceContext.SaveChanges();
Console.WriteLine(" Post 1 has been created.");
// 投稿は後から削除するためクラスレベルの変数で保持しているが
// コメントは紐付く投稿削除時に同時に削除されるため、ローカル変数として保持
// コメントは PostComment エンティティで管理しているため、必要なレコード作成
var comment1 = new PostComment
{
// 紐付く投稿を設定
PostId = _leadPost1.ToEntityReference(),
// メンションを含めることも可能だが、ここではシンプルに文字列のみ
Text = "Sample comment 1"
};
_serviceContext.AddObject(comment1);
_serviceContext.SaveChanges();
Console.WriteLine(" Comment 1 has been created.");
// 上記同様の操作だが、1 つの投稿に 3 つのコメントを作成
var post2 = new Post
{
RegardingObjectId = _lead2.ToEntityReference(),
// ここでは投稿元を手動に設定
Source = new OptionSetValue((int)PostSource.ManualPost),
Text = "This lead was created for a sample."
};
_serviceContext.AddObject(post2);
_serviceContext.SaveChanges();
Console.WriteLine(" Post 2 has been created.");
var comment2 = new PostComment
{
PostId = post2.ToEntityReference(),
Text = "Sample comment 2"
};
var comment3 = new PostComment
{
PostId = post2.ToEntityReference(),
Text = "Sample comment 3"
};
var comment4 = new PostComment
{
PostId = post2.ToEntityReference(),
Text = "Sample comment 4"
};
_serviceContext.AddObject(comment2);
_serviceContext.AddObject(comment3);
_serviceContext.AddObject(comment4);
_serviceContext.SaveChanges();
Console.WriteLine(" Comments 2, 3, and 4 have been created.");
// 潜在顧客の見込みあり操作を行う
// 規則が構成されているため、ウォールに投稿が自動作成される
// 2 つ目のレコードを見込みありにする
// QualifyLeadReques を利用
var qualifyLead2Request = new QualifyLeadRequest
{
// 取引先企業へ変換
CreateAccount = true,
LeadId = _lead2.ToEntityReference(),
Status = new OptionSetValue((int)lead_statuscode.Qualified)
};
var qualifyLead2Response = (QualifyLeadResponse)_serviceProxy.Execute(
qualifyLead2Request);
// 変換で作成された取引先企業を後で削除するように
// リストに追加
foreach (var entityRef in qualifyLead2Response.CreatedEntities)
{
_generatedEntities.Add(entityRef);
}
Console.WriteLine(" Lead 2 was qualified.");
// 3 つ目のレコードも見込みありにする.
var qualifyLead3Request = new QualifyLeadRequest
{
CreateAccount = true,
LeadId = _lead3.ToEntityReference(),
Status = new OptionSetValue((int)lead_statuscode.Qualified)
};
var qualifyLead3Response = (QualifyLeadResponse)_serviceProxy.Execute(
qualifyLead3Request);
foreach (var entityRef in qualifyLead3Response.CreatedEntities)
{
_generatedEntities.Add(entityRef);
}
Console.WriteLine(" Lead 3 was qualified.");
}
[PostToPersonalWalls メソッド]
個人ウォールへの投稿作成を行います。
private void PostToPersonalWalls()
{
Console.WriteLine("\r\n== Working with Personal Walls ==");
// 投稿先をユーザーのウォールにするため、自身の情報を取得
// WhoAmIRequest を利用
var whoAmIRequest = new WhoAmIRequest();
var whoAmIResponse = (WhoAmIResponse)_serviceProxy.Execute(whoAmIRequest);
// 取得した情報は EntityReference として保持
var currentUserRef = new EntityReference(
SystemUser.EntityLogicalName, whoAmIResponse.UserId);
// 1 つ目の取引先企業をメンションとして投稿を作成
_post1 = new Post
{
// RegardingObject にはユーザーを EntityReference として指定
RegardingObjectId = currentUserRef,
// 投稿元は手動作成
Source = new OptionSetValue((int)PostSource.ManualPost),
// メンションを含んだ投稿を作成
Text = String.Format("I'd rather not pursue @[{0},{1},\"{2}\"]",
Lead.EntityTypeCode, _lead1.Id, _lead1.FullName)
};
_serviceContext.AddObject(_post1);
_serviceContext.SaveChanges();
Console.WriteLine(" Personal wall post 1 was created.");
// 上記投稿にコメントを追加
var comment1 = new PostComment
{
PostId = _post1.ToEntityReference(),
Text = "Personal wall comment 1."
};
_serviceContext.AddObject(comment1);
_serviceContext.SaveChanges();
Console.WriteLine(" Personal wall comment 1 was created.");
// メンションを含まない投稿も作成
_post2 = new Post
{
RegardingObjectId = currentUserRef,
Source = new OptionSetValue((int)PostSource.AutoPost),
Text = "Personal wall post 2."
};
_serviceContext.AddObject(_post2);
_serviceContext.SaveChanges();
Console.WriteLine(" Personal wall post 2 was created.");
// 上記投稿に対してコメントを 4 つ作成
var comment2 = new PostComment
{
PostId = _post2.ToEntityReference(),
Text = "Personal wall comment 2."
};
var comment3 = new PostComment
{
PostId = _post2.ToEntityReference(),
Text = "Personal wall comment 3."
};
var comment4 = new PostComment
{
PostId = _post2.ToEntityReference(),
Text = "Personal wall comment 4."
};
var comment5 = new PostComment
{
PostId = _post2.ToEntityReference(),
Text = "Personal wall comment 5."
};
_serviceContext.AddObject(comment2);
_serviceContext.AddObject(comment3);
_serviceContext.AddObject(comment4);
_serviceContext.AddObject(comment5);
_serviceContext.SaveChanges();
Console.WriteLine(" Personal wall comments 2, 3, 4, and 5 were created.");
// ページングの動作を見るために、さらに投稿を追加
_post3 = new Post
{
RegardingObjectId = currentUserRef,
Source = new OptionSetValue((int)PostSource.ManualPost),
Text = "Personal wall post 3."
};
_post4 = new Post
{
RegardingObjectId = currentUserRef,
Source = new OptionSetValue((int)PostSource.AutoPost),
Text = "Personal wall post 4."
};
_serviceContext.AddObject(_post3);
_serviceContext.AddObject(_post4);
_serviceContext.SaveChanges();
Console.WriteLine(" Personal wall posts 3 and 4 were created.");
// 個人のウォールの投稿を取得
// 1 ページ目の取得
DisplayPersonalWallPage(1);
// 2 ページ目の取得
DisplayPersonalWallPage(2);
// 以降の投稿の作成時間がこれまでと明らかに時間が空くように
// 1 秒間待機
Thread.Sleep(1000);
// 上記 3 つ目の投稿にコメント作成
// ウォールの一番上部に表示されることを確認
var newPostComment = new PostComment
{
PostId = _post3.ToEntityReference(),
Text = "New comment to show that new comments affect post ordering."
};
_serviceContext.AddObject(newPostComment);
_serviceContext.SaveChanges();
Console.WriteLine("\r\n A new comment was created to show effects on post ordering.");
// 再度ウォールの 1 ページ目を表示して、並び順を確認
DisplayPersonalWallPage(1);
// コメント取得のページング
// 2 つ目の投稿に対するコメントを 2 件ずつ取得
// データ取得に QueryExpression を利用
var commentsQuery = new QueryExpression(PostComment.EntityLogicalName)
{
// 全ての列を取得
ColumnSet = new ColumnSet(true),
Criteria = new FilterExpression(LogicalOperator.And),
// ページング設定
PageInfo = new PagingInfo
{
Count = 2,
PageNumber = 1,
ReturnTotalRecordCount = true
}
};
// 2 つ目の投稿に紐付くものを指定
commentsQuery.Criteria.AddCondition(
"postid", ConditionOperator.Equal, _post2.Id);
// 取得するコメントがなくなるまでループ
EntityCollection commentsResult;
do
{
commentsResult = _serviceProxy.RetrieveMultiple(commentsQuery);
// 取得したコメントを表示
// ここで Comments for lead 2 となっていますが、実際は個人のウォールの
// 2つ目の投稿です。
Console.WriteLine("\r\n Comments for lead 2 page {0}",
commentsQuery.PageInfo.PageNumber);
foreach (PostComment comment in commentsResult.Entities)
{
DisplayComment(comment);
}
commentsQuery.PageInfo.PageNumber += 1;
}
while (commentsResult.MoreRecords);
}
[DisplayPersonalWallPage メソッド]
個人のウォールからデータを取得して表示します。
private void DisplayPersonalWallPage(int pageNumber)
{
// ページングが発生するよう、同時に 5 件まで取得
var pageSize = 5;
// RetrievePersonalWallRequest を利用
var personalWallPageReq = new RetrievePersonalWallRequest
{
// 1 つの投稿に対するコメント数上限に 2 を設定
CommentsPerPost = 2,
PageNumber = pageNumber,
PageSize = pageSize
};
var personalWallPageRes =
(RetrievePersonalWallResponse)_serviceProxy.Execute(personalWallPageReq);
Console.WriteLine("\r\n Personal Wall Page {0} Posts:", pageNumber);
// 結果をループして表示
foreach (Post post in personalWallPageRes.EntityCollection.Entities)
{
// DisplayPost メソッドでは、投稿とそのコメントを表示
DisplayPost(post);
}
}
以降では上記と同じような仕組みで、レコードのウォールに対する
投稿と表示を行いますが、解説か割愛します。
またレコードの削除に関しても、通常のエンティティと同様の手段で
削除していますので、詳細は割愛します。
動作確認
今回はアクティビティ フィードが構成されていない環境で、サンプルを
実行してみました。
アクティビティ フィードの構成
ユーザーエンティティと潜在顧客エンティティが構成されています。
ウォールも有効です。
また潜在顧客の規則もアクティブです。
個人ウォールの投稿
フォローしている潜在顧客の、作成時および取引先企業への
変換時の自動投稿が表示されています。
ユーザー投稿およびコメントも反映され、コメントも 2 件で
ページングしている様子が分かります。すべてのコメントを表示を
クリックすると全て表示されます。
上記画面対応するコマンドプロンプト側のレコード取得も成功しています。
まとめ
アクティビティ フィードは完全に SDK 対応です。サンプルはコンソール
アプリケーションですが、プラグインとして開発することも可能であるため
前回ワークフローで実装した内容を、同期のプラグインとして開発し、
レコード操作と同じタイミング、トランザクションで投稿を操作できます。
是非お試しください!!
‐ Dynamics CRM サポート 中村 憲一郎
Comments
Anonymous
November 10, 2011
SDKを使用せずに RESTでActivity Feedsの情報を取得できますでしょうか。Anonymous
November 13, 2011
コメントありがとうございます。 回答が遅くなりました。 アクティビティ フィードも通常のエンティティですので REST での操作が可能です。 例えば以下の URL でユーザー投稿の一覧を取得できます。 XRMServices/2011/OrganizationData.svc/PostSet?$select=Text&$filter=Source/Value eq 2 また以下の URL で関連するコメントも取得できます。 XRMServices/2011/OrganizationData.svc/PostSet?$select=Text,Post_Comments/Text&$filter=Source/Value eq 2&$expand=Post_Comments ただし通常のエンティティと異なり、自動投稿やメンションは特殊な形式で取得されますので、必要に応じて加工しなければいけません。 現状サンプルがありませんが、サンプルが公開され次第紹介します。 中村Anonymous
November 15, 2011
ありがとうございます。 サンプルよろしくお願いします。