Friday, May 26, 2017

Microsoft 70-487: Create and implement a WCF Data Services service

Exam Objectives

Address resources; implement filtering; create a query expression; access payload formats (including JSON); use data service interceptors and service operators



Quick Overview of Training Materials

Exam Ref 70-487 - Chapter 1.5


It's worth noting that, although it appears to still be a part of the current test, WCF Data Services seems to be going the way of WinForms.  Mike Pizzo, a Principal Architect at Microsoft on the OData team, in a 2014 talk mentioned that they were moving away from WCF Data Services because the stack is so monolithic.  Instead, they were moving more toward Web API, and newer demos for OData seems to be moving more toward Web API as well.  I wouldn't be surprised if the next iteration of the 70-487 test moves more in that direction as well.


Address Resources


Lesson learned the hard way: OData does not support Geography data types.  This popped up while I was monkeying around with the demo, because the Address entity in the AdventureWorks2012 database includes a "SpacialLocation" field which is a Geography data type.  At first I thought I was having a problem because of Entity Framework 6, but after I imported the EF6 provider and made the necessary code changes, I was still getting the same error.  It wasn't until I stumbled across the suggestion of using annotations to turn on full stack trace printing that I figured out the error.  I opened the edmx file in the xml viewer and removed the "SpacialLocation" field from the csdl, ssdl, and msl portions of the file, and removed the field from the generated class, and I was golden.

Setting up the initial project couldn't be much simpler (other than getting hung up on wonky data types).  New empty ASP.NET project, add an Entity Framework data model, throw in the service, set access permissions, and BOOM.... that's literally it:


For demo purposes I just made the permissions as open as possible, this would probably not be the way you'd want to do this in production:


namespace WCFDataService
{
    [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
    public class WcfDataService : DataService<AdventureWorks2012Entities>
    {
        // This method is called only once to initialize service-wide policies.
        public static void InitializeService(DataServiceConfiguration config)
        {
            // TODO: set rules to indicate which entity sets and service operations are visible, 
            // updatable, etc.
            // Examples:
            config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
            config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
        }
    }
}


I was curious about the reflection provider, so I decided to see if I could implement an OData service from a repository instead of directly from the EF context, and it actually proved to be a very elucidating experience.  So here is "Reflection Provider 101":

You create a "data class" which exposes public properties of type IQueryable<T>.  These will be your collections (basically like your dbSets on a EF context).  T is a type that must be an "entity" (thank you StackOverflow for clarity).  This is not in the same sense as in Entity Framework.  For the reflection provider to consider T an entity, the class must either be annotated using [DataServiceKey("<name of key field/property>")] or the property itself must be annoted with [Key].  One "gotcha" with DataServiceKey is that the name must match exactly.  I had a small mistake in mine ("EmployeeId" instead of "EmployeeID") that resulted in terribly unhelpful error messages and an hour or two of frustration.  In my example, I was able to add the necessary annotations to the generated EF model classes indirectly since they were partial classes:


namespace WCFDataService
{
    [DataServiceKey("DepartmentID")]
    [IgnoreProperties("EmployeeDepartmentHistories")]
    public partial class Department {}

    //[DataServiceKey("BusinessEntityID")]
    [IgnoreProperties("EmployeeDepartmentHistories", "Person", ...)]
    public partial class Employee {
        [Key]
        public int EmployeeID {
            get
            {
                return this.BusinessEntityID;
            }
            set
            {
                this.BusinessEntityID = value;
            }
        }
    }
}


This code also highlights another point with the reflection provider:  Everything included in the model that isn't also an entity (in the WCF Data Services sense) must be a simple type.  You can't have properties that are complex object types from outside the model.  This is easy to take for granted with the Entity Framework provider, because everything is already nicely bundled up for us.  Trying to get fancy with tacked on properties, I ended up seeing a lot of "Not Implemented" exceptions in the browser (I dropped the "EmployeeID" as the Key because it made even filtering impossible)...



public partial class Employee {

