Tuesday, January 13, 2015

Adding Remote Event Receivers to list on Host web + Debugging

In the previous post, we looked at the process of adding and debugging of Remote Event Receiver. The receiver was added to the list, which is set in our Web application.
In this post we will take a look at how to add event receivers to the lists that are in the Host Web.

As an example, we will create a Provider-hosted application (app). This application will allow us to connect the remote event receiver to any list in the Host Web.


First step: create a list (Custom List) and name it Test.


Then create Provider-hosted app in Visual Studio (as in the previous post).
So we created a solution that consists of two projects (app + website).


Now add the Remote Event Receiver. Right click on the project name RERHostDemo -> Add -> New Item ... select Remote Event Receiver.
Let’s name it RERHostReceiver. Then click Add.


Next step: select List Item Events, as a source of Custom List, as well as select the event, on which should trigger our receiver - An item was added. Click Finish


Both of our project will be modified. The project RERHostDemo reveal element RERHostReceiver, then click on Elements.xml to view its contents.


We see that this receiver will be connected to all lists with a template Custom List (ListTemplateId = 100). Now open the second draft RERHostDemoWeb, code file receiver - RERHostReceiver.svc.cs. Now we set up the breakpoint in ProcessOneWayEvent method and run the application in debug mode - press F5 (Do not forget to set the service bus and endpoint for RERHostDemo project. How to do this, see the previous post).
After launching the application, appears the window asking for permission. Click Trust It.


Then we are redirected to the page of our web application. Now this is a normal MVC application without any changes.


Without closing the tab (or Visual Studio will be out of debug mode), open another tab and go to your portal. Open the test list and use it to create the element. Our breakpoint will not work.
Let's take a look at the Test list of remote receiver.
To do this, open the Powershell ISE and run the following code:

 function listEventReceivers([Microsoft.SharePoint.Client.ClientContext]$context, [string]$listName)   
 {  
   $list = $context.Web.Lists.GetByTitle($listName);   
   $context.Load($list)  
   $eventReceivers = $list.EventReceivers  
   $context.Load($eventReceivers)  
   $context.ExecuteQuery()  
   foreach ($er in $eventReceivers)  
   {  
     Write-Host ("Found ER: " + $er.ReceiverName)  
     Write-Host ("ReceiverClass: " + $er.ReceiverClass)  
     Write-Host ("ReceiverAssembly: " + $er.ReceiverAssembly)  
     Write-Host ("EventType: " + $er.EventType)  
     Write-Host ("ReceiverUrl: " + $er.ReceiverUrl)  
     Write-Host ("Synchronization: " + $er.Synchronization)  
   }  
 }  
 $username = "test"   
 $password = "test"   
 $url = "https://test.sharepoint.com"  
 $securePassword = ConvertTo-SecureString $password -AsPlainText -Force  
 $listName = "Test"  
 Add-Type -Path "c:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.dll"   
 Add-Type -Path "c:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Runtime.dll"   
 $clientContext = New-Object Microsoft.SharePoint.Client.ClientContext($url)   
 $credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($username, $securePassword)   
 $clientContext.Credentials = $credentials  
 if (!$clientContext.ServerObjectIsNull.Value)   
 {   
   listEventReceivers $clientContext $listName  
 }   

As the result of the executed code, the screen will be empty, since in the list of Test does not have event receivers (for detailed info about the above code - here)

The point is that the markup in RERHostReceiver (Elements.xml) file connects only to our receiver lists in the app web. Declaratively, connection of remote event receivers to lists on Host Web is not possible.
But we can do it programmatically.
First, open AppManifest.xml file (in RERHostDemo project), then go to the tab Permissions. In order to enable us to add event receivers from the code, it is necessary for user to have the right to Manage Host Web. In the Scope column set Web, and Permissions column Manage value.


Now remove RERHostReceiver element from the RERHostDemo project, and leave the code of the receiver in the project RERHostDemoWeb (we will use it later).

In RERHostDemoWeb project open the file Index.cshtml and replace its contents with the following code:
 @{  
   ViewBag.Title = "Home Page";  
 }  
 <div class="row">  
   @using (Html.BeginForm("Subscribe", "Home", new { SPHostUrl = Request.QueryString["SPHostUrl"] }))  
   {  
   @Html.AntiForgeryToken()  
   <div class="col-md-10">  
     @Html.DropDownList("listTitle", (IEnumerable<SelectListItem>)ViewBag.HostLists)  
     <input type="submit" value="Subscribe" />  
   </div>  
   }  
 </div>  



Now open HomeController.cs file, change the code of the Index method to the following:

 [SharePointContextFilter]  
 public ActionResult Index()  
 {  
   var spContext = SharePointContextProvider.Current.GetSharePointContext(HttpContext);  
   using (var clientContext = spContext.CreateUserClientContextForSPHost())  
   {  
     if (clientContext != null)  
     {  
       var spWeb = clientContext.Web;  
       var hostListColl = spWeb.Lists;  
       clientContext.Load(spWeb, w => w.Id);  
       clientContext.Load(hostListColl);  
       clientContext.ExecuteQuery();  
       ViewBag.HostLists = hostListColl.Select(l => new SelectListItem() { Text = l.Title, Value = l.Title });  
     }  
   }  
   return View();  
 }  

