在提供程序托管的加载项中处理列表项事件

这是关于开发 SharePoint 托管的 SharePoint 加载项的基础知识系列文章中的第 10 篇文章。你应该首先熟悉 SharePoint 加载项以及本系列中之前的文章(可在创建提供程序托管的 SharePoint 加载项入门中找到相关内容)。

注意

如果已完成有关提供商托管加载项的本系列文章之一,便已生成 Visual Studio 解决方案,可以在继续阅读本主题的过程中使用。 也可以从 SharePoint_Provider-hosted_Add-Ins_Tutorials 下载存储库,再打开 BeforeRER.sln 文件。

本系列中的上一篇文章指出,已下达的订单会被添加到企业数据库的“订单”表中,并且“按预期到货”列表中会自动新增此订单对应的项。 在本地连锁店到货后,用户会将“到货”列设置为“是”。 项的字段值变化会创建项更新事件,可以为此事件添加自定义处理程序。

在本文中,你将为此列表项事件创建处理程序,然后以编程方式将其部署在 SharePoint 外接程序的首次运行逻辑中。 处理程序将项添加到公司数据库中的 Inventory 表中。 然后,它将“预期发货量”列表的“已添加到库存”列设置为“是”。 你还将了解如何防止第二项更新事件触发无限系列项更新事件。

以编程方式部署“按预期到货”列表

注意

只要重新打开解决方案,Visual Studio 中的“启动项目”设置往往会还原为默认值。 重新打开本系列文章中的示例解决方案后,请务必立即按照以下步骤操作:

  1. 右键单击“解决方案资源管理器”最上面的解决方案节点,再选择“设置启动项目”
  2. 确保三个项目都在“操作”列中设置为“启动”
  1. 在“解决方案资源管理器”中,打开“ChainStoreWeb”项目中的 Utilities\SharePointComponentDeployer.cs 文件。 将下列方法添加到 SharePointComponentDeployer 类。

       private static void CreateExpectedShipmentsList()
      {
         using (var clientContext = sPContext.CreateUserClientContextForSPHost())
         {
     	var query = from list in clientContext.Web.Lists
     		    where list.Title == "Expected Shipments"
     		    select list;
     	IEnumerable<List> matchingLists = clientContext.LoadQuery(query);
     	clientContext.ExecuteQuery();
    
     	if (matchingLists.Count() == 0)
     	{
     		ListCreationInformation listInfo = new ListCreationInformation();
     		listInfo.Title = "Expected Shipments";
     		listInfo.TemplateType = (int)ListTemplateType.GenericList;
     		listInfo.Url = "Lists/ExpectedShipments";
     		List expectedShipmentsList = clientContext.Web.Lists.Add(listInfo);
    
     		Field field = expectedShipmentsList.Fields.GetByInternalNameOrTitle("Title");
     		field.Title = "Product";
     		field.Update();
    
     		expectedShipmentsList.Fields.AddFieldAsXml("<Field DisplayName='Supplier'" 
     							    + " Type='Text' />", 
     							    true,
     							    AddFieldOptions.DefaultValue);
     		expectedShipmentsList.Fields.AddFieldAsXml("<Field DisplayName='Quantity'" 
     							    + " Type='Number'" 
     							    + " Required='TRUE' >" 
     							    + "<Default>1</Default></Field>",
     							    true, 
     							    AddFieldOptions.DefaultValue);
     		expectedShipmentsList.Fields.AddFieldAsXml("<Field DisplayName='Arrived'" 
     							   + " Type='Boolean'"
     							   + " ShowInNewForm='FALSE'>"
     							   + "<Default>FALSE</Default></Field>",
     							    true, 
     							    AddFieldOptions.DefaultValue);
     		expectedShipmentsList.Fields.AddFieldAsXml("<Field DisplayName='Added to Inventory'" 
     							    + " Type='Boolean'" 
     							    + " ShowInNewForm='FALSE'>"
     							    + "<Default>FALSE</Default></Field>", 
     							    true, 
     							    AddFieldOptions.DefaultValue);
    
     		clientContext.ExecuteQuery();
     	}
          }
      }
    

    虽然此代码并未引入本系列中上一篇文章没有提到的任何功能,但请注意以下几点:

    • 它将“数量”字段的“必填”属性设置为“TRUE”,这样此字段就必须始终有值。 然后,它将默认值设置为 1。

    • 在“新建项”表单中,“到货”和“添加到库存”字段处于隐藏状态。

    • 理想情况下,“添加到库存”字段也在“编辑项”表单上处于隐藏状态,因为只有当项更新事件处理程序先将项添加到企业的“库存”表后,它才会变为“是”。 由于技术原因(将在后续步骤中进行解释),字段必须在“编辑项”表单中处于可见状态,这样才能在项更新事件处理程序中以编程方式对字段执行写入操作。

  2. 将以下代码行添加到“DeployChainStoreComponentsToHostWeb”方法中,位置为代码行 RemoteTenantVersion = localTenantVersion 正上方。

      CreateExpectedShipmentsList();
    

