>
Blog
Book
Portfolio
Search

7/19/2014

6745 Views // 0 Comments // Not Rated

An Investigation Into Nullability In The SharePoint Client Object Model

An interesting problem I've run into with CSOM is the concept of null. Developers know null very well: an object that has no value. We've all caught more NullReferenceExceptions over the years than the most expert angler has caught fish. If I had to explain it to an intro-level computer science class, I'd describe it as the difference between having a wallet with no money in it and not having a wallet at all.

And for the last fifteen years or so, that's been a simple truth in my career. But lately, as I've become more intimate with the latest and greatest of my favorite Microsoft technologies, this truth has taken on certain complexities. Conceptually it's still valid, but not unlike Marty McFly from the Back to the Future franchise, I'm not thinking fourth dimensionally. Let me give you an example before we get back to SharePoint.

I've been using Entity Framework ever since I can remember. And like any other member of my self-taught toolset, I used it for one purpose on one project. Then, over the course of subsequent tasks and new engagements, I become more confident in the product, and slowly allowed the full breadth and depth of the technology to ooze into my brain one feature at a time, like a glacier of knowledge roaring toward the ocean of expertise at a modest clip of nine feet per year.

So at first, it was simply an ORM. Then I learned Linq. Then lazy loading and eager loading. And so on. But it wasn't until I had elucidated myself as to how these layers stack upon each other to build a truly sophisticated data access layer that I simultaneously learned that I didn't really know what I was doing until this moment. My thinking only covered three dimensions. The following flow chart describes my Visual Studio-based interaction with an existing database in its entirety:

New Project -> Nuget -> Install EF -> New EDMX -> Generate Model From Database -> Write some sexy Linq -> Tune some IQueryables -> Build rest of app.

