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);
    }
}

以降では上記と同じような仕組みで、レコードのウォールに対する
投稿と表示を行いますが、解説か割愛します。

またレコードの削除に関しても、通常のエンティティと同様の手段で
削除していますので、詳細は割愛します。

動作確認

今回はアクティビティ フィードが構成されていない環境で、サンプルを
実行してみました。

アクティビティ フィードの構成

ユーザーエンティティと潜在顧客エンティティが構成されています。
ウォールも有効です。

image

また潜在顧客の規則もアクティブです。

image

個人ウォールの投稿

フォローしている潜在顧客の、作成時および取引先企業への
変換時の自動投稿が表示されています。

image

image

ユーザー投稿およびコメントも反映され、コメントも 2 件で
ページングしている様子が分かります。すべてのコメントを表示を
クリックすると全て表示されます。

image

上記画面対応するコマンドプロンプト側のレコード取得も成功しています。

image

まとめ

アクティビティ フィードは完全に SDK 対応です。サンプルはコンソール
アプリケーションですが、プラグインとして開発することも可能であるため
前回ワークフローで実装した内容を、同期のプラグインとして開発し、
レコード操作と同じタイミング、トランザクションで投稿を操作できます。

是非お試しください!!

‐ Dynamics CRM サポート 中村 憲一郎