次の方法で共有


SharePoint アドインでアドイン イベント レシーバーを作成する

事前にプロバイダー向けのホスト型 SharePoint アドインについて理解していること、少なくとも "Hello World" レベル以上のものを開発した経験があると助けになります。 「プロバイダー向けのホスト型 SharePoint アドインの作成を始める」を参照してください。

また、SharePoint アドインのイベントの処理に精通している必要もあります。

詳細なコード サンプルを取得する

この記事で継続的に用いられている例には、完成したコード サンプルが含まれています。 以下にその他のサンプルをいくつか示します。 これらのサンプルはこの記事で説明されているアーキテクチャには従っていません。 アドイン イベント レシーバーを構築するための優れた方法は 1 つとは限らず、Microsoft のガイダンスも時間の経過とともに改良される可能性があることを銘記してください。

アドインがインストールされたイベント レシーバーを追加する

  1. Visual Studio で、プロバイダーホスト型 SharePoint アドインのプロジェクトを開きます。 (アドイン イベント ハンドラーを SharePoint ホスト型アドインに追加すると、Office Developer Tools for Visual Studio によってプロバイダーでホストされるアプリに変換されます)。

  2. ソリューション エクスプローラーで、SharePoint アドインのノードを選択します。

  3. [プロパティ] ウィンドウで、[アドインのハンドルがインストールされました] の値を [True] に設定します。

    プロパティ ウィンドウでのアプリ イベント

    Office Developer Tools for Visual Studio で次の処理が実行されます。

    • スケルトン C# (または VB.NET) コードが含まれる AppEventReceiver.svc. という名前のファイルを追加します。 これはアドイン イベントを処理するサービスです。

    • 次のエントリを AppManifest.xml ファイルの Properties セクションに追加します。<InstalledEventEndpoint>~remoteAppUrl/AppEventReceiver.svc</InstalledEventEndpoint>。 このエントリにより、アドイン イベント レシーバーが SharePoint に登録されます。

      注:

      ~remoteAppUrl トークンは、このプロバイダー向けのホスト型 SharePoint アドインのリモート Web アプリケーションで使用されるトークンと同じものです。 Office Developer Tools for Visual Studio では、Web アプリケーションとイベント ハンドラーのドメインは同じものと想定しています。 まれに同じでない場合がありますが、その場合には ~remoteAppUrl トークンをサービスの実際のドメインに手動で置き換える必要があります。

    • SharePoint アドイン プロジェクトに Web プロジェクトが作成されていない場合は、Web プロジェクトを作成します。 また、ツールにより、プロバイダー向けのホスト型アドイン用にアドイン マニフェストが構成されます。 さらに、ページ、スクリプト、CSS ファイル、その他のアーティファクトも追加されます。 アドインで必要なリモート コンポーネントがイベント処理 Web サービスのみの場合は、これらをプロジェクトから削除できます。 アドイン マニフェスト内の StartPage 要素が、削除済みページを指さないようにする必要もあります。

  4. Visual Studio を実行しているのと同じコンピューター上に SharePoint テスト ファームがない場合、Microsoft Azure サービス バスを使用してデバッグするようにプロジェクトを構成します。 詳しくは、「SharePoint アドインでのリモート イベント レシーバーのデバッグとトラブルシューティング」をご覧ください。

  5. AppEventReceiver.svc ファイルに ProcessOneWayEvent メソッドがある場合、そのメソッドの実装には throw new NotImplementedException(); 行のみを含める必要があります。このメソッドはアドイン イベント ハンドラーでは使用できないからです。

    アドイン イベント ハンドラーは、イベントを終了するかロールバックするかを SharePoint に通知するオブジェクトを戻す必要があり、ProcessOneWayEvent メソッドは何も戻しません。

  6. このファイルには、以下のような ProcessEvent メソッドが含まれます。 (クライアント コンテキストを取得する方法を示すコード ブロックも存在する可能性があります。削除するか、コメント アウトします)。

    public SPRemoteEventResult ProcessEvent(SPRemoteEventProperties properties)
    {
        SPRemoteEventResult result = new SPRemoteEventResult();
    
        return result;
    }
    

    このコードについては、次の点に注意してください。

    • SPRemoteEventProperties オブジェクトは、SOAP メッセージとしてハンドラー Web サービスに送信されます。それには、イベントを識別する EventType プロパティなど、SharePoint からのコンテキスト情報が含まれます。

    • ハンドラーが戻す SPRemoteEventResult オブジェクトには、Status プロパティが含まれます。このプロパティで可能な値は、SPRemoteEventServiceStatus.ContinueSPRemoteEventServiceStatus.CancelNoErrorSPRemoteEventServiceStatus.CancelWithError です。 Status プロパティの既定値は Continue です。この場合には SharePoint にイベントの終了が通知されます。 他の 2 つの値は SharePoint に以下の内容を通知します。

      • さらに最大 3 回までハンドラーを実行します。
      • 引き続きキャンセル ステータスを取得する場合には、イベントをキャンセルし、そのイベントの一部として実行されたすべての操作をロールバックします。
  7. result 変数を宣言する行のすぐ後に、処理するイベントを特定する以下のスイッチ構造を追加します。

    switch (properties.EventType)
    {
        case SPRemoteEventType.AppInstalled:
            break;
        case SPRemoteEventType.AppUpgraded:
            break;
        case SPRemoteEventType.AppUninstalling:
            break;
    }
    

    注:

    AppInstalledAppUpdatedAppInstalling イベント用に複数のハンドラーがある場合は、各イベント独自の URL がアドイン マニフェストに登録されます。 そのため、 エンドポイントは異 なりますが、この記事 (および Office Developer Tools for Visual Studio) では、まったく同じエンドポイントがあることを前提としています。そのため、コードはそれを呼び出したイベントを決定する必要があります。

  8. アドイン イベント ハンドラーにロールバック ロジックと『既に実行』ロジックを含める」で説明されているように、インストール ロジックにエラーがあると、ほとんどの場合、アドインのインストールをキャンセルし、SharePoint がインストールのために行った処理をロール バックし、ハンドラーが行った処理をロール バックする必要があります。

    これを行う方法の 1 つは、AppInstalled イベント用の case 内に以下のコードを追加することです。

    case SPRemoteEventType.AppInstalled:
    try
    {
        // Add-in installed event logic goes here.
    }
    catch (Exception e)
    {
        result.ErrorMessage = e.ErrorMessage;
        result.Status = SPRemoteEventServiceStatus.CancelWithError;
    
        // Rollback logic goes here.
    }
    break;
    

    注:

    30 秒より長くかかるインストール コードをアドイン自体に移動します。 アドインの初回実行時に実行される "初回実行" ロジックにそれを追加できます。 アドインには、"準備ができました" のようなメッセージが表示されます。または、アドインは、初期化コードを実行するようにユーザーに求めることができます。

    "初回実行" ロジックが適さないアドインの場合、別のオプションとして、イベント ハンドラーでリモートの非同期プロセスを開始し、その直後に StatusContinue に設定された SPRemoteEventResult オブジェクトを返すようにする方法があります。 この方法の欠点は、そのリモート プロセスが失敗した場合に、SharePoint に対してアドインのインストールをロールバックするように通知する手段がないということです。

  9. アドイン イベント ハンドラーのアーキテクチャ ストラテジ」で説明したように、すべてのシナリオで使用できるわけではありませんが、ハンドラー委任ストラテジが推奨されます。 継続して用いている例で、ホスト Web にリストを追加するときにハンドラー委任ストラテジを実装する方法を示します。 ハンドラー委任戦略を使用しない同様の AppInstalled イベント ハンドラーを作成する方法については、 SharePoint/PnP/Samples/Core.AppEvents のサンプルを参照してください。

    以下に、新しいバージョンの AppInstalled case ブロックを示します。 なお、すべてのイベントに適用される初期化ロジックは switch ブロックの上に配置されています。 インストールされる同じリストは AppUninstalling ハンドラーで削除されるため、リストはそこで識別されます。

    SPRemoteEventResult result = new SPRemoteEventResult();
    String listTitle = "MyList";
    
    switch (properties.EventType)
    {               
        case SPRemoteEventType.AppInstalled:
    
    try
    {
            string error = TryCreateList(listTitle, properties);
            if (error != String.Empty)
            {
                throw new Exception(error);            
            }
    }
        catch (Exception e)
    {
            // Tell SharePoint to cancel the event.
            result.ErrorMessage = e.Message;
            result.Status = SPRemoteEventServiceStatus.CancelWithError;               
        }
            break;
        case SPRemoteEventType.AppUpgraded:
        break;
        case SPRemoteEventType.AppUninstalling:
        break;
    }                      
    
  10. リスト作成メソッドを、以下のコードを使用して AppEventReceiver クラスに private メソッドとして追加します。 TokenHelper クラスには、アドイン イベントのクライアント コンテキストを取得するために最適化されている特別なメソッドが含まれていることに注目してください。 最後のパラメーターに false を渡すことにより、コンテキストがホスト Web 用であることが示されます。

    private string TryCreateList(String listTitle, SPRemoteEventProperties properties)
    {    
        string errorMessage = String.Empty;          
    
        using (ClientContext clientContext =
            TokenHelper.CreateAppEventClientContext(properties, useAppWeb: false))
        {
            if (clientContext != null)
            {
            }
        }
        return errorMessage;
    }
    
    
  11. ロールバック ロジックは基本的には例外処理ロジックであり、SharePoint CSOM (クライアント側オブジェクト モデル) に含まれている ExceptionHandlingScope により、Web サービスは例外処理を SharePoint サーバーに委任できます (「方法: 例外処理範囲を使用する」を参照)。

    以下のコードを、前述のスニペットの if ブロックに追加します。

    ExceptionHandlingScope scope = new ExceptionHandlingScope(clientContext); 
    
    using (scope.StartScope()) 
    { 
        using (scope.StartTry()) 
        { 
        }         
        using (scope.StartCatch()) 
        {                                 
        } 
        using (scope.StartFinally()) 
        { 
        } 
    } 
    clientContext.ExecuteQuery();
    
    if (scope.HasException)
    {
        errorMessage = String.Format("{0}: {1}; {2}; {3}; {4}; {5}", 
            scope.ServerErrorTypeName, scope.ErrorMessage, 
            scope.ServerErrorDetails, scope.ServerErrorValue, 
            scope.ServerStackTrace, scope.ServerErrorCode);
    }
    
  12. 前述のスニペットでは SharePoint に対する呼び出し (ExecuteQuery) は 1 度だけですが、1 度ですべてを処理することはできません。 例外スコープで参照するすべてのオブジェクトは、最初にクライアントにロードされる必要があります。

    以下のコードを ExceptionHandlingScope のコンストラクターのに追加します。

    ListCollection allLists = clientContext.Web.Lists;
    IEnumerable<List> matchingLists =
        clientContext.LoadQuery(allLists.Where(list => list.Title == listTitle));
    clientContext.ExecuteQuery();
    
    var foundList = matchingLists.FirstOrDefault();
    List createdList = null;
    
  13. ホスト Web リストを作成するためのコードは StartTry ブロックに入れますが、このコードでは、リストが既に追加されているかどうかを最初にチェックする必要があります (「アドイン イベント ハンドラーにロールバック ロジックと『既に実行』ロジックを含める」を参照)。 ConditionalScope クラスを使用すると、If-then-else ロジックを SharePoint サーバーに委任できます (「方法: 条件範囲を使用する」も参照)。

    以下のコードを StartTry ブロック内に追加します。

    ConditionalScope condScope = new ConditionalScope(clientContext, 
            () => foundList.ServerObjectIsNull.Value == true, true);
    using (condScope.StartScope())
    {
        ListCreationInformation listInfo = new ListCreationInformation();
        listInfo.Title = listTitle;
        listInfo.TemplateType = (int)ListTemplateType.GenericList;
        listInfo.Url = listTitle;
        createdList = clientContext.Web.Lists.Add(listInfo);                                
    }
    
  14. StartCatch ブロックはリストの作成を元に戻しますが、リストが作成されたかどうかを最初にチェックする必要があります。これは、リストの作成が完了する前に StartTry ブロックで例外がスローされていた可能性があるためです。

    以下のコードを、StartCatch ブロックに追加します。

    ConditionalScope condScope = new ConditionalScope(clientContext, 
            () => createdList.ServerObjectIsNull.Value != true, true);
    using (condScope.StartScope())
    {
        createdList.DeleteObject();
    } 
    

    ヒント

    トラブルシューティング: 必要な時に StartCatch ブロックが処理されるか確認するには、SharePoint サーバー上にランタイム例外をスローする方法を用意する必要があります。 throw を使用したり、0 で除算したりしてもうまくいきません。これは、クライアント ランタイムが ExecuteQuery メソッドを使用してコードをバンドルしサーバーに送信できるようになる前に、クライアント側の例外が発生するためです。

    代わりに、次の行を StartTry ブロックに追加します。 クライアント側のランタイムはこれを受け入れますが、サーバー側では狙いどおり例外が発生するようになります。

    List fakeList = clientContext.Web.Lists.GetByTitle("NoSuchList");

    clientContext.Load(fakeList);

