Kirk Evans Blog

.NET From a Markup Perspective

Attaching Remote Event Receivers to Lists in the Host Web

Attaching Remote Event Receivers to Lists in the Host Web

Rate This
  • Comments 9

This post shows how to attach a remote event receiver to a list in the host web for a SharePoint provider-hosted app.

Background

While working on the SharePoint 2013 Ignite content, apps were still very much new and very little documentation existed.  We were fighting a problem of using an app that required a feature to first be activated in SharePoint.  How could we activate the feature?  That’s when Keenan Newton wrote his blog post, Defining content in Host Web from an App for SharePoint.  The idea was so simple: use the App Installed event. 

This post follows the same pattern.  I am going to take a pretty long path to do something that can be accomplished pretty quickly because there are a few confusing elements to this pattern:

  1. Handle the app installed event.
  2. When the app installed event occurs, an event is sent to our service.  We use the client side object model to attach an event receiver to a list in the host web.
  3. When an item is added to the list, an ItemAdded event is sent to our service.

Visually, it looks like this:

image

Once you understand this pattern, you’ll use it for all sorts of things such as activating features, creating subsites, applying themes, all kinds of stuff.

If you don’t care about how it all works, just skip to the end to the section “Show Me The Code!”

Remote Event Receivers

A remote event receiver is just like a traditional event receiver in SharePoint.  Your code registers itself with SharePoint to be called whenever an event occurs, such as a list is being deleted or a list item is being added.  With full trust code solutions, you would register your code by giving SharePoint an assembly name and type name.  Server side code for apps isn’t installed on SharePoint, but rather on your own web server, so how would you register a remote endpoint?  Provide a URL to a service. 

image

If you aren’t familiar with remote event receivers, go check out the Developer training for Office, SharePoint, Project, Visio, and Access Services which includes a module on remote event receivers. 

The point that I want to highlight here is that you tell SharePoint what WCF service endpoint to call when a specific event occurs.  That means that SharePoint needs to be able to resolve the address to that endpoint.

Handle App Installed and App Uninstalling

To perform this step, I assume you already have an Office 365 Developer Site Collection.  If you don’t, you can sign up for a free 30-day trial.  Even better, as an MSDN subscriber you get an Office 365 developer tenant as part of your MSDN benefits.

In Visual Studio 2013, create a new provider-hosted app. 

image

Provide the URL for your Office 365 developer site, used for debugging, and leave the host type as Provider-hosted.

image

The next screen asks if you want to use the traditional Web Forms model for your app, or if you prefer ASP.NET MVC.  I really love ASP.NET MVC, so I’ll use that option.

image

Finally, you are asked about how your app will authenticate.  We are using Office 365, so leave the default option, “Use Windows Azure Access Control Service”.  Click Finish.

image

Once your project is created, click on the app project (not the web project) and change its Handle App Installed and HandleAppUninstalling properties to True.

image

That will create a WCF service for you in the project where you can now handle an event for when the app is installed.

image

There are two methods, ProcessEvent and ProcessOneWayEvent, and sample code exists in the ProcessEvent method to show you how to get started with a remote event receiver. 

We are going to use the ProcessEvent method to register an event receiver on a list in the host web.  We will also use the ProcessEvent method to unregister the remote event receiver when the app is uninstalled.  Clean up after yourself!

Add a breakpoint in the ProcessEvent method, but don’t hit F5 just yet.

Debugging Remote Event Receivers

Let me restate that last part if you didn’t catch it: SharePoint needs to be able to resolve the address to your WCF endpoint.  Let me change that picture just a bit:

image

See the difference?  Here we have Office 365 calling our web service.  If we told O365 that our WCF service was available at http://localhost:44307/AppEventReceiver.svc, that server would try to make an HTTP call to localhost… the HTTP call would never leave that server.  There’s no way that SharePoint can figure out that what you really meant was to traverse your corporate firewall and get past the Windows Firewall on your laptop to call an HTTP endpoint in IIS Express.

