Useful Entity Mapping Supporting SharePoint Using TypeScript … and More???

1,845 reads

TL;DR — I wrote an entity mapper in TypeScript that leverages abstract classes and works great with SharePoint. This technique probably applies equally well to other back end domains. TypeScript is a great enabler for this kind of tool.

History

If you just want to get to the code, skip this section.

For years, we SharePoint developers have been writing applications that view SharePoint, in part, as a database. For the many of you that don’t know much about it, SharePoint is a collaboration platform that supports all kinds collaborations stuff, social things, workflows, doc management and related yadda yadda yaddas.

It also has this notion of a “List” which acts a lot like a database table. MSFT provides APIs that lets us perform the usual CRUD operations against them. List items, however, are really higher level entities than DB tables. They can be secured via API, they can be tagged socially (ratings and likes). They can be approved, involved in workflows and other yadda yadda yaddas. SharePoint provides a pretty functional UI around list items as well.

In the early days of SharePoint client-side dev, we tended to use tools like SPServices, put together and managed by the ever-so-clever Marc D Anderson. Without his tool, you’d write a lot of $.ajax() calls to endpoints, parse an XML or JSON response, hope it was well-formed, etc. People still use it. It’s great.

About two years ago, before Microsoft released “SharePoint Framework” (or SPFx, their newest a shiniest SP dev tool), I started working with TypeScript. There were no abstract classes at the time and that affected how I work with it.

Basic Models and Entity Mapping

When I had to write an end user feature that worked with SharePoint lists, I would start off with a kind of model object, informed by the list’s columns. We might create a SharePoint list list to support a scrolling news ticker as follow:

Title : string

: string Description : multiple lines of text

: multiple lines of text Effective Date : Date

: Date Expiration Date : Date

: Date Display Sequence: number

Here’s a dead simple TypeScript model for that list:

That gave me a simple bucket to hold onto the list item and from there, I could do all my UI work and sync it up with the list (both data retrieval and saving updates back to the list). The sync process was based around a general-purpose “call sharepoint api” type function that connected to SharePoint REST endpoints. A read operation took the form of:

Call the generic “call SharePoint API” function. (generic in the sense that it just takes an endpoint url and does a GET/POST; it’s not a <T> generic). Handle the response in a “physical” sense, meaning — did it error out? Is it a minimally valid response? Return that valid response (usually in JSON format) back to the caller. The caller would then take this JSON string and hand it off to a factory object. The factory object understands that specific JSON response and creates a TypeScript object for me.

Here’s the factory:

And there you have it — basic entity mapping. The CreateNewsItemFromJSON is a factory for NewsItem() objects. It maps the JSON response back to the entity. Among other things, you can see that the SharePoint column name doesn’t necessarily match the TypeScript field name.

I’m over-simplifying a bit because I actually went to the trouble of defining an interface to match that JSON response, so I got some nice intellisense since I didn’t need to rely upon an any input parameter for that.

I spent a bit of time talking about this in my book, Yet Another TypeScript Book.

Better Modeling and Mapping

This worked well. I had my model entity and the code could operate it against without the conceptual overhead of “omg, this is a SharePoiont list, where do the _x0020_’s go???” That stuff was safely tucked away in the factory method and nicely isolated from the rest of the app.

On the other hand, I ended up writing a lot of factory objects. Each of them is nearly the same. They all have a “createObjectFromJSon()” method that takes a JSON parameter. I used interfaces to describe the JSON so that helps, but even there, you end up with a lot of duplicative code. It was a copy/paste mess and pretty far from being DRY.

Nevertheless, I’d been living with it for a while when came along real generics in TypeScript. Generics made perfect sense to use. Every entity backed by a SharePoint list can participate in the same operations , such as:

Create

Read

Update

Delete

But also:

Assign and remove security

Rate the item on a scale of 1 to 5

“Like” an item

Get the ID (Guid) of its containing List

Wouldn’t it be nice to be able to do something like this?

TypeScript Generics make it possible:

The little screen shot above show that a “list helper” object is able to perform all of those operations against “news items.”

To enable this, I had to extend the model with new functionality. I don’t have a good intermediate step from the basic model above to the current state of my code base, so this listing is a bit of a humdinger:

Let’s break it down into smaller bits:

The top of the file imports a few objects and utility classes:

EntityHelper : home to some static utility classes used further down the model

: home to some static utility classes used further down the model EntityReadWriteAccess : Denotes whether some properties should be read AND written to SharePoint or just read. For instance, we don’t want to write back SharePoint IDs.