创建列表项事件接收器

注意

如果已完成本系列文章之一,便已将开发环境配置为支持调试远程事件接收器。 如果尚未完成配置,请先参阅将解决方案配置为支持调试事件接收器,再继续阅读本主题的其他任何内容。

Visual Studio 的 Office 开发人员工具包含“远程事件接收器”项,可添加到 SharePoint 加载项解决方案中。 不过,在本文撰写之时,这个项目项假定列表(将向其注册接收器)位于加载项 Web 上,因此工具会创建加载项 Web,并在其中创建一些 SharePoint 项目。 然而,连锁店加载项的接收器将(在后续步骤中)向主机 Web 上的“按预期到货”列表进行注册,因此加载项不需要加载项 Web。 (若要回顾加载项 Web 和主机 Web 的区别,请参阅 SharePoint 加载项。)

注意

列表和列表项事件接收器称为远程事件接收器 (RER),因为它们的代码对于 SharePoint 为远程状态,即位于云中或者位于 SharePoint 服务器场外部的内部部署服务器中。 但是,触发它们的事件位于 SharePoint 中。

  1. 在“解决方案资源管理器”中,右键单击“ChainStoreWeb”项目中的“服务”文件夹,再依次选择“添加>WCF 服务”

  2. 当出现提示时,将服务命名为“RemoteEventReceiver1”,再选择“确定”

  3. 这些工具创建接口文件、*.svc 文件和代码隐藏文件。 由于不需要接口文件 IRemoteEventReceiver1.cs,因此请删除它。 (工具可能已自动打开它;如果是,请关闭并删除此文件。)

    注意

    在本系列前面的一篇文章中,当为安装和卸载事件创建加载项事件接收器时,Visual Studio 的 Office 开发人员工具向应用清单文件添加了这些接收器的 URL。 列表和列表项事件接收器未在应用清单中进行注册, 而是以编程方式(在提供商托管加载项中)进行注册。 将在后续步骤中执行此操作。

  4. 打开代码隐藏文件 RemoteEventReceiver1.svc.cs。 用以下代码替换它的全部内容。

       using System;
     using System.Collections.Generic;
     using Microsoft.SharePoint.Client;
     using Microsoft.SharePoint.Client.EventReceivers;
     using System.Data.SqlClient;
     using System.Data;
     using ChainStoreWeb.Utilities;
    
     namespace ChainStoreWeb.Services
     {
         public class RemoteEventReceiver1 : IRemoteEventService
         {
     	/// <summary>
     	/// Handles events that occur before an action occurs, 
     	/// such as when a user is adding or deleting a list item.
     	/// </summary>
     	/// <param name="properties">Holds information about the remote event.</param>
     	/// <returns>Holds information returned from the remote event.</returns>
     	public SPRemoteEventResult ProcessEvent(SPRemoteEventProperties properties)
     	{
     	    throw new NotImplementedException();
     	}
    
     	/// <summary>
     	/// Handles events that occur after an action occurs, 
     	/// such as after a user adds an item to a list or deletes an item from a list.
     	/// </summary>
     	/// <param name="properties">Holds information about the remote event.</param>
     	public void ProcessOneWayEvent(SPRemoteEventProperties properties)
     	{
    
     	}
         }
     }
    

    关于此代码,请注意以下几点:

    • 接口 IRemoteEventServiceMicrosoft.SharePoint.Client.EventReceivers 命名空间中进行定义。

    • 虽然连锁店加载项中没有任何“前面发生的”事件要处理,但 IRemoteEventService 接口仍需要使用 ProcessEvent 方法。

  5. 将以下代码添加到 ProcessOneWayEvent 方法。 请注意, ItemUpdated 事件是此示例将处理的唯一事件,因此我们使用的可能是简单的 if 结构,而非 switch。 但事件接收器通常会处理多个事件,因此我们希望您查看以 SharePoint 外接程序开发人员身份在事件处理程序中最常使用的模式。

       switch (properties.EventType)
     {
         case SPRemoteEventType.ItemUpdated:
    
     	// TODO12: Handle the item updated event.
    
     	break;
     }  
    
  6. TODO12 替换为下面的代码。 同样,此处使用的是 switch 结构,尽管简单的 if 结构就够用,这是为了让大家能够了解 SharePoint 事件接收器中的常用模式。

       switch (properties.ItemEventProperties.ListTitle)
     {
         case "Expected Shipments":
    
     	// TODO13: Handle the arrival of a shipment.
    
     	break;
     }
    
  7. 响应到货的代码应执行以下两项操作:

    • 将已到达连锁店的项目添加到公司库存。

    • 将"预期装运"列表上的"已添加到库存"字段设置为"是"。 但是,此操作应该仅在项目已成功添加到库存时执行。

    TODO13 替换为以下代码。 将在后续步骤中创建 TryUpdateInventoryRecordInventoryUpdateLocally 这两种方法。

       bool updateComplete = TryUpdateInventory(properties);
     if (updateComplete)
     {
         RecordInventoryUpdateLocally(properties);
     }
    
  8. 此时,ProcessOneWayEvent 方法应如下所示:

       public void ProcessOneWayEvent(SPRemoteEventProperties properties)
     {
         switch (properties.EventType)
         {
     	case SPRemoteEventType.ItemUpdated:
    
     	    switch (properties.ItemEventProperties.ListTitle)
     	    {
     		case "Expected Shipments":
     		    bool updateComplete = UpdateInventory(properties);
     		    if (updateComplete)
     		    {
     			RecordInventoryUpdateLocally(properties);
     		    }
     		    break;
     	    }
     	    break;
         }          
     }
    
  9. 将下列方法添加到 RemoteEventReceiver1 类。

       private bool TryUpdateInventory(SPRemoteEventProperties properties)
     {
         bool successFlag = false;
    
     	// TODO14: Test whether the list item is changing because the product has arrived
     	// or for some other reason. If the former, add it to the inventory and set the success flag
     	// to true.     
    
         return successFlag;
     }
    
  10. 虽然“按预期到货”列表中有五列,但并不希望处理程序对大多数类别的项更新做出反应。 例如,如果用户纠正供应商名称拼写,则会触发项更新事件,但处理程序不应执行任何操作。 仅当“到货”字段已设置为“是”时,处理程序才应做出反应。

    还需要测试另一个条件。 假设“到货”已设置为“是”,并且项中的产品已添加到库存(即“添加到库存”已设置为“是”)。 但后来用户误操作将商品的“到货”字段更改回“否”,然后为了纠正这个错误,他又将“到货”字段重新设置为“是”。 误操作和纠正操作都会触发项更新事件。 处理程序不会对误操作做出反应,因为它仅在“到货”为“是”时,才会做出反应。不过,处理程序会对将“到货”设置回“是”的纠正操作做出反应。这样一来,相同的产品和数量就会再次被添加到库存中。 在这种情况下,处理程序应仅在“添加到库存”值为“否”时才做出反应。

    因此,处理程序需要知道在用户更新项后这些字段的值是什么。 SPRemoteEventProperties 对象有 ItemEventProperties 属性。 相应地,它也有编入索引的 AfterProperties 属性,用于保留更新项中的字段值。 以下代码使用这些属性来测试处理程序是否应做出反应。 将 TODO14 替换为以下代码。

     var arrived = Convert.ToBoolean(properties.ItemEventProperties.AfterProperties["Arrived"]);
    var addedToInventory = Convert.ToBoolean(properties.ItemEventProperties.AfterProperties["Added_x0020_to_x0020_Inventory"]);
    
    if (arrived && !addedToInventory)
    {
    
       // TODO15: Add the item to inventory
    
       successFlag = true;
    }
    
  11. TODO15 替换为以下代码。

     using (SqlConnection conn = SQLAzureUtilities.GetActiveSqlConnection())
    using (SqlCommand cmd = conn.CreateCommand())
    {
       conn.Open();
       cmd.CommandText = "UpdateInventory";
       cmd.CommandType = CommandType.StoredProcedure;
       SqlParameter tenant = cmd.Parameters.Add("@Tenant", SqlDbType.NVarChar);
       tenant.Value = properties.ItemEventProperties.WebUrl + "/";
       SqlParameter product = cmd.Parameters.Add("@ItemName", SqlDbType.NVarChar, 50);
       product.Value = properties.ItemEventProperties.AfterProperties["Title"]; // not "Product"
       SqlParameter quantity = cmd.Parameters.Add("@Quantity", SqlDbType.SmallInt);
       quantity.Value = Convert.ToUInt16(properties.ItemEventProperties.AfterProperties["Quantity"]);
       cmd.ExecuteNonQuery();
    }
    

    由于这主要是 SQL 和 ASP.NET 编程,因此不会详细介绍,但请注意以下几点:

    • 此代码使用 ItemEventProperties.WebUrl 属性获取租户名称,即主机 Web URL。

    • 此代码再次使用 AfterProperties 获取产品名称和产品数量的值。

    • 我们将产品名称字段称为“Title”,尽管 在 CreateExpectedShipmentsList 方法中,显示名称已更改为“Product” () ,因为字段始终由其内部名称引用。

  12. 虽然 TryUpdateInventory 方法尚未完成,但此时应如下所示。

     private bool TryUpdateInventory(SPRemoteEventProperties properties)
    {
       bool successFlag = false;
    
       var arrived = Convert.ToBoolean(properties.ItemEventProperties.AfterProperties["Arrived"]);
       var addedToInventory = Convert.ToBoolean(properties.ItemEventProperties.AfterProperties["Added_x0020_to_x0020_Inventory"]);
    
       if (arrived && !addedToInventory)
       {
    	using (SqlConnection conn = SQLAzureUtilities.GetActiveSqlConnection())
    	using (SqlCommand cmd = conn.CreateCommand())
    	{
    	    conn.Open();
    	    cmd.CommandText = "UpdateInventory";
    	    cmd.CommandType = CommandType.StoredProcedure;
    	    SqlParameter tenant = cmd.Parameters.Add("@Tenant", SqlDbType.NVarChar);
    	    tenant.Value = properties.ItemEventProperties.WebUrl + "/";
    	    SqlParameter product = cmd.Parameters.Add("@ItemName", SqlDbType.NVarChar, 50);
    	    product.Value = properties.ItemEventProperties.AfterProperties["Title"]; // not "Product"
    	    SqlParameter quantity = cmd.Parameters.Add("@Quantity", SqlDbType.SmallInt);
    	    quantity.Value = Convert.ToUInt16(properties.ItemEventProperties.AfterProperties["Quantity"]);
    	    cmd.ExecuteNonQuery();
    	}            
    	successFlag = true;
       }  
       return successFlag;
    }
    
  13. TryUpdateInventory 方法返回 true 时,处理程序便会调用方法(还不是写入),将“添加到库存”字段设置为“是”,从而更新“按预期到货”列表中的相同项。 这本身就是项更新事件,因此会再次调用处理程序。 (实际上,“添加到库存”字段现为“是”,可防止处理程序将同一商品再次添加到库存,但仍会调用处理程序。)

    当项目更新事件由编程更新触发时,SharePoint 的行为略有不同: 它仅在 AfterProperties 中包含更新中更改的字段。 因此,“ 已到达 ”字段将不存在,因为只有 “已添加到库存” 字段已更改。

    代码行...

    var arrived = Convert.ToBoolean(properties.ItemEventProperties.AfterProperties["Arrived"]);

    ...抛出“KeyNotFoundException”

    有多种方法可以解决此问题。 在此示例中,我们将捕获异常并使用 catch 块,以确保 successFlag 已设置为 false。 执行此操作可确保项目不会更新第三次。

    将方法中第一行 bool successFlag = false; 与最后一行 return successFlag; 之间的全部内容都添加到“try”块中。

  14. 将以下“catch”块添加到“try”块正下方。

     catch (KeyNotFoundException)
    {
       successFlag = false;
    }
    

    注意

    KeyNotFoundException 也是我们必须在“编辑项”窗体上保持“已添加到库存”字段可见的原因。 SharePoint does not include fields that are hidden on the Edit Item form in AfterProperties.

  15. 此时,整个方法应如下所示。

     private bool TryUpdateInventory(SPRemoteEventProperties properties)
    {
       bool successFlag = false;
    
       try 
       {
    	var arrived = Convert.ToBoolean(properties.ItemEventProperties.AfterProperties["Arrived"]);
    	var addedToInventory = Convert.ToBoolean(properties.ItemEventProperties.AfterProperties["Added_x0020_to_x0020_Inventory"]);
    
    	if (arrived && !addedToInventory)
    	{
    	    using (SqlConnection conn = SQLAzureUtilities.GetActiveSqlConnection())
    	    using (SqlCommand cmd = conn.CreateCommand())
    	    {
    		conn.Open();
    		cmd.CommandText = "UpdateInventory";
    		cmd.CommandType = CommandType.StoredProcedure;
    		SqlParameter tenant = cmd.Parameters.Add("@Tenant", SqlDbType.NVarChar);
    		tenant.Value = properties.ItemEventProperties.WebUrl + "/";
    		SqlParameter product = cmd.Parameters.Add("@ItemName", SqlDbType.NVarChar, 50);
    		product.Value = properties.ItemEventProperties.AfterProperties["Title"]; // not "Product"
    		SqlParameter quantity = cmd.Parameters.Add("@Quantity", SqlDbType.SmallInt);
    		quantity.Value = Convert.ToUInt16(properties.ItemEventProperties.AfterProperties["Quantity"]);
    		cmd.ExecuteNonQuery();
    	    }            
    	    successFlag = true;
    	}  
       }
       catch (KeyNotFoundException)
       {
    	successFlag = false;
       }
       return successFlag;
    }
    
  16. 将下列方法添加到 RemoteEventReceiver1 类。

     private void RecordInventoryUpdateLocally(SPRemoteEventProperties properties)
    {
       using (ClientContext clientContext = TokenHelper.CreateRemoteEventReceiverClientContext(properties))
       {
    	List expectedShipmentslist = clientContext.Web.Lists.GetByTitle(properties.ItemEventProperties.ListTitle);
    	ListItem arrivedItem = expectedShipmentslist.GetItemById(properties.ItemEventProperties.ListItemId);
    	arrivedItem["Added_x0020_to_x0020_Inventory"] = true;
    	arrivedItem.Update();
    	clientContext.ExecuteQuery();
       }
    }
    

    至此,这还是大家在本系列的前面几篇文章中熟知的代码模式。 但请注意以下一点不同之处:

    • 此代码通过调用 TokenHelper.CreateRemoteEventReceiverClientContext 方法(而不是 SharePointContext.CreateUserClientContextForSPHost 方法)获取 ClientContext 对象,而通过页面(如 EmployeeAdder 页面)调用 SharePoint 的代码则使用了后一种方法。

    • 使用不同方法获取 ClientContext 对象的主要原因是,SharePoint 向事件接收器传递创建此类对象所需信息的方式不同于将这些信息传递到页面的方式。 对于事件接收器,它传递的是 SPRemoteEventProperties 对象;而对于页面,它则是通过启动加载项页面的请求的正文,传递称为“上下文令牌”的特殊字段。

  17. 保存并关闭接收器代码文件。