Thankfully, someone incredibly smart on the Visual Studio team (hat’s off, Chaks!) figured out how to use Windows Azure Service Bus to debug remote events.  That means that SharePoint now has an endpoint that it can deliver messages to, and your app can then connect to service bus to receive those messages.

image

Even better, you really don’t have to know much about this to make it all work.  If you don’t have an Azure subscription already, you can sign up for a free trial.  If you have MSDN, you get an Azure subscription as part of your MSDN benefits that includes monthly credits!  If you are worried about the cost here, don’t be: as of today, you are charged $0.10 for every 100 relay hours, $0.01 for every 10,000 messages.  I seriously doubt anyone is leaving their machine debugging for that long.

Once you have an Azure subscription, log into the Windows Azure Management Portal.  Go to the Service Bus extension on the left of the screen.

image

On the bottom of the screen, click Create to add a new namespace. 

image

Give it a unique name and provide a location near you.

image

Once the namespace is created, click the Connection Information button, you will see your connection string. Copy it into your clipboard buffer.

image

Go back to Visual Studio.  In the Solution Explorer, click the app project (not the web project) in Visual Studio’s Solution Explorer pane, then go to Project / AttachEventsInHostWeb Properties…

image

Go to the SharePoint tab and check the checkbox to enable debugging via Windows Service Bus and paste your connection string. 

image

Now, let’s test our app so far.  Press F5 in Visual Studio to start debugging.

image

Our breakpoint is then hit.  Let’s inspect where the WCF message was sent to. In the Watch window in Visual Studio, add the value System.ServiceModel.OperationContext.Current.RequestContext.RequestMessage.Headers.To

image

You can see that SharePoint Online sent the message to:

https://kirkevans.servicebus.windows.net/2228577862/1147692368/obj/0958b186-260a-4fb4-a140-7437d6f2b686/Services/AppEventReceiver.svc

This is the service bus endpoint used during debugging.  This solves our earlier problem of SharePoint not being able to send messages to https://localhost:44307.  The messages are relayed from Service Bus to our local endpoint.

Ask for Permission

The last bit of setup that we need to do is to ask for permission.  We are going to add a remote event receiver to a list in the host web, which means we need to ask for permission to manage the list.  We don’t need Full Control for this operation, we just need Manage.  Further, we only need Manage permission for a list, not the whole web, site collection, or tenant.

The list we will work with is an Announcements list, which has a template ID of 104.  Adding the BaseTemplateId=104 property in a list permission request significantly reduces the number and type of lists that a user chooses from when granting permission.

image

Notice the app-only permission request?  That’s added when we handle the App Installed and App Uninstalling events, because when those happen we want to execute operations that the current user may not have permission to. 

Show Me The Code!

Finally, we’re here.  First, let’s define the name of the event handler and implement the required ProcessEvent method.

private const string ReceiverName = "ItemAddedEvent";
private const string ListName = "Announcements";

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

    switch (properties.EventType)
    {
        case SPRemoteEventType.AppInstalled:
            HandleAppInstalled(properties);
            break;
        case SPRemoteEventType.AppUninstalling:
            HandleAppUninstalling(properties);
            break;
        case SPRemoteEventType.ItemAdded:
            HandleItemAdded(properties);
            break;
    }

            
    return result;
}