    public Department currentDepartment
    {
        get
        {
            return EmployeeDepartmentHistories.ToList().First().Department;
        }
    }
}



The Entity Framework provider had no such problem with relations (although the way the AdventureWorks database connects an Employee to their Department is a bit... odd...).  One other thing to note about the reflection provider.  As mine is implemented, it would be read-only, because I haven't implemented the IUpdatable interface.  Oh and final thing... if you use Entity Framework 6, it allegedly needs some additional tweaking.  I say allegedly because I couldn't get it to work, trashed Demo version 1 and started from scratch with EF 5.



Implement Filtering


The config settings that I was so cavalier in setting for my demo (SetEntitySetAccessRule and SetServiceOperationAccessRule) actually give you a great deal of control over how users can interact with your service.

First off, SetEntitySetAccessRule controls how the entity collections are accessed.  In the above demo code, this would be the Departments and Employees collections.  The first argument is either a string designating which collection the rule applies to (stringly typing ahhhh!!) or "*", which denotes all collections not explicitly set.  The second argument is from the EntitySetRights enum.  Because this enum has the Flag attribute, you can use bitwise operations on it (so you can use | to denote more than one value).  Possible values are:

  • None
  • ReadSingle
  • ReadMultiple
  • WriteAppend
  • WriteMerge
  • WriteReplace
  • WriteDelete
  • AllRead
  • AllWrite
  • All

So if I wanted to allow single record reads of Departments, but any write op, I could use

config.SetEntitySetAccessRule("Departments", 
     EntitySetRights.ReadSingle | EntitySetRights.AllWrite);

The SetServiceOperationAccessRule setting works similarly, only it applies to service operations. These are methods defined directly on the service, rather than as properties on the datasource (so something like "FindLargestOrder" or something, which returns just one element, is a possibility). ServiceOperations don't support writing apparently, as all the rights on the ServiceOperationRights enum are read related:

  • None
  • ReadSingle
  • ReadMultiple
  • AllRead
  • All
  • OverrideEntitySetRights




Create a Query Expression


With WCF Data Services exposing a RESTful url, querying the service is all about the URL.  The URL can be broken down into a number of components.  The service root url is essentially the "base" url, and will typically end in .svc (unless one has monkeyed around with routing I suppose).  The root of my reflection service is http://localhost:43317/HumanResources/HumanResourcesService.svc/. Navigating to the root will show which collections are available, but not much else.  To get more details about the service, you can request the service metadata using the $metadata resource. This gives you a wsdl-like xml document that describes the service in much greater detail:


To retrieve records from a collection, tack on the name of the collection to the root.  Doing this will display all the entities associated with that collection unless paging is configured.  You can select a single entity by including the value of a key in parens after the resource name.  I found this notation a little odd at first, but that's how it's made to work:


If we want to return just one property from the entity (which could be a navigation property to another entity), we add a slash and the entity name.  This only works when we are building off a single entity, though (well... maybe not... in the spec it seems there are operations bound to collections...).  Once we get down to a single primitive entity, we can retrieve just the value (without the surrounding XML or JSON) using the $value resource.  So in the above example, navigating to:

http://localhost:43317/HumanResources/HumanResourcesService.svc/Employees(1)/LoginID/$value

returns simply adventure-works\ken0
Everything I've described so far extending past the base service root url is what the spec calls the "resource path".  The optional third component that can follow is the "query options", which is formatted like query string parameters.  These query operations provide a set of capabilities not unlike basic SQL statements:

