EventHandler 事件

EventHandler给我们在MOSS中开发带来了很大的自由度,很方便地就捕获到在数据操作时的事件,插入相应的业务代码,实现我们的业务逻辑。在使用了好长一段时间EventHandler后,忽然想归纳一下我这段时间在使用EventHandler时遇到的问题,给大家分享一下!

 

1. 在ItemAdding事件或ItemUpdating事件中获取用户输入的值
        这是一个很普通的操作,通常我们都会遇到这样一个业务逻辑:只有当用户输入的值符合要求才允许用户添加记录,否则取消该操作;或者在用户添加一条记录时,获取用户输入的值,再去进行其它操作。遇到这些业务,必须的是要在ItemAdding事件中获取到用户用户的值,那我们自然就会很想到一个属性properties.ListItem,这是正在添加的记录项,但恰恰这是添加前的事件,properties.ListItem的值为Null,并不能获取得到,就算在ItemUpdating事件中,properties.ListItem虽然是有值,但并不是用户输入的最新的值,而是原来的值(旧值)。
        想在记录项添加时或修改时得到新的值,我们可以直接使用下面的语句
        properties.AfterProperties["Title"].ToString();

2. 在ItemAdding事件或ItemUpdating事件中修改该记录项的值
        我们有时候在设计列表的时候,会添加一些对用户隐藏的栏,用于记录与该记录相关的信息,只能通过程序去改写此栏的值,那我们在添加新记录项或修改记录项时怎样修改该栏的值呢?很自然,我们会写出下面的程序
public override void ItemUpdating(SPItemEventProperties properties)
{
    SPListItem item
= properties.ListItem;
     item[
"Title"] = "这是我指定的值";
     item.Update();
}

    但很抱歉,运行这段的下场只有一个——出错!系统会报出“保存冲突”的错误信息。回头仔细看看代码,代码在记录项正在修改时,再次修改了该记录项。与SQL相似,MOSS也会在进入ItemUpdating事件前,把该记录项上了锁,以防止该记录项在没完成本次修改前给再次修改,因此这样写注定是失败的!如果是在ItemAdding事件更是如此,在前文说了,在ItemAdding事件时,properties.ListItem的值为Null,该记录项都还没生成,所以根本不可能也不应该通过properties.ListItem来修改某个栏上的值,因为properties.ListItem所表示的是一个记录项旧的值(新增前的旧值没是没有,null)
    那通过什么属性来修改呢?MOSS一定会提供一个途径给我们做这事的!我们再来关注一下properties.AfterProperties这个属性,从字面意思可以很容易知道,这是记录项新的值,但很可惜,这个属性是只读的,我们也不能直接修改这个属性达到我们的目的。接着往下看,在properties.AfterProperties中还有一个属性ChangedProperties,连起来看,properties.AfterProperties.ChangedProperties字面意思很像是该属性保存该字段改变了的值(SDK也是这样说的,Gets properties that have changed in the collection),这很容易就理解成MOSS会自动比较用户修改了记录的哪些值,开始的时候,我也是这样理解的,但很奇怪,无论我怎样修改记录的值,在调试时,该属性值的Count属性始终是0,并不如我开始所想的!
     那时在网上查了很多资料,都只是说奇怪MS为什么会提供ChangedProperties这样一个HashTable类型的属性,而且始终是没有值放到里面去的。在一次反编译MS的官方站点模板中的EventHandler事件程序时,我才恍然大悟!我们应该这样理解ChangedProperties这个HashTable,它是用于给我们修改栏值的一个集合,我觉得SDK的英文应该改成“Gets properties that will be changed in the collection”才对(个人见解),因为在ItemAdding事件或ItemUpdating事件中,记录项并没有真的生成或修改,ListItem代表的是原来的值,AfterProperties代表的是这个记录项将要变成的值,共中的ChangedProperties提供再次修改的一次机会。所以,要在ItemAdding事件或ItemUpdating事件中修改该记录项的值得在这样写

public override void ItemAdding(SPItemEventProperties properties)

{
   
// Demo1: 在新建时取用户输入的值并作修改
    string newValue = "新的值是:" + properties.AfterProperties["Title"].ToString();
    properties.AfterProperties.ChangedProperties.Add(
"Title", newValue);
}

 

===================================

正如你所知道的,事件接收器(EventHandler)非常强大,可以挂在许多SharePoint事件上。它们可以应用于功能(Feature)事件,如FeatureActivated,列表事件,如FieldAdded,等等。然而,其中我们最常用的还是SPItemEventReceiver中的一系列接收器。通过继承该类,我们可以把自己的代码挂接在列表项或文档库文档上发生的一系列事件上。

