SharePointでのイベントレシーバーのサイクリングと一時的な無効化

ご存知かもしれませんが、SharePointには多くのイベントレシーバー(EventReceiver)があり、SharePointオブジェクトで標準操作(リストアイテムの追加/削除/変更など)を実行するときにカスタムコードを呼び出すことができます。 イベントレシーバーでの作業は別の記事に値しますが、このテーマについては非常に多くの記事があります 。たとえば、 こちらです。



今日、私たちは、初心者のSharePoint開発者がレシーバーで作業するときに発生する可能性のある2つの特殊な問題を調べます。



1.イベントの周期的な呼び出し。



受信機がそれ自体を無限のサイクルに「駆動」できる状況を想像するのは非常に簡単です。 例として、アイテム項目「更新後」(ItemUpdated)のイベントレシーバーを取り上げます。 コードはこの要素を使用して追加のアクションを実行し(たとえば、ユーザーが入力したデータに基づいて、追加の計算を実行し、必要なフィールドに書き込みます)、もちろんUpdateを呼び出してデータを保存します。 通話後、どうなりますか? もちろん。 レシーバーで記述されたコードが再度呼び出されます。 そして無限に。



public override void ItemUpdated(SPItemEventProperties properties) { base.ItemUpdated(properties); SPListItem Item = properties.ListItem; Item["Title"] = DateTime.Today.ToString("dd.MM.yyyy"); Item.Update(); }
      
      







私には理解できない理由で、一部の開発者は、イベントハンドラーの実行を無効にするには、オブジェクトのUpdateではなくSystemUpdateを呼び出すだけで十分であると考えています。 しかし、これはそうではありません。 msdnは、このメソッドを使用すると、Modified、ModifiedByフィールドを更新せずにアイテムのバージョンを増やすことなくリストアイテムを更新できると言っているだけでなく、逆も実験的に証明されています。



つまり、 SystemUpdateを使用してイベントハンドラーの呼び出しを無効にするだけでは十分ではありませんが、上記の例では、 Updateの代わりにSystemUpdateを呼び出して、上記のフィールドへの変更を除外し、バージョン管理が有効になっている場合は要素の新しいバージョンを作成することをお勧めします



この問題は、 SPEventReceiverBaseのEventFiringEnabledプロパティ(またはSPEventReceiverBaseから現在継承されているオブジェクト)を解決するのに役立ちます。



このプロパティを使用すると、現在のスレッドでSPEventReceiverBaseクラスから継承されたイベントハンドラーを呼び出す機能を制御できます。 つまり、コード内でイベントハンドラーの呼び出しをオンまたはオフにすることができ、特定のメソッドでハンドラーを無効または無効にすることで他のコードまたは単純なユーザー操作(レシーバーも呼び出す)が影に隠れることを恐れることはありません。



 public override void ItemUpdated(SPItemEventProperties properties) { base.ItemUpdated(properties); SPListItem Item = properties.ListItem; this.EventFiringEnabled = false; Item["Title"] = DateTime.Today.ToString("dd.MM.yyyy"); Item.Update(); this.EventFiringEnabled = true; }
      
      





メソッドの実行中に指定されたコードが呼び出されると、レシーバーへの呼び出しは切断されます。 EventFiringEnabledプロパティがfalseに 設定されている場合、 SPEventReceiverBaseから継承されたすべてのレシーバーが無効になるという事実に注意を払う価値があり、これを自分で処理する必要があります。 つまり、受信者への呼び出しをオフにしてからオンにするまでの間に、更新用のイベントハンドラーが関連付けられている別のリストを更新しようとすると、失敗します(理由はこれより少し低い)。 実行されることになっていたコードを強制する必要があります。



上記のコードに注意を払うと、最適でないことがすぐに明らかになります。 少なくとも、try-catch-finallyでラップする方が理にかなっています(まあ、または少なくとも単にtry-finally)。 これまたはそれが何であるか知っていますか?



結果は、出力に関係なく、イベントハンドラーの実行を有効にする次のコードです。



 public override void ItemUpdated(SPItemEventProperties properties) { base.ItemUpdated(properties); try { SPListItem Item = properties.ListItem; this.EventFiringEnabled = false; Item["Title"] = DateTime.Today.ToString("dd.MM.yyyy"); Item.Update(); } finally { this.EventFiringEnabled = true; } }
      
      





