SharePoint, why is listdata service using strange names?

The wonders and pains of Microsoft.SharePoint.Linq.Util.GetFriendlyName

I'm doing some more work with SharePoint as a Single Page Application (spaspa.codeplex.com) and realized that when you are using the REST interface via _vti_bin/listdata.svc, the names of the list it generates are extremely strange.  That is, it doesn't map nicely to anything from the client object model.

Take for example, the out of the box Shared Documents library in a team site.

The URL is: /Shared%20Documents/

The display name is: Shared Documents

The list GUID is: {DEB93E69-7EDE-4AA0-974D-C05FCCCCE619}

But in ListData.svc,

The REST URL is: /_vti_bin/listdata.svc/SharedDocuments/

If you try to use any of the following, you'd get an error:

  • /_vti_bin/listdata.svc/Shared%20Documents/
  • /_vti_bin/listdata.svc/shareddocuments/
  • /_vti_bin/listdata.svc/{DEB93E69-7EDE-4AA0-974D-C05FCCCCE619}/

 

If you are coding to specific lists, this is probably not a big issue, since you'd target the list you want directly.  But for a project like SPASPA, where I am making no assumptions about what the names of the list actually looks like - this is very strange, and very difficult to work around.

Furthermore, if you are trying to mix and match REST service API with Client Object Model - you can't map them.  They have different names.  The client object model does not return SharedDocuments anywhere.

At a glance, you think - oh it's no big deal, just take the URL and strip out the spaces.

Ah ha, that's where you'd fail terribly.

It's almost like when Microsoft implemented the LINQ interface, they decided to make up their own names - and there's some sort of pattern:

  • If you have a lowercase list URL at: /doc/
  • The listdata.svc URL will be: /_vti_bin/listdata.svc/Doc/

Yep, magically capitalized.

  • And if you go crazy and create a list URL at: /my%20test%20lisT/
  • Then rename the title to: my test lisTT
  • Guess what's the listdata.svc end point?  /_vti_bin/listdata.svc/MyTestLisTT/

So, not just magically capitalized, camel-cased too, and it seem to be generated from the Title, disregarding the URL.  Crazy.