: Denotes whether some properties should be read AND written to SharePoint or just read. For instance, we don’t want to write back SharePoint IDs. AbstractViewPortItem : This contains a collection of properties that are common across a family of object (news items, featured news, favorties and the like).

: This contains a collection of properties that are common across a family of object (news items, featured news, favorties and the like). SFInterfaces: defines a handful of common interfaces.

Here’s the next chunk:

First, it defines an interface for the model at line 1 in the listing . Note that it defines an interface that extends another interface, “ISharePointListBackedEntity”. This is important since any entity that implements that interface can now use all of the SharePoint list operations shown above. (If you’d like to detour into an explanation about abstract classes, try this blog post + video combination I posted a little while back)

It defines the core model properties, such as Title, Effective and Expiration Dates and such.

The entity’s constructor invokes the super’s constructor at line 22 and then assigns useful default values to the properties.

Here’s the final chunk:

This section of code defines a static method, GetSharePointMetadata(). As you’ll see, the generic SP List Helper uses this metadata to do the mapping at runtime between the SharePoint list’s columns and this entity’s properties.

There’s a lot going on. Let’s first look at line 10. This defines the actual mappings and does so by populating an array, colMappings. Here’s a typical mapping:

This defines a mapping between a SharePoint list column named “ID” and a model property, “SharePointID”). At runtime, the code populates the SharePoint “ID” list column into the model’s “SharePointID” property for read operations.The optional 3rd parameter, “EntityReadWriteAccess.READ_ONLY” indicates that this mapping is ignored when writing back to SharePoint. (The default is both Read and Write, that’s why you don’t see it specified on the other properties).

The low-level mapping is accomplished via a collection of static methods on the EntityMapper class. There is one associated method for each SharePoint data type. These mapplet functions understand how to translate the JSON response of an integer to a TypeScript number, a SharePoint JSON MMS object into an array of strings, etc.

There is one final mandatory link — the name of the SharePoint list itself. This is accomplished on line 8.

There are a few final things going on here beyond straight mapping.

At line 4, I define a “console logging label”. This is used by the helper to emit log messages with a label that distinguishes it from other helper’s that might logging out to the console.

Lastly, there is this line:

At runtime, it’s helpful to create an empty instance for the purposes of validating the integrity of the mapping. That validation is implemented at run-time by these functions:

The generic entity helper invokes these validation functions at run-time to help reduce the common problem of working with case-sensitive field names and tricky spellings that arise when you’re forced to match text strings in JSON to class properties or other JavaScript variables.These validation functions will throw an error if we try to map a class property, “xyzzy” to a SharePoint list column that doesn’t exist, or vice-versa. It will catch an invalid mapping like this:

Good:

EntityHelper.GetIntegerMapping(“ID”, “SharePointID”, …);

Bad:

EntityHelper.GetIntegerMapping(“ID”, “SharepointID”, …);

In the “good” case, ID maps to SharePointID.

In the bad case, SharepointID is mis-spelled. The runtime validation logic detects this problem and warns you about it.

The fundamental idea is that we can create a model.ts file that connects the model to the SharePoint list in a more declarative way. By setting up the colMappings array with both the mappings (e.g. “VP_EffectiveDate” ← → “Effective Date”) as well as the appropriate serialization / deserialization function, we no longer need to concern ourselves with parsing JSON, constructing urls to correctly hit REST endpoints and the like. There is one source of truth as far as the entity mapping is concerned.

Being able to validate mappings at runtime is the icing on the cake.

Practical Effects

Here’s a short video showing what it’s like to work with tools like this:

There are several really nice consequences that come of using abstract classes like this:

Strongly typed results.

Automatic mapping to SharePoint list columns.

Consistent CRUD+ operations. It’s always “helper.SaveItem()” or “helper.DeleteItem()”.

Fixing a bug for one method fixes it for all. I recall working on a project where I’d made a common error in one of those early factory mappers. I had to make the same update to a dozen factories, introduced a new error in a couple while I was doing it, etc.

Adding a new capability for one model entity means that all model entities get to enjoy it. For instance, when I started off with this, I was only focused gets, saves and deletes. At one point, I needed to set security. I enhanced the core splisthelper object with a security function and now all models can be secured.

Sensible Next Steps

This entity mapping code evolved organically and was a great learning experience. It works well, but there are some obvious opportunities to improve it.

It would be nice to do something like this:

Basically, use TypeScript Decorators to specify entity mappings rather than the static method I’m using.

</end>

<postscript>

I recently published a book on TypeScript! It’s free and you can access it here: https://www.gitbook.com/book/pagalvin/yet-another-typescript-book/details

</endForReal>

Tags