Those methods (HandleAppInstalled, HandleAppUninstalling, HandleItemAdded) are methods that we will define. 

   1:  private void HandleAppInstalled(SPRemoteEventProperties properties)
   2:  {
   3:      using (ClientContext clientContext =
   4:          TokenHelper.CreateAppEventClientContext(properties, false))
   5:      {
   6:          if (clientContext != null)
   7:          {
   8:              List myList = clientContext.Web.Lists.GetByTitle(ListName);
   9:              clientContext.Load(myList, p => p.EventReceivers);
  10:              clientContext.ExecuteQuery();
  11:   
  12:              bool rerExists = false;
  13:   
  14:              foreach (var rer in myList.EventReceivers)
  15:              {                        
  16:                  if (rer.ReceiverName == ReceiverName)
  17:                  {
  18:                      rerExists = true;
  19:                      System.Diagnostics.Trace.WriteLine("Found existing ItemAdded receiver at " 
  20:                          + rer.ReceiverUrl);
  21:                  }
  22:              }
  23:   
  24:              if (!rerExists)
  25:              {
  26:                  EventReceiverDefinitionCreationInformation receiver =
  27:                      new EventReceiverDefinitionCreationInformation();
  28:                  receiver.EventType = EventReceiverType.ItemAdded;
  29:   
  30:                  //Get WCF URL where this message was handled
  31:                  OperationContext op = OperationContext.Current;
  32:                  Message msg = op.RequestContext.RequestMessage;
  33:   
  34:                  receiver.ReceiverUrl = msg.Headers.To.ToString();
  35:   
  36:                  receiver.ReceiverName = ReceiverName;
  37:                  receiver.Synchronization = EventReceiverSynchronization.Synchronous;
  38:                  myList.EventReceivers.Add(receiver);
  39:   
  40:                  clientContext.ExecuteQuery();
  41:   
  42:                  System.Diagnostics.Trace.WriteLine("Added ItemAdded receiver at "
  43:                          + msg.Headers.To.ToString());
  44:              }
  45:          }
  46:      }
  47:  }

Lines 8-10 just get the list and the event receivers for the list using the client side object model.  The real work is in lines 24-38 where we obtain the WCF address of where the message was originally sent to and use that URL for our new event receiver.  This is how we add a remote event receiver to a list in the host web.

We need to clean up after ourselves, otherwise we may continue to receive messages after someone has uninstalled the app.

   1:  private void HandleAppUninstalling(SPRemoteEventProperties properties)
   2:  {
   3:      using (ClientContext clientContext =
   4:          TokenHelper.CreateAppEventClientContext(properties, false))
   5:      {
   6:          if (clientContext != null)
   7:          {
   8:              List myList = clientContext.Web.Lists.GetByTitle(ListName);
   9:              clientContext.Load(myList, p => p.EventReceivers);
  10:              clientContext.ExecuteQuery();
  11:   
  12:              var rer = myList.EventReceivers.Where(
  13:                  e => e.ReceiverName == ReceiverName).FirstOrDefault();
  14:   
  15:              try
  16:              {
  17:                  System.Diagnostics.Trace.WriteLine("Removing ItemAdded receiver at "
  18:                          + rer.ReceiverUrl);
  19:   
  20:                  //This will fail when deploying via F5, but works
  21:                  //when deployed to production
  22:                  rer.DeleteObject();
  23:                  clientContext.ExecuteQuery();
  24:                          
  25:              }
  26:              catch (Exception oops)
  27:              {                            
  28:                  System.Diagnostics.Trace.WriteLine(oops.Message);                            
  29:              }
  30:                          
  31:          }
  32:      }
  33:  }

Now let’s handle the ItemAdded event.

   1:  private void HandleItemAdded(SPRemoteEventProperties properties)
   2:  {
   3:      using (ClientContext clientContext =
   4:          TokenHelper.CreateRemoteEventReceiverClientContext(properties))
   5:      {
   6:          if (clientContext != null)
   7:          {
   8:              try
   9:              {                        
  10:                  List photos = clientContext.Web.Lists.GetById(
  11:                      properties.ItemEventProperties.ListId);
  12:                  ListItem item = photos.GetItemById(
  13:                      properties.ItemEventProperties.ListItemId);
  14:                  clientContext.Load(item);
  15:                  clientContext.ExecuteQuery();
  16:   
  17:                  item["Title"] += "\nUpdated by RER " +
  18:                      System.DateTime.Now.ToLongTimeString();
  19:                  item.Update();
  20:                  clientContext.ExecuteQuery();
  21:              }
  22:              catch (Exception oops)
  23:              {                        
  24:                  System.Diagnostics.Trace.WriteLine(oops.Message);
  25:              }
  26:          }
  27:   
  28:      }
  29:   
  30:  }