当你利用这些事件时,就很快会发现存在前(同步)后(异步)两种事件。其方法的后缀分别为“ing”(比如,ItemAdding)和“ed”(比如,ItemAdded),分别代表了变更发生前调用和发生后调用。这些都是最基本的东西。

接下来,当你更深入时甚至发现,你可以提取变更前和变更后的状态变化。比如,我们可以挂接到一个文档库的ItemUpdating事件上,来阻止用修改某个特定的字段。其代码大致如下:

 

  public override void  ItemUpdating(SPItemEventProperties properties)
  {
       if (properties.BeforeProperties["column"] != properties.AfterProperties["column"])
      {
          properties.Cancel = true;
          properties.ErrorMessage = "该栏不允许修改";
      }
  }

对于文档库而言,该代码运行良好。然而,你应该知道,BeforeProperties哈希表对于列表项来说并不公开。用SDK中的原话说:“对于文档,BeforeAfter 属性可为发布事件(例如 ItemUpdated)提供保证,但 Before 属性不能用于有关列表项的发布事件。”。

当他们说“不能用于有关列表项的发布事件”时,意思是说在后事件(比如 ItemUpdated,ItemDeleted等)中不可用吗?这里词语表述不是很清楚,因此我决定花点时间测试一下各种常见事件(像添加,修改和删除)与列表和库的组合。每个测试包括了添加一个新项,编辑该项,然后删除该项。下面是对列表的测试结果:

列表 BeforeProperties AfterProperties properties.ListItem
ItemAdding 没值 新值
ItemAdded 没值 新值 新值
ItemUpdating 没值 更改的值 老值
ItemUpdated 没值 更改的值 更改的值
ItemDeleting 没值 没值 老值
ItemDeleted 没值 没值

“没值”的意思是该栏的值在哈希表中不可用。

“新值”的意思是该栏当前的值是可用的。

更改的值”的意思是该栏修改后的值是可用的。

“老值”的意思是该栏在修改前的值是可用的。

下面是对于文档库的测试结果:

文档库 BeforeProperties AfterProperties properties.ListItem
ItemAdding 没值 没值
ItemAdded 没值 没值 新值
ItemUpdating 老值 更改的值 老值
ItemUpdated 老值 更改的值 更改的值
ItemDeleting 没值 没值 老值
ItemDeleted 没值 没值

Properties.ListItem是指列表项在事件中当前的值。空表示该项不可用。通过我的分析得到如下结论:

毫不奇怪,我们在ItemAdding(在项被添加前)和ItemDeleted(在项被删除后)得到的是空值。之前Lshai Sagi也证明过这一点。

正如SDK中记载的,对于列表项事件不公开BeforeProperties。

对于列表项而言ItemAdding 和ItemAdded正确返回了AfterProperties的值,而对于库文件则无效。这一点很有意思。

通过之前的实验发现,在ItemDeleted中没有任何可用的值。这也很明显,一旦删除了就彻底消失了。

因此,如果再回到我们原来的问题上,如何防止用户在列表项的事件中修改某一栏的值?对于列表而言,你可以看到,如果挂在ItemUpdating事件上,我们可以对当前列表项的值(properties.ListItem)和AfterProperties的值进行比较。代码如下:

 

  if (properties.ListItem["column"] != properties.AfterProperties["column"])
  {
      properties.Cancel = true;
      properties.ErrorMessage = "该栏不允许修改";
  }

==========================================

SharePoint 2010: Send Notification on Item Approved/Rejected (when Content Approval Status is changed)

In SharePoint 2010 new improvements are made for better event receiving management. Few new event handlers are added for site, web, list, listitems etc. However, One thing that I think badly needed was the content approval events  for list items.

Nowadays content approval has become an integral part of content management system. Requirements has come up to do more work on content approval/reject. But unfortunately, SharePoint list/library doesn’t have events like ContentApproved, ContentRejected, ContentRequestedForReview so that user can tap the events to do their own work on content approval status changes. So it seems we need to do a lot of works manually to send notifications on content approval status changes.

 

Problem: Approving Status change events Missing

One of my client wanted to get notification on the following scenarios:

1. On Item Add/Update: If a user edit an item and item goes to pending status as the item needs approval, the approving teams need to be notified that an item is waiting for their approval.

2. On Item Approved: If the approving team approve the item,the user who added/updated the item needs to be notified.

3. On Item rejected: If the approving team reject the item, the user who added/updated the item needs to be notified with reasons why the item rejected.

But the SharePoint Object Model doesn’t have the extensibility at this point where approving status changes.

Why Approval Status change event missing?