TryCreateList メソッド全体は次のようになります。 (StartFinally ブロックは、使用されていない場合でも必要です)。

    private string TryCreateList(String listTitle, SPRemoteEventProperties properties)
    {    
        string errorMessage = String.Empty;  

        using (ClientContext clientContext = 
            TokenHelper.CreateAppEventClientContext(properties, useAppWeb: false))
        {
            if (clientContext != null)
            {
                ListCollection allLists = clientContext.Web.Lists;
                IEnumerable<List> matchingLists = 
                    clientContext.LoadQuery(allLists.Where(list => list.Title == listTitle));
                clientContext.ExecuteQuery();
                var foundList = matchingLists.FirstOrDefault();
                List createdList = null;

                ExceptionHandlingScope scope = new ExceptionHandlingScope(clientContext); 
                using (scope.StartScope()) 
                { 
                    using (scope.StartTry()) 
                    { 
                        ConditionalScope condScope = new ConditionalScope(clientContext, 
                                () => foundList.ServerObjectIsNull.Value == true, true);  
                        using (condScope.StartScope())
                        {
                            ListCreationInformation listInfo = new ListCreationInformation();
                            listInfo.Title = listTitle;
                            listInfo.TemplateType = (int)ListTemplateType.GenericList;
                            listInfo.Url = listTitle;
                            createdList = clientContext.Web.Lists.Add(listInfo);
                        }
                    } 
                    
                    using (scope.StartCatch()) 
                    { 
                        ConditionalScope condScope = new ConditionalScope(clientContext, 
                                () => createdList.ServerObjectIsNull.Value != true, true);
                        using (condScope.StartScope())
                        {
                            createdList.DeleteObject();
                        }    
                    } 

                    using (scope.StartFinally()) 
                    { 
                    } 
                } 
                clientContext.ExecuteQuery();

                if (scope.HasException)
                {
                        errorMessage = String.Format("{0}: {1}; {2}; {3}; {4}; {5}", 
                        scope.ServerErrorTypeName, scope.ErrorMessage, 
                        scope.ServerErrorDetails, scope.ServerErrorValue, 
                        scope.ServerStackTrace, scope.ServerErrorCode);
                }
            }
        }
        return errorMessage;
    }