注册接收器

最后一个任务是告诉 SharePoint 我们有一个自定义的接收器,我们希望只要"预期装运"列表上的项目有更新时,SharePoint 即调用此接收器。

  1. 打开 SharePointComponentDeployer.cs 文件,将以下代码行添加到“DeployChainStoreComponentsToHostWeb”方法中,位置为创建“按预期到货”列表的代码行的正下方(将在下一步中添加此方法)。 请注意,向此方法传递的是 HttpRequest 对象,即加载项起始页向 DeployChainStoreComponentsToHostWeb 方法传递的对象。

      RegisterExpectedShipmentsEventHandler(request);
    
  2. 将下列方法添加到 SharePointComponentDeployer 类。

       private static void RegisterExpectedShipmentsEventHandler(HttpRequest request)
     {
         using (var clientContext = sPContext.CreateUserClientContextForSPHost())    
         {
     	var query = from list in clientContext.Web.Lists
     		    where list.Title == "Expected Shipments"
     		    select list;
     	IEnumerable<List> matchingLists = clientContext.LoadQuery(query);
     	clientContext.ExecuteQuery();
    
     	List expectedShipmentsList = matchingLists.Single();
    
     	// TODO16: Add the event receiver to the list's collection of event receivers.       
    
     	clientContext.ExecuteQuery();
         }
     }
    
  3. TODO16 替换为以下代码行。 请注意,事件接收器有轻型 CreationInformation 类,就像列表和列表项也有一样。

     EventReceiverDefinitionCreationInformation receiver = new EventReceiverDefinitionCreationInformation();
     receiver.ReceiverName = "ExpectedShipmentsItemUpdated";
     receiver.EventType = EventReceiverType.ItemUpdated;
    
      // TODO17: Set the URL of the receiver.
    
     expectedShipmentsList.EventReceivers.Add(receiver);
    
    
  4. 现在,需要向 SharePoint 告知事件接收器 URL。 在生产中,接收器将与远程页面位于同一域中,但使用路径 /Services/RemoteEventReceiver1.svc。 由于处理程序是要通过加载项起始页在首次运行逻辑中进行注册,因此域位于页面调用请求的 HttpRequest 对象的主机头中。 代码已将相应对象从页面传递到了 DeployChainStoreComponentsToHostWeb 方法,此方法本身又将它传递到了 RegisterExpectedShipmentsEventHandler 方法。 因此,可以使用下列代码设置接收器 URL。

    receiver.ReceiverUrl = "https://" + request.Headers["Host"] + "/Services/RemoteEventReceiver1.svc";

    遗憾的是,若要通过 Visual Studio 调试加载项,这样做就不起作用了。 调试时,接收器托管在 Azure 服务总线中,而不是托管远程页面的 localhost URL 中。 这样就需要为接收器设置独特的 URL,但具体还是取决于是否要调试。所以,将 TODO17 替换为下列使用 C# 编译器指令的结构。 请注意,在调试模式下,接收器 URL 是通过 web.config 设置(将在后续步骤中创建此设置)进行读取。

       #if DEBUG
     		    receiver.ReceiverUrl = WebConfigurationManager.AppSettings["RERdebuggingServiceBusUrl"].ToString();
     #else
     		    receiver.ReceiverUrl = "https://" + request.Headers["Host"] + "/Services/RemoteEventReceiver1.svc"; 
     #endif
    
    
  5. 此时,整个 RegisterExpectedShipmentsEventHandler 方法应如下所示。

       private static void RegisterExpectedShipmentsEventHandler(HttpRequest request)
     {    
         using (var clientContext = sPContext.CreateUserClientContextForSPHost())
         {
     	var query = from list in clientContext.Web.Lists
     			    where list.Title == "Expected Shipments"
     			    select list;
     	IEnumerable<List> matchingLists = clientContext.LoadQuery(query);
     	clientContext.ExecuteQuery();
    
     	List expectedShipmentsList = matchingLists.Single();
    
     	EventReceiverDefinitionCreationInformation receiver = new EventReceiverDefinitionCreationInformation();
     	receiver.ReceiverName = "ExpectedShipmentsItemUpdated";
     	receiver.EventType = EventReceiverType.ItemUpdated;
    
     #if DEBUG
     	receiver.ReceiverUrl = WebConfigurationManager.AppSettings["RERdebuggingServiceBusUrl"].ToString();
     #else
     	receiver.ReceiverUrl = "https://" + request.Headers["Host"] + "/Services/RemoteEventReceiver1.svc"; 
     #endif
     	expectedShipmentsList.EventReceivers.Add(receiver);
     	clientContext.ExecuteQuery();
         }
     }
    
  6. 将以下“using”语句添加到文件最上面。

      using System.Web.Configuration;
    
  7. 为了确保当且仅当要调试加载项时 DEBUG 才为 true,请执行以下子过程:

    1. 在“解决方案资源管理器”中,右键单击“ChainStoreWeb”项目,再选择“属性”

    2. 打开“属性”的“生成”选项卡,再从最上面的“配置”下拉列表中选择“调试”

    3. 确保已选中“定义 DEBUG 常量”复选框(通常默认处于选中状态)。 下面的屏幕截图展示了正确的设置。

      图 1. Visual Studio 中“属性”选项卡的“生成”子选项卡

      Visual Studio 中“属性”选项卡的“生成”子选项卡。“配置”下拉列表设置为“调试”。“定义 DEBUG 常量”复选框处于选中状态。

    4. 将“配置”下拉列表更改为“发布”,再确保已取消选中“定义 DEBUG 常量”复选框(通常默认处于取消选中状态)。 下面的屏幕截图展示了正确的设置。

      图 2. 清除复选框的“属性”选项卡的“生成”子选项卡

      “属性”选项卡的“生成”子选项卡。“配置”下拉列表设置为“发布”。“定义 DEBUG 常量”复选框处于取消选中状态。

    5. 若有任何更改,请保存并关闭“属性”选项卡。

  8. 打开 web.config 文件,将以下标记添加为“appSettings”元素的子项(将在下一部分中获取此设置的值)。

      <add key="RERdebuggingServiceBusUrl" value="" />
    