The best solution would be if SharePoint team would provide us with out-of-box events for content approval. In that case, two events would be suffice. The events might be : ContentApprovingStatusChanging and ContentApprovingStatusChanged and the event argument’s AfterProperties and BeforeProperties values could be filled with the the old value and new value of Content Approving Status field value. However, one may argue that ItemAdded/ItemUpdate events are similar like Content Approval events. So when user add/edit an item and as part of the add/edit if approval status field get updated then which events to fire? ItemAdded/ItemUpdate or content approval events. Hmm.. maybe there’s complexities with the new content approval events and SharePoint team has not added the new content approval events.

 

Resolution: Use ItemAdded, ItemUpdating and ItemUpdated events to keep track of approval status changing

So consider now the problem we’re going to talk about. We need a notification system where we need to send notification to the approver or user (who is waiting for approval) on approval status change. We’ll develop a list item event receiver for ItemAdded and ItemUpdated events. When a new item will be added it’s easy to identify item status and if the status is pending then we can send notification to all people in the approving team. But when an Item is updated, you need to keep track of if the Approving status field value is changed, if so then u need to send notification. However, you can only get the old approval status field value in ItemUpdating event, but you don’t want to send notification in ItemUpdating. So it’s safe to send notification in ItemUdated event but in ItemUpdated event you’ll not get the old value. You can access the old value in ItemUpdating. So here’s the deal:

  • Create a new field say OldStatus in the list. This field will be used to keep track of if the approval status field value has been changed.
  • In ItemUpdating event, set the current approval status (before updating) to OldStutus field.
  • In ItemUpdated, compare the current status to OldStatus field value and if they are not same then it’s for sure that the approval status has been changed. So send notification.

 

So let’s go with the steps. First we need an List Event Receiver that will listen three events of the list: ItemAdded, ItemUpating and ItemUpdated. You need to send notification on two events: ItemAdded and ItemUpdated. However, we need to hook the event ItemUpdating to know whether the approval status is going to be changed

Create a List Event Receiver to send notification

  1. Send notification on Item Added

    On Item added event, check if the item status is pending. If so then send notification. The following code snippet may give you the gist.

    public override void ItemAdded(SPItemEventProperties properties)
    {
    const string approvalStatusFieldInternalName = "_ModerationStatus";

     

    var list = properties.List;
    var approvalStatuField = list.Fields.GetFieldByInternalName(approvalStatusFieldInternalName);
    var approvalStatusFieldValue = properties.ListItem[approvalStatuField.Id];
    var approvalStatus = (approvalStatusFieldValue == null) ? string.Empty :
    approvalStatusFieldValue.ToString();
    if (approvalStatus == "Pending")
    {
    //SendNotification()
    }
    }

  2. Keep track of the approval status field value (before updated) on Item Updating event

    I’m assuming that you have a field OldStatus where I’ll keep the approval status field value which is going to be changed. I’ll explain later in this post how to automatically add the field in list. But for now just take for granted that you have a field OldStatus in your list of type string. The following code show how to keep the approval status (before update) value in OldStatus field in ItemUpdating Event.

    public override void ItemUpdating(SPItemEventProperties properties)
    {
    const string approvalStatusFieldInternalName = "_ModerationStatus";

     

    var list = properties.List;
    var approvalStatuField = list.Fields.GetFieldByInternalName(approvalStatusFieldInternalName);
    var approvalStatusFieldValue = properties.ListItem[approvalStatuField.Id];
    var approvalStatusValue = (approvalStatusFieldValue == null) ? string.Empty :
    approvalStatusFieldValue.ToString();

    if (string.IsNullOrEmpty(approvalStatusValue)) return;

    EventFiringEnabled = false;
    properties.ListItem["OldStatus"] = approvalStatusValue;
    properties.ListItem.SystemUpdate(false);
    EventFiringEnabled = true;
    }

  3. Check the OldStatus field value and current approval status value to know if the approval status changed.

Item updated is fried once the update is done. So we’ll get the updated value of Approval Status. But fortunately, we have kept the old value of Approval Status field in OldStatus field during ItemUpdating event as shown in step 2.

public override void ItemUpdated(SPItemEventProperties properties)
{
const string approvalStatusFieldInternalName = "_ModerationStatus";
var list = properties.List;

 

var approvalStatusField = list.Fields.GetFieldByInternalName(approvalStatusFieldInternalName);
var currentStatuFieldValue = properties.ListItem[approvalStatusField.Id];
var currentStatus = (currentStatuFieldValue == null) ? string.Empty :
currentStatuFieldValue.ToString();

var oldStatusFieldValue = properties.ListItem["OldStatus"];
var oldStatus = (oldStatusFieldValue == null) ? string.Empty : oldStatusFieldValue.ToString();

if (string.IsNullOrEmpty(oldStatus) && oldStatus != currentStatus)
{
//SendNotification();
}
}

 

