In SharePoint 2013 gibt es im Rahmen des neuen App-Modell auch das Konzept der Remote Event Receiver. Im Prinzip ein Event Receiver, der nicht mit Server API programmiert wird, sondern analog zu Apps Remote läuft und daher auch mittels Client Side Object Model zu programmieren ist.
Das Visual Studio bietet in einem App Projekt die Möglichkeit einen “Remote Event Receiver” als Item hinzuzufügen. Im Web Projekt wird dann ein Service angelegt. Der Code ist simpel. Eine Klasse welches das Interface “IRemoteEventService” implementiert. Hier finden wir zwei Funktionen. ProcessEvent und ProcessOneWayEvent.
ProcessEvent ist für die synchronen Events und ProcessOneWayEvent wird für asyncrone Events aufgerufen.
Im App-Projekt wird zusätzlich mittels einer Elements.xml Datei das Event im SharePoint Server registriert und die Url des Remote Webs hinterlegt. Die Datei wird vom Visual Studio automatisch angelegt. Die Events werden mit einem Wizard ausgewählt. Die Bedienung des Wizard ist analog zur Anlange eines Server-seitigen EventReceivers.
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Receivers ListTemplateId="100">
<Receiver>
<Name>RemoteEventReceiver1ItemAdding</Name>
<Type>ItemAdding</Type>
<SequenceNumber>10000</SequenceNumber>
<Url>~remoteAppUrl/Services/RemoteEventReceiver1.svc</Url>
</Receiver>
<Receiver>
<Name>RemoteEventReceiver1ItemAdded</Name>
<Type>ItemAdded</Type>
<SequenceNumber>10000</SequenceNumber>
<Url>~remoteAppUrl/Services/RemoteEventReceiver1.svc</Url>
</Receiver>
</Receivers>
</Elements>
In diesem Beispiel, habe ich den Namen des Eventreceivers gleich beim Visual Studio Vorschlag belassen “RemoteEventReciever1”.
Bei meinen Versuchen hat diese XML Datei keine Auswirkung auf das Verhalten von SharePoint gehabt. Der EventReceiver wurde nicht aufgerufen. Ich habe es mit Visual Studio 2013 und Visual Studio 2012 probiert. Kein Erfolg. Daher habe ich einen mir persönlich bequemeren und zuverlässigeren Weg gewählt. Der EventReceiver wird per Programm-Code registriert.
Jede App bietet die Möglichkeit App Events für Install, Upgrade und Uninstall zu hinterlegen. Hierfür muss in den Projekteigenschaften die Eigenschaften “Handle App Installed”, “Hanlde App Uninstalling” oder “Handle App Ugraded” auf “true” gesetzt werden.
Sobald eine dieser Eigenschaften auf True gesetzt wird, wird vom Visual Studio wieder ein EventReceiver Service angelegt.
Egal welches Event behandelt wird, die notwendigen Informationen werden im Parameter “properties” der Methode ProcessEvent oder ProcessEventOneWayEvent übergeben.
1: public SPRemoteEventResult ProcessEvent(SPRemoteEventProperties properties)
2: {
3: SPRemoteEventResult result = new SPRemoteEventResult();
4:
5: using (ClientContext clientContext = TokenHelper.CreateAppEventClientContext(properties, false))
6: {
7: if (clientContext != null)
8: {
9: clientContext.Load(clientContext.Web);
10: var list = clientContext.Web.Lists.GetByTitle("test1");
11: clientContext.Load(list);
12:
13: string opc = OperationContext.Current.Channel.LocalAddress.Uri.AbsoluteUri.Substring(0,
14: OperationContext.Current.Channel.LocalAddress.Uri.AbsoluteUri.LastIndexOf("/"));
15:
16: string remoteUrl = string.Format("{0}/RemoteEventReceiver1.svc", opc);
17:
18: EventReceiverDefinitionCreationInformation newEventReceiver = new EventReceiverDefinitionCreationInformation()
19: {
20: EventType = EventReceiverType.ItemAdded,
21: ReceiverName = "RemoteEventReceiver1",
22: ReceiverUrl = remoteUrl,
23: SequenceNumber = 1000
24: };
25: list.EventReceivers.Add(newEventReceiver);
26: clientContext.ExecuteQuery();
27: }
28: }
29:
30: return result;
31: }
In der Event Routine wird zunächst ein ClientContext gebildet (Zeile 5). Dieser Context arbeitet mit dem App User. D.h. der Context wird unter dem App Account erstellt. Die App muss somit das App Only Berechtigungsmodell verwenden. (siehe hier) Der entsprechende Eintrag wird im AppManifest automatisch vom Visual Studio gesetzt:
<AppPermissionRequests AllowAppOnlyPolicy="true" >
<AppPermissionRequest Scope="http://sharepoint/content/sitecollection/web" Right="FullControl" />
</AppPermissionRequests>
In Zeile 18 wird die EventReceiverDefintion erstellt. Oder genauer gesagt, das entsprechende CreationInformation Objekt angelegt. Die Properties sind selbsterklärend. Zu guter Letzt wird der EventReceiver an die entsprechende SharePoint Liste gehängt. In meinem Fall eine Liste mit dem Namen “Test1” (siehe Zeile 10).
Da bei der Definition auch die URL des Eventreceivers anzugeben ist, stellt uns der Operation Context die Url zu Verfügung. Entweder local für Debugging oder die tatsächliche Remote Adresse des Services. Die URL wird in den Zeilen 13-16 gebildet.
Der Event Receiver
Da der Event Receiver als “ItemAdded” angelegt wurde, bedeutet dies, dass die Methode “ProcessOneWayEvent” aufgerufen wird. (Asynchrones Event). Der Code im Event Receiver ist straight forward. Zunächst wieder den ClientContext bilden. Danach werden in den Zeilen 8 und 9 die Liste bzw das ListItem geladen. Die Information wie ListTitle und ListItemId werden im Übergabe-Parameter “properties” an den EventReceiver übergeben.
Da es sich hier um ein Item Event handelt sind diese beiden Properties im Property “ItemEventProperties” zu finden. Genauso sind in dieser Klasse “ListEventProperties”, “WebEventProperties”, “AppEventProperties” und “SecurityEventProperties” zu finden.Die Eventart kann über das Property “EventType” ermittelt werden.
Ziel des EventReceivers ist es, dem ListenItem eine eigene Berechtigung zu geben. In weiterer Folge wird im Code die Berechtigungsvererbung für das Item unterbrochen und dem User, der das Item angelegt hat, nur noch Leserechte gegeben.
Da der Eventreceiver mit den Rechten der App ausgeführt wird (App Only Policy) benötigt der User, der das Item anlegt keine Verwaltungsrechte auf der Liste.
1: public void ProcessOneWayEvent(SPRemoteEventProperties properties)
2: {
3: using (ClientContext clientContext = TokenHelper.CreateRemoteEventReceiverClientContext(properties))
4: {
5: if (clientContext != null)
6: {
7: clientContext.Load(clientContext.Web);
8: List liste = clientContext.Web.Lists.GetByTitle(properties.ItemEventProperties.ListTitle);
9: ListItem it = liste.GetItemById(properties.ItemEventProperties.ListItemId);
10: clientContext.Load(it);
11: clientContext.Load(clientContext.Web.CurrentUser);
12: clientContext.ExecuteQuery();
13:
14: it.BreakRoleInheritance(false, true);
15:
16: RoleDefinition roldef = clientContext.Web.RoleDefinitions.GetByName("Read");
17: Principal p = clientContext.Web.EnsureUser(properties.ItemEventProperties.UserLoginName);
18: RoleDefinitionBindingCollection c = new RoleDefinitionBindingCollection(clientContext);
19: c.Add(roldef);
20:
21: it.RoleAssignments.Add(p, c);
22: it.Update();
23:
24: clientContext.ExecuteQuery();
25: }
26: }
27: }
Ergebnis ist nun, dass ein User zwar ein Item anlegen kann, sobald es aber angelegt ist, kann der User nur mehr selbst lesend darauf zugreifen. Auch wenn diese Vorgehensweise möglich ist gebe ich doch eines zu bedenken: Wenn das Service nicht verfügbar ist, so wird das Item angelegt, aber die Berechtigungen nicht verändert. Es ist zu überlegen, ob man in diesem Anwendungsfall nicht doch zu einem Workflow oder einem Server-Seitigen EventReceiver greift.