And you say oh that's easy, just split by spaces and turn it into CamelCase.

  • Enter foreign characters: My Test ïist  (that's a lowercase i with tréma: ALT+139)
  • Listdata.svc says: /_vti_bin/listdata.svc/MyTestÏist/  (capitalized: ALT+0207)

 

At some point, you decided that the only way forward is to read the listdata.svc code.

Here's the gem:

Microsoft.SharePoint.Linq.Util.GetFriendlyName

 

// Microsoft.SharePoint.Linq.Util
internal static string GetFriendlyName(string name)
{
    string[] array = Regex.Split(name, "[^\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}\\p{Nl}\\p{Mn}\\p{Mc}\\p{Nd}\\p{Pc}\\p{Cf}]", RegexOptions.Compiled);
    for (int i = 0; i < array.Length; i++)
    {
        if (!string.IsNullOrEmpty(array[i]) && char.IsLower(array[i], 0))
        {
            array[i] = char.ToUpper(array[i][0], CultureInfo.InvariantCulture) + ((array[i].Length > 0) ? array[i].Substring(1) : string.Empty);
        }
    }
    name = string.Join(string.Empty, array);
    if (string.IsNullOrEmpty(name))
    {
        throw new InvalidOperationException(Resources.GetString("CannotConvertNameToValidIdentifier", new object[]
        {
            name
        }));
    }
    if (Regex.IsMatch(name[0].ToString(), "[^\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}\\p{Nl}]", RegexOptions.Compiled))
    {
        name = "_" + name;
    }
    if (name.Length > 128)
    {
        name = name.Substring(0, 128);
    }
    return name;
}

 

Why you should always go to the source

 

There are some surprising bits in here, for example:

  • If your Title starts with a number, the function prepends an underscore.
    • Title: 1My Test ïist
    • REST: /_vti_bin/listdata.svc/_1MyTestÏist/
  • And if it exceeds 128 characters it is truncated.  I think this one is very unlikely, but may be people do go crazy with their list title.

 

Food for thought

A consequence of this kind of renaming, is that now there's a new problem.

Take two lists:

  • List 1 title: shared documents
  • List 2 title: SharedDocuments

The listdata service gives you:

  • /_vti_bin/listdata.svc/SharedDocuments/
  • /_vti_bin/listdata.svc/SharedDocuments0/

 

Which is what list?  Who knows!

SharePoint in a single page, now with List Item edit

 

Here's the latest screenshot from this project on http://spaspa.codeplex.com

 

 

I've put in buttons that allows you to edit a list item, and that drops the item into edit mode.

Here I'm modifying the Title field.  When you hit Save, it will commit the changes back to SharePoint.

Twitter Bootstrap UI turns out to be really good at guessing screen sizes and adapt.  Here's the page in landscape mode.

image

SPSPerth 2012 Update

This is my first time in Perth!  Hello Perthites!

I even took a train to Cottesloe beach - a very nice beach, I watched the sunset and did what any geek would do, I checked in the Indian Ocean.

I will update this post with links after the sessions. In the mean time, you can reference my current collection of articles on these topics regarding REST, custom service, and Knockout

/rest/

Downloads

Reference

Make sure you follow Matt Menezes
http://spmatt.wordpress.com/

He's a regular at Perth user group circles and knows a lot about Knockout.  I think he is slotted in for a future session in the upcoming months - don't miss it!

InfoPath - disabling backspace key in browser form

How to disable the backspace key using Javascript/jQuery for an InfoPath browser form in SharePoint 2010.

 

The problem

One really troubling problem with almost all web solutions is how the Backspace key works.  By default, it tells your browser to go back to the previous page in history.

If you are just browsing around on your Intranet, this is probably not a big deal - firstly, you wouldn't be pressing backspace unless you actually wanted to go back.  Secondly, if you did indeed made a mistake and pressed the backspace key, you'd just undo that action by clicking the forward button, or navigate to another link.  No problem.

 

When you are filling in a form on the browser though, such as through InfoPath, suddenly the backspace key is a big deal.  You users may be using the backspace key to delete text that they are in the middle of entering.  And if they didn't have a textbox focused, the backspace key is sent to the browser form, and suddenly you have a problem.  The form disappeared, and you have lost information.

 

InfoPath is quite smart - it remembers which view you are supposed to be on, and when you navigate to an outdated browser historical view of the form - Form Server will automatically redirect you forward to show you the correct form you are supposed to be viewing.  This is good, at least in the navigation sense.  So the only remaining problem is the lost data - your user may have had a whole page filled out and this mistake has just cleared their form.  Not good.

 

Enter Javascript

So, the plan is simple:

  1. Inject javascript to the existing browser form
  2. Listen to keydown event for a backspace key (keycode 8)
  3. Eat the event and stop it from propagation, so the browser don't see it

Using jQuery, you'll need this simple function.

 

function document_keydown(e) {
    if (e.keyCode == 8 && e.target.tagName != "INPUT") {

        // letting us know we've ate a backspace key
        SP.UI.Notify.addNotification('Ate a backspace key, hew!', false);

        // cancel backspace navigation
        e.preventDefault();
        e.stopImmediatePropagation();
        return false;
    }
};

$(document).keydown(document_keydown);

 

Injecting Javascript in modal dialog

 

If you are using SharePoint 2010's modal dialog to show InfoPath in a modal popup, it's slightly trickier.

 

// grab a reference to the modal window object in SharePoint
var w = SP.UI.ModalDialog.showModalDialog(options);

if (w) {
    // get the modal window's iFrame element
    var f = w.get_frameElement();

    // watch frame's readyState change - when page load is complete, re-attach keydown event
    // on the new document       
    f.onreadystatechange = function(e) {
        if (f.readyState == 'complete') {
            var fwin = f.contentWindow || f.contentDocument;
            $(fwin.document).keydown(document_keydown);
        }
    };
}

Result

 

nom-nom-nom backspace keys.

image

 

Note, because the Javascript catches the keypress event at the document level.  If your user still has focus on the input (textbox) level, the event will not be stopped - so your user still will be able to backspace when they are using a textbox.

AUSPC 2012 summary

I had an awesome time presenting Building your own custom REST Services and consuming them with jQuery AJAX in the Australian SharePoint Conference 2012.  A big thank you to the developers that came and geeked out with me for an hour on a Wednesday afternoon.

Notes

When you build a custom WCF Service using the CKS template, it is deployed to a subfolder the web front end's ISAPI folder, which, in turn, is mapped to the site's /_vti_bin/ folder.

image

 

It appears that in my earlier attempt to create an incomplete version of the REST service template item, I interfered with how CKS's templates worked - and correct features for deploying the WCF service is no longer being included in the package.  While existing items were updated and deployed, the new WCF service that I was creating wasn't being deployed to the ISAPI folder.

I've removed the REST template item from my Visual Studio .NET environment, and voila - the WCF services deployed nicely once again.

So that's one mystery resolved.  Now I scratch my head about how to fix my VS.NET REST template item.

 

Downloads