Interceptors
A Data Services “interceptor” lets you intercept the calls from clients after the OData-ness has been parsed into .NET-ness but before it’s been executed. This lets you change or cancel an operation. Interceptors come in two flavors: change and query.
A “change interceptor” is a method marked with the ChangeInterceptor attribute along with the associated collection name, e.g.
[ChangeInterceptor("TinyLinks")] public void OnChangeTinyLink(TinyLink link, UpdateOperations operation) { switch (operation) { // Make sure new links are active case UpdateOperations.Add: link.IsActive = true; break; // Nothing to do here case UpdateOperations.Change: break; // Oops -- use the IsActive flag instead case UpdateOperations.Delete: throw new DataServiceException(405, "DELETE not allowed. Set IsActive to false instead."); } }
Here we’ve got a change interceptor that does two things. The first is that it looks for a new TinyLink being added and sets the IsActive flag, making sure that every new link starts out as active. Because the interceptor happens after the dehydration of the OData but before the operation is passed along to the service provider, we can make changes and they’ll be honored.
The second thing this interceptor does is to effectively disable the delete operation. This is handy because lots of times we don’t want to delete data – we want to “tombstone” it, i.e. make it “inactive” but not actually remove it from the database. When a caller is attempting a delete, we construct a DataServiceException, which takes an HTTP status code (405 in our case) and the message and turns it into an HTTP error[21]. With this interceptor in place, all new TinyLink objects are added with IsActive set to true and a client attempting to delete a TinyLink will get an error:
Unhandled Exception: System.Data.Services.Client.DataServiceRequestException: An error occurred while processing this request. ---> System.Data.Services.Client. DataServiceClientException: <?xml version="1.0" encoding="utf-8" standalone="yes "?><error xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"><code></code><message xml:lang="en-US">DELETE not allowed. Set IsActive to false instead.</message> </error>
Instead, our client code must “delete” a link by deactivating it:
var link3 = service.TinyLinks.Where(l => l.Id == linkId).First(); var bill2 = service.TinyUsers.Where(u => u.Id == billId).First(); //service.DeleteObject(link3); // Interceptor doesn't allow deletes link3.IsActive = false; service.UpdateObject(link3); service.DeleteLink(bill2, "Links", link3); service.DeleteObject(bill2); service.SaveChanges(SaveChangesOptions.Batch);
Notice here that in addition to updating the IsActive flag on the TinyLink, we’re also deleting the TinyUser (there isn’t an interceptor forbidding that), which means that we need to delete the link between the two objects via the DeleteLink method, just like we added it previously.
A “query interceptor,” on the other hand, is a method marked with the QueryInterceptor attribute along with the associated collection name. Like a change interceptor, a query interceptor can change or cancel the query:
[QueryInterceptor("TinyLinks")] public Expression<Func<TinyLink, bool>> OnQueryTinyLinks() { // Grab the current user string user = GetCurrentUser(); if (string.IsNullOrWhiteSpace(user)) { RequestLogin(); } // Filter by current user return link => link.User != null && link.User.Name == user; }
The idea of this query interceptor implementation is that we’re looking for the currently logged in user and, if they aren’t logged in, asking them to do so. Once they are logged in, we filter on that user name from our database to only allow a user to see their own links.
What does it mean to be “logged in” to an OData endpoint? That’s an excellent question.
Footnote
[21] I’m a big fan of Wikipedia for my HTTP status code: http://en.wikipedia.org/wiki/List_of_HTTP_status_codes (http://tinysells.com/148). 405 happens to be “Method Not Allowed,” which is what we’re attempting to communicate.