Create a feature receiver to attached List Event Receiver and to create field OldStatus

Finally We need an feature receiver (not list event receiver) which will do two works: Attached our list event receiver to a list and create a field OldStatus in the list.

  • FeatureActivating Event

In FeatureActivating you need to check first if the event is already registered. If not registered then register the event. Also make sure the list has OldStatus field. In the code below, listNeedsToAttachedNotitificationReceivers is array of list names which needs to attach the event receivers.

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
string[] listNeedsToAttachedNotitificationReceivers = { "Product", "Order" };
var myAssemblyName = "MyProject.SharePoint";

 

foreach (var listName in listNeedsToAttachedNotitificationReceivers)
{
var web = properties.Feature.Parent as SPWeb;
var list = web.Lists[listName];
SPEventReceiverDefinitionCollection spEventReceiverDefinitionCollection = list.EventReceivers;
if (!IsEventReceiverAlreadyAttached(spEventReceiverDefinitionCollection, myAssemblyName))
{
//Attach three ItemAdded, ItemUpdating and itemUpdated event receivers
SPEventReceiverType eventReceiverType = SPEventReceiverType.ItemAdded;
spEventReceiverDefinitionCollection.Add(eventReceiverType,
Assembly.GetExecutingAssembly().FullName,
"MyProject.SharePoint.Receivers.ListItem.ContentApprovalEventHandler");
eventReceiverType = SPEventReceiverType.ItemUpdated;
spEventReceiverDefinitionCollection.Add(eventReceiverType,
Assembly.GetExecutingAssembly().FullName,
"MyProject.SharePoint.Receivers.ListItem.ContentApprovalEventHandler");
eventReceiverType = SPEventReceiverType.ItemUpdating;
spEventReceiverDefinitionCollection.Add(eventReceiverType,
Assembly.GetExecutingAssembly().FullName,
"MyProject.SharePoint.Receivers.ListItem.ContentApprovalEventHandler");
list.Update();
}
EnusureOldStatusFieldExists(list);
}
}

private static bool IsEventReceiverAlreadyAttached(SPEventReceiverDefinitionCollection spEventReceiverDefinitionCollection, string myAssemblyName)
{
bool eventReceiverAttached = false;
for (int i = 0; i < spEventReceiverDefinitionCollection.Count; i++)
{
if (spEventReceiverDefinitionCollection[i].Assembly.Contains(myAssemblyName))
{
eventReceiverAttached = true;
break;
}
}
return eventReceiverAttached;
}

private static void EnusureOldStatusFieldExists(SPList list)
{
var field = list.Fields.TryGetFieldByStaticName("OldStatus");
if (field == null)
{
list.Fields.Add("OldStatus", SPFieldType.Text, false);
list.Update();
}
}

  • Feature Deactivating Event

In feature deactivating event, unregister the list event receivers. If you want you can delete the OldStatus field. However I have not deleted the field in the code below:

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
string[] listNeedsToAttachedNotitificationReceivers = { "Product", "Order" };
var myAssemblyName = "MyProject.SharePoint";

 


foreach (var listName in listNeedsToAttachedNotitificationReceivers)
{
var receiversToRemove = new List<Guid>();
var web = properties.Feature.Parent as SPWeb;
var list = web.Lists[listName];
SPEventReceiverDefinitionCollection spEventReceiverDefinitionCollection = list.EventReceivers;
for (int i = 0; i < spEventReceiverDefinitionCollection.Count; i++)
{
if (spEventReceiverDefinitionCollection[i].Assembly.Contains(myAssemblyName))
{
receiversToRemove.Add(spEventReceiverDefinitionCollection[i].Id);
}
}
if (receiversToRemove.Count > 0)
{
foreach (var guid in receiversToRemove)
{
list.EventReceivers[guid].Delete();

}
list.Update();
}
}
}

How it works all together?

It’s a bit complex huh? oK, let’s me explain how it works.

  • The feature receiver needs to be activated first. The feature receiver attached the event receiver to list and create a string field OldStatus in the list.
  • Next if an item is added to the list, the listItem event gets fired and if the item status is pending (means needs approval) then send notification.
  • If an existing item is edited and saved then ItemUpdating event is fired. This is the event where the item is not yet saved. So I have put the current approval status in the OldStatus field. In ItemUpated event I have compared the OldStatus and current status field value. If the valued doesn’t match then the approval status is changed and we need to send the notification.


你可能感兴趣的:(handler)