ヒント

デバッグ: ハンドラー委任ストラテジを使用しているかどうかに関係なく、デバッガーでコードをステップ実行するときには、ハンドラーがキャンセル ステータスを返すすべてのシナリオにおいて、SharePoint は 3 回までを限度としてハンドラーを再度呼び出すことに注意してください。 そのため、デバッガーは最大で 4 回、コード全体を繰り返します。

ヒント

コードのアーキテクチャ: 宣言型マークアップを使用すると、アドイン Web 上のコンポーネントをハンドラーの外部にインストールできるので、通常、ハンドラーがアドイン Web と対話できる 30 秒を一切消費する必要はありません。 その必要がある場合は、コードにアドイン Web 用の別個の ClientContext オブジェクトが必要になります。 つまり、アドイン Web とホスト Web は、SQL Server データベースがどちらとも異なるのと同様に、互いに別個のコンポーネントであるということです。 したがって、アドイン Web に呼び出しを実行するメソッドは、継続して用いている例の TryCreateList メソッドと同様、AppInstalled case ブロックの中の try ブロックに記述されます。 ただし、ハンドラーはアドイン Web で実行されたアクションをロールバックする必要 はありません 。 エラーが発生する場合、ハンドラーはイベントをキャンセルするだけで済みます。イベントがキャンセルされると、SharePoint がアドイン Web 全体を削除するためです。

アドイン アンインストール イベント レシーバーを作成する

  1. プロジェクトの [アドインのアンインストールの処理] プロパティを [True] に設定します。 Web サービス ファイルが既に存在する場合、ツールは別のファイルを作成しませんが、UninstallingEventEndpoint 要素がアドイン マニフェストに追加されます。

  2. AppUninstalling case ブロック内のコードで、アドインが第 2 段階のごみ箱から削除された後 (これによりイベントがトリガーされます) に不要となるアドインのアーティファクトを削除する必要があります。 ただし、可能な場合には、完全に削除するのではなく、コンポーネントを "使用中止" にする必要があります。 アンインストール イベントをロールバックする必要が生じる場合にそれらを復元しなければならないためです。 その場合、アドインはまだ第 2 段階のごみ箱にあるので、ユーザーがそれを復元して再び使用できます。 削除したコンポーネントをロールバック ロジックで再作成するだけでアドインが再び作動するようになることもありますが、コンポーネント内のデータや構成設定は失われます。

    このストラテジは、SharePoint コンポーネントに関しては比較的簡単です。SharePoint には対象を復元できるごみ箱があり、ごみ箱にアクセスするための CSOM API もあるからです。 この手順の後の方で、その方法を示します。 他のプラットフォームでは、別の方法が必要になる場合があります。 たとえば、アドイン アンインストール ハンドラーに含まれる SQL Server テーブルの行を使用中止にする場合、ハンドラーの T-SQL ストアド プロシージャでそのテーブルに IsDeleted 列を追加し、それを対象行で True に設定できます。 プロシージャでエラーが発生すると、ロールバック ロジックにより値が False にリセットされます。 エラーが発生せずにプロシージャが終了した場合、成功フラグを戻す直前に、後からその行を削除するタイマー ジョブを設定できます。

    アドインを削除した後にも、リストなどのデータを保持したい場合もあります。ただし、この記事の例として、以下に、インストールされたイベント ハンドラーによって作成されたリストを削除するアンインストール イベント ハンドラーを示します。

    case SPRemoteEventType.AppUninstalling:
    
    try
    {
        string error = TryRecycleList(listTitle, properties);
        if (error != String.Empty)
        {
            throw new Exception(error);
        }
    }
    catch (Exception e)
    {
        // Tell SharePoint to cancel the event.
        result.ErrorMessage = e.Message;
        result.Status = SPRemoteEventServiceStatus.CancelWithError;
    }
    break;
    
  3. リストをリサイクルするためのヘルパー メソッドを追加します。 このコードの以下の点に注目してください。

    • このコードは、リストを完全に削除するのではなく、リサイクルします。 こうしておけば、イベントが失敗した場合、 StartCatch ブロックの実行内容 (データも含む) を復元できます。 メソッドが正常に実行されてイベントが完了すると、アドインは削除済みデータ バックアップから完全に削除されますが、リストはごみ箱内に引き続き存在します。

    • コードは、ユーザーが SharePoint UI で既にリサイクルしている可能性があるため、リストをリサイクルする前にリストの存在をテストします。 Similiarly では、ロールバック コードは、ユーザーが既に復元しているか、2 番目のステージのごみ箱に移動している可能性があるため、リストを復元する前に、ごみ箱にリストが存在するかどうかを確認します。

    • リストに対する参照が null かどうかをチェックしてリストの存在をテストする条件範囲が 2 つあります。 どちらも、全く同じオブジェクトに関して NULL かどうかを 2 度テストする内部 if ブロックがあります。 条件範囲ブロックが含まれる外部テストはサーバー上で実行されますが、NULL かどうかの内部テストも必要です。 これは、クライアント ランタイムがコードを行単位で移動して、ExecuteQuery メソッドがサーバーに送信する XML メッセージを作成するためです。 foundList オブジェクトと recycledList オブジェクトへの参照が見つかると、NULL かどうかの内部チェック内にそれらの行が含まれていない場合、それらのいずれかの行により NULL 参照例外がスローされます。

      private string TryRecycleList(String listTitle, SPRemoteEventProperties properties)
      {
          string errorMessage = String.Empty;
      
          using (ClientContext clientContext = 
              TokenHelper.CreateAppEventClientContext(properties, useAppWeb: false))
          {
              if (clientContext != null)
              {
                  ListCollection allLists = clientContext.Web.Lists;
                  IEnumerable<List> matchingLists = 
                      clientContext.LoadQuery(allLists.Where(list => list.Title == listTitle));
                  RecycleBinItemCollection bin = clientContext.Web.RecycleBin;
                  IEnumerable<RecycleBinItem> matchingRecycleBinItems = 
                      clientContext.LoadQuery(bin.Where(item => item.Title == listTitle));        
                  clientContext.ExecuteQuery();
      
                  List foundList = matchingLists.FirstOrDefault();
                  RecycleBinItem recycledList = matchingRecycleBinItems.FirstOrDefault();    
      
                  ExceptionHandlingScope scope = new ExceptionHandlingScope(clientContext);
                  using (scope.StartScope())
                  {
                      using (scope.StartTry())
                      {
                          ConditionalScope condScope = new ConditionalScope(clientContext, 
                              () => foundList.ServerObjectIsNull.Value == false, true);
                          using (condScope.StartScope())
                          {
                              if (foundList != null)
                              {
                                  foundList.Recycle();
                              }
                          }
                      }
                      using (scope.StartCatch())
                      {
                          ConditionalScope condScope = new ConditionalScope(clientContext, 
                              () => recycledList.ServerObjectIsNull.Value == false, true);
                          using (condScope.StartScope())
                          {
                              if (recycledList != null)
                              {
                                  recycledList.Restore(); 
                              }
                          }
                      }
                      using (scope.StartFinally())
                      {
                      }
                  }
                  clientContext.ExecuteQuery();
      
                  if (scope.HasException)
                  {
                      errorMessage = String.Format("{0}: {1}; {2}; {3}; {4}; {5}", 
                          scope.ServerErrorTypeName, scope.ErrorMessage, 
                          scope.ServerErrorDetails, scope.ServerErrorValue, 
                          scope.ServerStackTrace, scope.ServerErrorCode);
                  }
              }
          }
          return errorMessage;
      }
      