After launching the application, we will see a drop-down list filled with lists of names from Host Web.


By clicking on the Subscribe button, Subscribe action of the Home controller will be triggered. We have not yet written code for it, so click on the button does not make sense.
Let’s go back to Visual Studio. Add new class RERUtility to RERHostDemoWeb project with the following code:

 using Microsoft.SharePoint.Client;  
 using System;  
 using System.Collections.Generic;  
 using System.Linq;  
 using System.Web;  
 namespace RERHostDemoWeb  
 {  
   public class RERUtility  
   {  
     public static void AddListItemRemoteEventReceiver(ClientContext context, string listName, EventReceiverType eventType,  
       EventReceiverSynchronization synchronization, string receiverName,  
       string receiverUrl, int sequence, string receiverAssemblyName = "", string receiverClassName = "")  
     {  
       var list = context.Web.Lists.GetByTitle(listName);  
       context.Load(list);  
       var eventReceivers = list.EventReceivers;  
       context.Load(eventReceivers);  
       context.ExecuteQuery();  
       var newRER = new EventReceiverDefinitionCreationInformation();  
       newRER.EventType = eventType;  
       newRER.ReceiverName = receiverName;  
       newRER.ReceiverClass = receiverClassName;  
       newRER.ReceiverAssembly = receiverAssemblyName;  
       newRER.ReceiverUrl = receiverUrl;  
       newRER.Synchronization = synchronization;  
       newRER.SequenceNumber = sequence;  
       list.EventReceivers.Add(newRER);  
       list.Update();  
       context.ExecuteQuery();  
     }  
   }  
 }  

In this class, we created a method that gets the list by name and adds to it the remote event receiver.
Re-open HomeController.cs file and add the following code:

 [SharePointContextFilter]  
     [HttpPost]  
     public ActionResult Subscribe(string listTitle)  
     {  
       var spContext = SharePointContextProvider.Current.GetSharePointContext(HttpContext);  
       using (var clientContext = spContext.CreateUserClientContextForSPHost())  
       {  
         if (!string.IsNullOrEmpty(listTitle))  
         {  
           RERUtility.AddListItemRemoteEventReceiver(  
             clientContext,  
             listTitle,  
             EventReceiverType.ItemAdded,  
             EventReceiverSynchronization.Asynchronous,  
             "RERHostReceiver",  
             "",  
                 10);  
         }  
       }  
       return RedirectToAction("Index", new { SPHostUrl = spContext.SPHostUrl.ToString() });  
     }  

Pay attention to ("") value in the penultimate parameter in AddListItemRemoteEventReceiver method. In this setting, we need to pass a reference to our receiver, namely, on the service responsible for events processing. If our service was published online, then we would know its address. But what if we want to debug receiver code without publishing (to localhost)?
The answer - we need to register the service address in the service bus.
Select RERHostDemo project and press F4, in the appeared properties window, set True in Handle App Installed.


Visual Studio automatically creates a handler class in the folder Services, which will be triggered when you install our application. Open AppEventReceiver.svc (project RERHostDemoWeb).
Replace the code of the ProcessEvent method to the following:

 public SPRemoteEventResult ProcessEvent(SPRemoteEventProperties properties)  
 {  
    SPRemoteEventResult result = new SPRemoteEventResult();  
    var serviceUrl = System.ServiceModel.OperationContext.Current.Channel.LocalAddress.Uri.ToString();  
    return result;  
 }  

Set the breakpoint on line on the last line. Press F5 to run the solution in debug mode. This will open a browser window with information on the requested rights; click Trust It. Wait until our receiver launches (it may take a few seconds). Once the code execution stops, look at the contents of serviceUrl variable. It contains service address, which is responsible for receiving the event when the application is installing. Copy and save it in notepad.


Exit debug mode, open HomeController.cs file, go to Subscribe method. Replace the value of the ("") penultimate parameter to the service address that you saved in Notepad. At the end of the address, replace AppEventReceiver.svc to the name of your service - RERHostReceiver.svc.
You can safely remove AppEventReceiver service. Also, do not forget to set false value in the property of the RERHostDemo project for Handle App Installed.


The following example creates a receiver that will be triggered asynchronously to the «Item was added» event. Now open RERHostReceiver.svc.cs file and set breakpoint in ProcessOneWayEvent method. Press F5. Select list, to which we want to add our receiver, click Subscribe.
Open another tab in your browser and go to the list, to which we added the receiver. Create a new element. Hooray, our breakpoint load.


ps: Unfortunately, at the moment it is impossible to remove the event receiver that are added «through service bus». An error «Access Denied» occurs. The only way  is to delete the list. Receivers, written by published service (in Azure) can be removed properly. Here you can see examples of code for removing/ adding/etc. event receivers.