获取接收器 URL 以供调试

加载项事件接收器和列表项事件接收器为 Windows Communication Service (WCF) 服务。每个 WCF 服务都知道自己的终结点,并将它存储在多个位置,包括 System.ServiceModel.OperationContext.Current.Channel.LocalAddress.Uri 对象。

调试时,加载项接收器托管在 Azure 服务总线终结点上,这与列表项接收器的终结点几乎相同。 区别在于外接程序终结点的 URL 以“AppEventReceiver.svc”结尾,但列表项接收器的 URL 以“RemoteEventReceiver1.svc”结尾。因此,我们可以在外接程序接收器中获取终结点的 URL,对终结点的末尾稍作更改,然后将其用作 web.config RERdebuggingServiceBusUrl 设置的值。

  1. 打开“ChainStoreWeb”项目的“服务”文件夹中的 AppEventReceiver.svc.cs 文件。

  2. 将以下内容添加为"ProcessEvent"方法的第一行。

      string debugEndpoint = System.ServiceModel.OperationContext.Current.Channel.LocalAddress.Uri.ToString(); 
    
  3. 就在此方法的下一行中添加断点。

  4. 按 F5 调试加载项。 由于 web.config 处于打开状态,且每次按 F5 时 Visual Studio 的 Office 开发人员工具都会更改其中的设置,因此会看到重新加载文件的提示。 选择“是”

  5. 到达断点时,将光标悬停在 debugEndpoint 变量之上。 当 Visual Studio 数据提示显示时,按向下箭头,再选择“文本可视化工具”

    图 3. 具有Azure 服务总线 URL 的 Visual Studio 文本可视化工具

    Visual Studio 文本可视化工具及其中的一个 Azure 服务总线 URL。

  6. 从可视化工具中复制字符串值并将其粘贴在某个位置。

  7. 关闭可视化工具,然后停止在 Visual Studio 中调试。

  8. 删除或注释掉您在此过程第二步添加的行,然后一并删除断点。

  9. 将复制的字符串末尾处的“AppEventReceiver.svc”替换为“RemoteEventReceiver1.svc”。

  10. 复制并粘贴修改后的 URL,作为 web.config 文件中 RERdebuggingServiceBusUrl 键的值。