I need to point out line 4.  TokenHelper has two different methods for creating a client context for an event.  The first is CreateAppEventClientContext, which is used for app events such as AppInstalled or AppUninstalling.  The second is CreateRemoteEventReceiverClientContext, which is used for all other events.  This has tripped me up on more than one occasion, make sure to use the CreateRemoteEventReceiverClientContext method for handling item events.

That’s really all there is to it… we use the AppInstalled event to register an event on a list in the host web, use the same WCF service to handle the event.  These operations require Manage permission on the object where the event is being added.

Testing it Out

We’ve gone through the steps of creating the app and adding the service bus connection string, let’s see the code work!  Add breakpoints to each of your private methods in the WCF service and press F5 to see it work.

We are prompted to trust the app.  Notice that only the announcements lists in the host web show in the drop-down.

image

Click Trust It.  A short time later, the breakpoint in the HandleAppInstalled method fires.  We continue debugging, and then O365 prompts us to log in. 

Your app’s main entry point is then shown.

image

Without closing the browser (which would stop your debugging session), go back to your SharePoint site.  Go to the Announcements list and add a new announcement.

image

W00t!  Our breakpoint for the ItemAdded event is then hit!

image

If you want to inspect the properties of the remote event receiver that was attached, you can use Chris O’Brien’s scripts from his post, Add/delete and list Remote Event Receivers with PowerShell/CSOM:

image 

Debugging and the Handle App Uninstalling Event

Recall that we will be using the App Installed event to register a remote event receiver on a list in the host web.  We want to also remove the remote event receiver from the list.  If we try to use the AppUninstalling event and unregister the event using DeleteObject(), it doesn’t work.  You will consistently receive an error saying you don’t have permissions.  This only happens when side-loading the app, which is what happens when you use F5 to deploy the solution with Visual Studio. 

Unfortunately, that means that the receivers that are registered for the list hang around.  The only way to get rid of them is to delete the list.  Again, this only occurs when side-loading the apps, it doesn’t happen when the app is deployed.

To see the App Uninstalling event work, we are going to need to deploy our app.

Deploy to Azure and App Catalog

In my previous post, Creating a SharePoint 2013 App With Azure Web Sites, I showed how to create an Azure web site, go to AppRegNew.aspx to create a client ID, and a client secret.  I then showed how to publish the app to an Azure web site, and package the app to generate the .app package.  I did the same here, deploying the web application to an Azure web site called “rerdemo”. 

image

Instead of copying the .app package to a Developer Site Collection, we are instead going to copy the .app package to our App Catalog for our tenant.  Just go to the Apps for SharePoint library and upload the .app package.

image

Now go to a SharePoint site that you want to deploy the app to.  Make sure to create an Announcements list.  Our app could have done this in the App Installed event, but c’mon, this post is long enough as it is.  I’ll leave that as an exercise to the reader.

image

Before we add the app to the site, let’s see something incredibly cool.  Go to the Azure web site in Visual Studio, right-click and choose Settings, and turn up logging for everything.

image

Click save.

Right-click the Azure web site and choose View Streaming Logs in Output Window.  You’ll be greeted with a friendly message.

image

Now go back to your SharePoint site and choose add an app.  You should now see your app as one of the apps that can be installed.

image

Click Trust It.

image

Your app will show that it is installing.

image

Go back to Visual Studio and look at the streaming output logs.

image

OMG.  I don’t know about you, but I nearly wet myself when I saw that.  That is so unbelievably cool.  Let’s keep playing to see what other messages show up.  Go to the Announcements list and add an item.

image

Shortly after clicking OK, you’ll see the Title has changed.

image

Finally, uninstall the app to test our HandleAppUninstalling method.

image

We see a new message that the remote event receiver is being removed.

image

And we can again use Chris O’Brien’s PowerShell script to check if there are any remote event receivers still attached to the Announcements list.

image

Now, go back to Visual Studio.  Right-click on the Azure web site and choose Settings.  Go to the Logs tab and choose Download Logs.

image

A file is now in my Downloads folder.

image