What took a while (again, we're talking glacier speed here) to sink in was what this code really does. It's not just a wrapper around ADO.NET. It's not just some complier glitter. I needed to think fourth dimensionally. I needed to really understand what it means to execute a query on the client verses on the server. I finally learned that throwing in a ToList at the end of my lambda chain to get around The method or operation is not implemented. errors is really bad news. I had to mature and really understand not just what the framework does, but also how it does it.

Getting back to SharePoint, CSOM is kinda sorta like Linq-To-SharePoint, inasmuch as we operate against a data context (ClientContext) defined at a particular scope (a site collection). Its members are not as strongly typed as the now-seemingly-ancient SPMetal objects were, but we can think of it as a DbContext with extremely eager loading configured; we have to manually load the properties of the objects we need to access, like so:

Code Listing 1

  1. //check server relative url property
  2. if (!context.Web.IsPropertyAvailable("ServerRelativeUrl"))
  3. {
  4. //load server relative url property
  5. context.Load(context.Web, w => w.ServerRelativeUrl);
  6. context.ExecuteQuery();
  7. }

Where "context" is an instance of my ClientContext. As you can see in Line #'s 5 and 6, I need to opt-in to any properties I'm loading, and then call ExecuteQuery to actually make the request. This is a little onerous, but it forces us to keep our queries nice and trim. The only real problem I have with this paradigm is up in Line #2, where we need to use a hard-coded string to check if the property in question has been loaded by name. Now for more complex properties ("parentWeb" is an instance of Web from the same context) it's a little different:

Code Listing 2

  1. //check webs property
  2. if (!parentWeb.IsObjectPropertyInstantiated("Webs"))
  3. {
  4. //load webs
  5. context.Load(parentWeb.Webs,
  6. w => w.Include(ww =>
  7. ww.ServerRelativeUrl));
  8. context.ExecuteQuery();
  9. }

The distinction between IsPropertyAvailable and IsObjectPropertyInstantiated is that the former is for scalar properties while the latter is for child ClientObjects (entities). After loading up your object and its properties, the next thing we do is check if it is null before coding against it. Something I've noticed with SharePoint over the years is that if something doesn't exist (like a list or a file) you get an exception instead of a null object. I prefer the null over the bomb (such as calling SingleOrDefault instead of Single in Linq) because I can handle the "null" case in a much cleaner manner.

If I ask SharePoint for a file that doesn't exist, I think it's perfectly acceptable to give me back a null SPFile object. It's not an error or a flaw in my logic; I want to know if something's there, and if not, I want to do something about it; throwing an exception is not the proper response. This is different than asking for the ninth item in an SPListItemCollection that only has a length of six. In the file case, I'm inspecting a url and politely asking if it exists. As for the items, I'm asserting that it's there and demanding its object representation; by all means throw an exception then.

The reason I go into all of this is because CSOM is a little different; it requires some four-dimension thinking. Fortunately, I came up with a quick extension method to take care of the client/server time travel necessary to comprehend what null is all about in the SharePoint client C# object model. Basically, when you request a ClientObject from your ClientContext, you'll usually get something back as a result.

CSOM is essentially a thin veil over WCF, wrapping calls to the "_vti_bin/client.svc" service. Therefore, when you make a request, the aforementioned result will be a ClientObject, which is basically a proxy back to some corresponding "server" object. The big "null" issue here is what if the server object doesn't exist; in other words, what if it itself is null? This leaves us with a non-null object on the client representing, technically speaking, nothing on the server. How do we react to that?

Fortunately, ClientObject (the base of all CSOM objects) has a ServerObjectIsNull property. This is a nullable Boolean, so all we have to do is inspect its value (which we don't have to explicitly opt-into per the code samples above) to determine if our ClientObject is real or just a ghost. Below is the extension method I wrote to perform the necessary checks against any ClientObject instance.

Code Listing 3

  1. public static bool IsNull(this ClientObject clientObject)
  2. {
  3. //check object
  4. if (clientObject == null)
  5. {
  6. //client object is null, so yes, we're null (we can't even check the server object null property)
  7. return true;
  8. }
  9. else if (!clientObject.ServerObjectIsNull.HasValue)
  10. {
  11. //server object null property is itself null, so no, we're not null
  12. return false;
  13. }
  14. else
  15. {
  16. //server object null check has a value, so that determines if we're null
  17. return clientObject.ServerObjectIsNull.Value;
  18. }
  19. }

This method is pretty straight forward: either the client object itself is null, so return true; otherwise return the value of the ServerObjectIsNull property (Line #'s 4-8 and 14-18). The interesting part is the middle check on Line #9, where we determine if the ServerObjectIsNull property is null. This is getting a little meta: a null check being null? Did we just wander into the fifth dimension?

As you can tell by Line #12, if this property is null, that surprisingly means that the object is not null. I discovered this by experimentation; my first stab at this method returned true for this case, and I found that this was leading to problems. For example, if you assume a file at a url is null and attempt to therefore create a new one at the same location, depending on how you do it, you could quietly end up with an overridden file, or violently get an exception that the file already exists.

So, put simply, if SharePoint assigns a null to this property, that means the object exists. Thinking more on this, it does follow a certain nullable Boolean paradigm I use in my apps. Boolean-valued variables default to false, and their nullable cousins of course default to null. Now in my code, I oftern have a scenario where instead of true or false or null, I actually really only care about true or not true. Consider the following example:

Let's say I have a multithreaded polling algorithm where I'm waiting for a file be uploaded. I might want a flag that gets set to true when the file is detected. Once this switch is tripped, the code will break out of the polling loop and continue on; I don't ever return this value to my caller; it's just a gate in my logic. I never really care about the false case; I'm just waiting for it to be true.

CSOM might be following a similar paradigm here: why bother burning the cycles of setting the ServerObjectIsNull variable when the null case provides enough context for my code? (Yes, it's not exactly an expensive operation to set a proper false value here, but the nature of CSOM is so steadfastly concerned with performance that I kind of understand wanting to conserve every last bit of juice on the server.)

The irony here is that it was my overzealous null checking that lead to this problem in the first place. But now I live in the fourth dimension where I fully comprehend what it means to be null in a given context. It reminds me of a time way back in my first C++ class where I overrode the equals operator, introduced a bug in that bit of logic, and spent the better part of an evening rethinking my grasp on the first dimension; for s brief moment, two and two most certainly did not equal four.

And here I am, literally decades later, now able to deal with a similarly-confounding concept: an object representation of a CSOM file that doesn't exist. Have fun nulling!

No Tags

No Files

No Thoughts

Your Thoughts?

You need to login with Twitter to share a Thought on this post.


Loading...