以下のテキストは、 usingステートメントを使用した別の可能な切断オプションを示しています



2.受信者の外部で特定の操作を実行するときにイベントを無効にする必要がある。



レシーバー自体の内部でレシーバーの実行を無効にする方法を見つけました。 しかし、受信機自体の外部で受信機の実行を無効にする必要がある部分もあります。 つまり、たとえば、リストアイテムを更新したいが、レシーバーが呼び出されないようにするコンソールアプリケーションでは、レシーバーはその中のデータを処理して上書きし、アイテムで必要な操作を行ってからレシーバーで更新します。



基本的に、タスクは前のタスクと非常に似ていますが、 SPEventReceiverBaseオブジェクトはなく、レシーバーの呼び出しを無効にするものはありません。 ここでは、リフレクターに目を向けて、このクラスが何であり、どのように継続するかを確認する必要があります。



 protected bool EventFiringEnabled { get { return !SPEventManager.EventFiringDisabled; } set { SPEventManager.EventFiringDisabled = !value; } }
      
      





コードから、クラスにEventFiringEnabledプロパティがあり(イベント呼び出しをオフにするために少し前に使用した)、その助けを借りて、静的プロパティSPEventManager.EventFiringDisabledから値を取得または設定していることがわかります。 このプロパティのコードは次のとおりです。



 internal static bool EventFiringDisabled { get { SPEventManager.EnsureTlsEventFiringDisabled(); object data = Thread.GetData(SPEventManager.m_tlsEventFiringDisabled); return data != null && (bool) data; } set { SPEventManager.EnsureTlsEventFiringDisabled(); Thread.SetData(SPEventManager.m_tlsEventFiringDisabled, (object) (bool) (value ? 1 : 0)); } }
      
      







メソッドは静的であり、内部の作業は既にスレッドに基づいているため、 SPEventReceiverBaseから継承されたすべてのクラスは現在のオブジェクトのコンテキストで機能しなくなります。 必要な情報は、ストリームに割り当てられたメモリセルで読み書きされます。 どのレシーバーから他のハンドラーへの呼び出しが切断またはオンにされるかはそれほど重要ではないことがわかりますが、どのスレッドでこれが行われるかは重要です。 したがって、インスタンスを初期化し、既におなじみのEventFiringEnabledプロパティを操作するために必要なコードの場所で、 SPEventReceiverBase (または特定のケースではSPItemEventReceiverから継承されます)から継承した独自のクラスを作成するだけで十分です。



 public class DisableItemEvents : SPItemEventReceiver { public bool CustomEventFiringEnabled { get { return base.EventFiringEnabled; } set { base.EventFiringEnabled = value; } } }
      
      





そして、たとえば次のように呼び出します:



 var EventsDisable = new DisableItemEvents(); try { Item["Title"] = DateTime.Today.ToString("dd.MM.yyyy"); EventsDisable.CustomEventFiringEnabled = false; Item.Update(); } finally { EventsDisable.CustomEventFiringEnabled = true; }
      
      





それは私たちが期待したものでした。 レシーバーを更新すると機能しませんでした。 先ほど言ったように、このアプローチには生命権があり、多くの人がそれを使用していますが、この設計については少し異なる方法で説明します。 私はパターンの使用が本当に好きです。 少なくとも、この機能を使用すると、命令内のエラーに関係なく、Disposeが常に呼び出されることを確認できます。



クラスを次のように説明しました。



 public class DisableItemEvents : SPItemEventReceiver, IDisposable { private bool _EventStatus; public DisableItemEvents() { _EventStatus = base.EventFiringEnabled; base.EventFiringEnabled = false; } public void Dispose() { base.EventFiringEnabled = _EventStatus; } }
      
      





このクラスの操作は次のとおりです。



 using (new DisableItemEvents()) { Item.Update(); //     } Item.Update(); //   
      
      







ここでは、コメントはすでに不要ですが、それでもなお。 DisableItemEventsオブジェクトを初期化すると、 EventFiringEnabledプロパティの元の値が保存され、falseに設定されます。 オブジェクトがアンロードされると、EventFiringEnabledプロパティが返されます。



したがって、レシーバーの内部と外部の両方からイベントハンドラーからの呼び出しを切断するための可能なオプションを検討しました。



All Articles