I can double-click on the zip file to navigate into it.  Go to LogFiles / http / RawLogs and see the log file that is sitting there.  Double-click it.  You can see the IIS logs for your site!

image

For More Information

Developer training for Office, SharePoint, Project, Visio, and Access Services

Defining content in Host Web from an App for SharePoint

Add/delete and list Remote Event Receivers with PowerShell/CSOM

Streaming Diagnostics Trace Logging from the Azure Command Line (plus Glimpse!)

Creating a SharePoint 2013 App With Azure Web Sites

  • Hi Kirk,

    I'm quite new to all of this SharePoint development, but this is great. I've followed the process and have it working fine.

    The only problem is that when an item is added to my Custom List I want to add the functionality to start a workflow already associated with the list.

    Lots of other examples on the web utilize Client.WorkflowServices however this doesnt seem to be available in my project.

    Have you any idea how that can be incorporated into your example above?

    Best regards

  • Hi Kirk,

    I'm struggling with similar scenario.

    I have a local machine with local installation of SharePoint 2013. I create a SharePoint hosted app. Within that I created a Remote Event Handler which further created another web project. I also added the App Install event into the same web project.

    Now I hosted (Published Web Project) Remote Event Handler and App Install Handler in local IIS with Anonymous login. Also I generated .app file and uploaded to app catalog.

    Now when I try to install my app, I attach my code to the w3p process and it breaks into my App Event Handler however TokenHelper.CreateAppEventClientContext(properties, false) is returning null. I further debugged to find that ContextTokenString is empty...I'm not sure however I feel this is happening due to anonymous login?

    Any help really appreciated..

    Thanks

    Manu

  • Manu - most often I've seen this issue when the app endpoint is incorrectly using HTTP instead of HTTPS.  SSL is required.

  • Hi Kirk,

    I've essentially followed your example to create remote event receivers. I'm able to debug them fine. The new event receivers also fire fine.....*only* during the first debug session that they are attached to the list in. On subsequent debugs, the list event receivers have already been added, but they don't fire. The App Installed event receivers still fire fine. Its just that the list receivers do not.

    Do you have any idea as to why this may happen?

    Cheers,

    Sam

  • Hi Kirk,

    I am having the same issue as Sam above me is having. It seems that the event receiver only fires for the first debug session after being attached to the list in the host web. Any input on why this might be happening would be appreciated.

    Thanks,

    Jordan

  • Jordan and Sam - I believe it has to do with the way that Visual Studio installs the app when you press F5.  Try installing the app using an app catalog instead and verify that it works.  If you are working with an on-premises farm, then you can check the ULS logs to see if there is any indication of what might have failed.  

  • Hi Kirk & Jordan,

    I can confirm that this is the case. Frustrating, but i'm glad now that we've uncovered the reason. I guess the cause is the next thing to find out, hopefully it can be fed back to the VS team.

    So essentially, i packaged the app, installed to the app catalog, installed the app on the relevant site, and the event receivers registered nicely and have fired every time they were supposed to.

    Thanks Kirk!

  • Guys,

    I have the breakpoint in my AppInstall event firing, but all other events (itemadded / itemupdated) on a list are not fired :-(.

    I verified that the remote event receiver is attached on the list with the "azure service bus service url". (dont know how else to call it). The url is siteprovisioningdev.servicebus.windows.net/.../SiteProvisioningService.svc

    If I browse to the url I get the following text, so I'm assuming it's ok.

    <feed xmlns="www.w3.org/.../Atom"><subtitle type="text">This is the list of publicly-listed services currently available.</subtitle><id>uuid:41725493-b0cb-4272-9f4e-c385a285e844;id=46931</id><updated>2014-06-25T09:47:25Z</updated><generator>Service Bus 1.1</generator></feed>

    Any ideas why my breakpoints don't get hit when I (for example) update an item?

  • What is point of returning SPRemoteEventResult result = new SPRemoteEventResult(); if there is no change in the object?

Page 1 of 1 (9 items)
Leave a Comment
  • Please add 4 and 3 and type the answer here:
  • Post
Translate This Page
Search
Archive
Archives