アドイン アンインストール レシーバーをデバッグしてテストする

  1. 次に示すページを、それぞれ別のウィンドウまたはタブで開きます。

    • サイト コンテンツ
    • サイトの設定 - ごみ箱 (_layouts/15/AdminRecycleBin.aspx?ql=1)
    • ごみ箱 - 第 2 段階のごみ箱 (_layouts/15/AdminRecycleBin.aspxView=2&?ql=1)
  2. F5 キーを押して、プロンプトが表示されたらアドインを信頼します。 アドインのスタート ページが開きます。 アンインストール ハンドラーのテストのみを行う予定の場合、このブラウザー ウィンドウを閉じても構いません。 ただし、ハンドラーをデバッグする場合は、開いたままにします。 終了すると、デバッグ セッションが終了します。

  3. [サイト コンテンツ] ページを更新し、アドインが表示されたら削除します。

  4. [サイトの設定 - ごみ箱] ページを更新します。 アドインが最上位項目として表示されます。 横にあるチェック ボックスを選択し、[選択項目の削除] をクリックします。

  5. [ごみ箱 - 第 2 段階のごみ箱] ページを更新します。 アドインが最上位項目として表示されます。 横にあるチェック ボックスを選択し、[選択項目の削除] をクリックします。 SharePoint によって、アドイン アンインストール ハンドラーがすぐに呼び出されます。

アドイン更新イベント ハンドラーを作成する

アドイン更新ハンドラーの作成の詳細については、「SharePoint アドインの更新イベントのハンドラーを作成する」を参照してください。

運用環境でのアドイン イベント レシーバー URL とホストの制限

このリモート イベント レシーバーは、クラウドか、オンプレミス サーバー (SharePoint サーバーとしても使用されているものは除く) でホストできます。 運用環境のレシーバー URL で特定のポートを指定することはできません。 つまり、HTTPS のポート 443 (推奨) と HTTP のポート 80 のいずれかを使用する必要があります。 HTTPS を使用し、レシーバー サービスがオンプレミスでホストされ、アドインが SharePoint Online 上にある場合、ホスティング サーバーには、証明機関からの公的に信頼された証明書が必要となります。 (自己署名証明書が有効になるのは、アドインがオンプレミスの SharePoint ファームにある場合のみです。)

関連項目