  • $filter - used to restrict the entities that are returned, includes a rich collection of operators.  A handy chart is located on the Protocol page of the spec, while how to use them in a url is explained on the URL Conventions page:  (Not all are supported by WCF Data Services)
    • boolean: eq, ne, gt, ge, lt, le, and, or, not, has
    • arithmetic: add, sub, -, mul, div mod
    • grouping: ( )
    • string functions: concat, contains, endswith, indexof, length, startswith, substring, tolower, toupper, trim
    • date and time:  date, day, fractionalseconds, hour, maxdatetime, minute, month, now, second, time, totaloffsetminutes, totalseconds, year
    • arithmetic functions:  ceiling, floor, round
    • type functions: cast, isof
    • geo functions: geo.distance, geo.intersects, geo.length
    • lambda operators: any, all.  Format for lambda is (d:d/Prop gt 100) which would be (d => d.Prop > 100) in C# lambda. 
    • literals:  primatives (null, true, 'string', etc.), collections (["red", "green"]), $it (basically this), $root (root service)
  • $expand - analogous to eager loading, this can be used on a singleton or a collection.
  • $select - allows you to do a projection, bringing back only the properties you specify
  • $orderby - sort entities according to the field specified
  • $top - equivalent to "Take" in LINQ
  • $skip - just like "Skip" in LINQ
  • $count - returns the number of records in the collection (this gave me fits in the reflection provider)
  • $search - allows you to specify a free text search expression (Not supported in WCF Data Services)



Access Payload Formats


Atom (XML) and JSON formats are supported in WCF Data Services, and switching between them is super easy.  The desired format can be included as a query parameter using $format=json or $format=atom.  It can also be set using the Accept header.  Interestingly, if both are set (header and query param) to different values, then the query param wins out:

Request made with Postman, if you were wondering...



Service Interceptors and Operators


There are two kinds of service interceptors in WCF Data Services: Query Interceptors, and Change Interceptors.  Interceptors are implemented by annotating methods that adhere to certain signature requirements.  Query interceptors return a LINQ expression that takes in an entity type and returns a bool.  This expression is used to filter results from the query, whether the result is a collection or a singleton.  The following code snippet will cause this service to only return Employees whose first name starts with "E".  While the MSDN How-to example had the method name reflect something like an event listener method, you can name it whatever you want really:


// Define a query interceptor for the Employee entity set.
[QueryInterceptor("Employees")]
public Expression<Func<Employee, bool>> WallaWallaBingBang()
{
    Debug.WriteLine("Firing Query Interceptor!!!");
    return e => e.Person.FirstName.StartsWith("E");
}


The ChangeInterceptor responds to "UpdateOperations" that occur on an individual entity, which includes Add, Change, and Delete operations.  In order for the change interceptor to trigger, write operations must be enabled to the entity (otherwise you can't change it... simple). The code below is a toy example that prevents deleting an Employee:

[ChangeInterceptor("Employees")]
public void BleepBloop(Employee employee, UpdateOperations operations)
{
    if (operations == UpdateOperations.Delete)
    {
        throw new DataServiceException(400,
            "Employees cannot be deleted! That's cruel!");
    }
}



Service operations (which apparently the author of the Exam Ref forgot about... derp) are methods defined directly on the service instead of as part of the data model.  They are defined by applying a [WebGet] or [WebInvoke] attribute to a method.  If this method returns an IQueryable, then you can apply operators such as $expand.  If it returns anything else, these operators aren't supported.  Here is a simple service operator that returns a random employee:


[WebGet]
public IQueryable<Employee> RandomEmployee()
{
    return CurrentDataSource.Employees.OrderBy(e => Guid.NewGuid()).Take(1);
}


While you could write this method to just return an Employee and call First() instead of Take(1), that would prevent you from using the OData operators.  Using the WebInvoke annotation would allow you to specify that your method responds to POST requests by default, or other methods if you specify them in the constructor, e.g [WebInvoke(Method = "PUT")].

The MSDN article mentioned using service operations and interceptors for things like validation and authorization, so I thought I would put together another sample.  It's still silly, but it kind of points to the beginnings of a genuine use of these facilities.  A service operation provides an authorization token to the requester.  A query interceptor then looks for this token as a header on the request.  Now, this would be a piss poor way to actually implement security.  But it illustrates the point:


private static Guid secret = Guid.NewGuid();
[WebGet]
public String GetMagicKey(String secretPassword)
{
    if ("Abracadabra".Equals(secretPassword))
    {
        return secret.ToString();
    } else
    {
        return "pffffft...shaaa right!";
    }
}

// Define a query interceptor for the Employee entity set.
[QueryInterceptor("Employees")]
public Expression<Func<Employee, bool>> YOUSHALLNOTPASS()
{
    Debug.WriteLine("YOU SHALL NOT PASS!!! Well maybe...");
    string token = HttpContext.Current.Request.Headers.Get("token");
    string s = secret.ToString();
    return entity => s.Equals(token);
}





The parameter to the GetMagicKey function is passed in as a URL param, returning the static secret for the service.  If this is not included on the request to Employees, then the service returns an empty collection.  If it is included, we get back all the employees (subject to whatever other query interceptors apply to that collection).  I tried to apply the query interceptor to every collection with one method, but alas I had no luck (Tried a wildcard in the annotation, crash... tried multiple annotations with a Func<object,bool> return type, crash).  There may be a way to do it, but I think I've beat this horse enough.




.

2 comments:

  1. Hello Troy,

    "In order for the change interceptor to trigger, write operations much be enabled to the entity (otherwise you can't change it... simple)"
    Shouldn't this be "must" rather than "much"?

    Anyways, great post!

    ReplyDelete