注意

调试在生产环境下运行的远程事件接收器时,如果需要不同的 URL,方法并不仅限于手动复制服务总线 URL ,并(将修改后的版本)粘贴到 web.config。 我们可以编程方式将 System.ServiceModel.OperationContext.Current.Channel.LocalAddress.Uri 的值存储在 SharePoint 或远程数据库中的某个位置,然后由我们的首次运行代码读取它并分配给 receiver.ReceiverUrl 属性。 可以将列表项事件接收器注册为加载项安装事件处理程序的一部分。 然后,可以编程方式读取并修改 System.ServiceModel.OperationContext.Current.Channel.LocalAddress.Uri,再将它分配给 receiver.ReceiverUrl,而无需将它存储到任意位置。

若要采用这种策略,“按预期到货”列表必须也是在加载项安装事件处理程序中进行创建,因为必须有此列表,才能向列表注册处理程序。

另请注意,可以将加载项事件接收器和列表项事件接收器合并为一个接收器(即相同的 .svc 和 .svc.cs 文件)。 在这种情况下,无需修改 URL,即可将它用作 receiver.ReceiverUrl 的值。

运行加载项并测试列表项接收器

  1. 打开香港店网站的“网站内容”页,再删除“按预期到货”列表(若有)。

  2. 按 F5 键部署并运行加载项。 Visual Studio 在 IIS Express 中托管远程 Web 应用,并在 SQL Express 中托管 SQL 数据库。 此外,它还在测试 SharePoint 网站上临时安装并立即运行加载项。 在加载项的起始页打开前,将会看到向加载项授予权限的提示。

  3. 在加载项的起始页打开后,选择最上面部件版式控制中的“返回到网站”按钮。

  4. 在香港店的主页中,转到“网站内容”页面,再打开“按预期到货”列表。

  5. 新建一项。请注意,“新建项”表单上不显示“到货”和“添加到库存”字段。

  6. 新建项后,重新打开它进行编辑。 选中“到货”复选框,再保存项。 这会触发项更新事件。 此时,项会添加到库存,并且“添加到库存”字段值会变为“是”(可能需要刷新页面,才能看到“添加到库存”值变化)。

  7. 单击浏览器的“后退”按钮,直到返回到连锁店加载项的起始页,再选择“显示库存”按钮。 此时,其中列出了标记为“到货”的项。

  8. 返回到“按预期到货”列表,并再添加一项:产品名称和供应商名称完全相同,但数量不同。

  9. 创建项目后,重新打开它以进行编辑。 将“已到达”的值更改为“是”并保存该项目。

  10. 单击浏览器的“后退”按钮,直到返回到连锁店加载项的起始页,再选择“显示库存”按钮。 此产品名称和供应商仍只对应有一项,但数量现在是“按预期到货”列表上两个项的数量之和。

  11. 若要结束调试会话,请关闭浏览器窗口或停止在 Visual Studio 中进行调试。 每次按 F5,Visual Studio 都会撤回旧版加载项并安装最新版本。

  12. 将在其他文章中使用此加载项和 Visual Studio 解决方案,因此最好在使用一段时间后,再最后撤回一次加载项。 在“解决方案资源管理器”中,右键单击此项目,再选择“撤回”

后续步骤

若要了解如何将加载项发布到 SharePoint 网站,请参阅部署和安装 SharePoint 加载项:方法和选项。 还可以继续参阅下列主题,了解如何在 SharePoint 加载项开发过程中执行更多高级操作: