Extending SharePoint 2013’s REST _api endpoints with Complex Types & Collections

Hi,

First, I’d like to thank Chris Givens (@givenscj) and Steve Curran (@spsteve) for their help on this topic. Chris has recently published an excellent blog post on this topic http://blogs.architectingconnectedsystems.com/blogs/cjg/archive/2014/04/17/Extending-SharePoint-2013-REST-APIs.aspx and a sample solution http://code.msdn.microsoft.com/Extending-SharePoint-2013-c39d01ae.
Chris replied to one of my tweets where I explained a technique describing how to consume custom web services from within Apps and I directly saw the potential in extending the REST endpoints. Before seeing Chris’s reply, I didn’t even know that we could extend them, I was more used to using the WCF built-in factories of SharePoint
In his example, Chris shows the required plumbing to inject your own endpoints (+CSOM) in SharePoint 2013, he demonstrates how to declare custom functions and custom properties.
However, I quickly encounter the three following questions which are not explained in Chris’s blog post nor in the sample code:

  • How to get parameters in custom functions
  • How to return custom types from functions and properties (Chris only shows how to work with primitives such as string/datetime,int…)
  • How to return collections

I thank Steve for the second bullet since he put me on the track on how to return a custom type by using a custom ValueTypeConverter. So, I reflectored a lot the SharePoint code and started to chase all the types deriving from ValueTypeConverter to see how SharePoint implements it. I implemented a converter for my custom type but it was just not working…no error but nothing happened. I lost a lot of time in trying to figure out what I was doing wrong. What really made me lose time is the fact that the the debugger was not hitting my breakpoints. I also tried to use System.Diagnostics.Debugger.Break() but nothing happend so I though that my code was not called…and I was wrong! I don’t know what happens (still happening in my environment) with the silly debugger but this bastard doesn’t like those breakpoints, I must highlight that this only happens in the ValueTypeConverter…So, when I finally had the bright idea of overriding all the members of ValueTypeConverter and adding a Throw new ApplicationException() in each overriden member, the thing was crashing, meaning that my code was well executed although the silly debugger was still not seing it for some reason. Worse, Visual Studio crashed many times whenever I tried to add breakpoints within my custom ValueTypeConverter. There must be some weird stuff happening under the covers.
Anyway, when I knew that my code was indeed triggered, I did it the old school way by adding some logging statements line after line to see up to where the code was executing and I realized that my implementation of GetProperties was not right (stole it from Reflector…but didn’t clean it up enough).Ok, enough talking, lets answer the above questions. (sample solution downloadable below)
How to get parameters in custom functions?
Let’s first declare a basic ClientCallableType containing a method with an input parameter:

[ClientCallableType(Name="SILVERIT", ServerTypeId="{E1BB82E8-0D1E-4e52-B90C-684802AB4EF7}")]
    public class SilverIT
    {
        public SilverIT()
        {

        }

        [ClientCallableMethod]
        public int AddFiveToInput(int input)
        {
            return (input + 5);
        }
}

So far, not rocket science. You need to decorate your class with the ClientCallableType attribute and your method with either ClientCallable, either ClientCallableMethod.

If you read Chris’s blog post, you should have understood the concept of server stub, if not, go read it again :). Basically, in the server stub, you have to describe which methods & properties are part of the type you create the server stub for and how methods should be invoked. So, for the above method, you need to create a serverstub as below (only taking important bits here):

 [ServerStub(typeof(SilverIT), TargetTypeId = "{E1BB82E8-0D1E-4e52-B90C-684802AB4EF7}")]
 public class SilverITServerStub : Microsoft.SharePoint.Client.ServerStub
 {
    protected override IEnumerable<MethodInformation> GetMethods(ProxyContext proxyContext)
    {
        if (proxyContext == null)
        {
            throw new ArgumentNullException("proxyContext");
        }
        MethodInformation m0 = new MethodInformation
        {
            Name = "AddFiveToInput",
            IsStatic = false,
            OperationType = OperationType.Default,
            ClientLibraryTargets = ClientLibraryTargets.All,
            OriginalName = "AddFiveToInput",
            WildcardPath = false,
            ReturnType = typeof(int),
            ReturnODataType = ODataType.Primitive,
            RESTfulExtensionMethod = true,
            ResourceUsageHints = ResourceUsageHints.None,
            RequiredRight = ResourceRight.Default
        };
        m0.Parameters.Add(new ParameterInformation
            {
                Name="input",
                RESTfulParameterSource = RESTfulParameterSource.Default,
                ParameterType = typeof(int),
                ParameterODataType = ODataType.Primitive,
                IsOptional = false,
                DefaultValue = null

            });

        yield return m0;
   }
   private static object AddFiveToInput_MethodProxy(object target, ClientValueCollection xmlargs, ProxyContext proxyContext)
    {
        int input = Microsoft.SharePoint.Client.ServerStub.GetArgument(xmlargs, 0).ConvertTo<int>();
        SilverIT me = target as SilverIT;
        return me.AddFiveToInput(input);
    }
  protected override object InvokeMethod(object target, string methodName, ClientValueCollection xmlargs, ProxyContext proxyContext, out bool isVoid)
    {
        if (proxyContext == null)
        {
            throw new ArgumentNullException("proxyContext");
        }
        SilverIT me = target as SilverIT;
        if (me == null)
        {
            throw new ArgumentNullException("target");
        }
        methodName = base.GetMemberName(methodName, proxyContext);

        switch (methodName)
        {
            case "Empty":
                isVoid = true;
                return null;
            case "AddFiveToInput":
                isVoid = false;
                return AddFiveToInput_MethodProxy(target, xmlargs, proxyContext);
        }
        return base.InvokeMethod(target, methodName, xmlargs, proxyContext, out isVoid);
    }
}

We have declared our method information and added one parameter. Then, to invoke that method, we first check the method name that was requested by the caller and call our proxy that first retrieves the mandatory parameter value.So now, if you it the endpoint _api/silverit/AddFiveToInput(5), you’ll get this:

<d:AddFiveToInput xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml" m:type="Edm.Int32">10</d:AddFiveToInput>

If you want to work with mutliple parameters, you have of course to add them to the Parameters collection of your method but note that when calling your function later on, you’ll have to specify the name and the value like this : /_api/silverit/myfunction(param1=’value1′,param2=’value2′). With one parameter only, it’s not required to specify its name, it’s always good to know.
So, the first question is answered. Note here that I’m only handling a primitive parameter and I’m only returning a primitive type (int). With that only, it’s hard to go bar for a real world implementation. So, let’s now answer the second question.

How to return custom types
So far, things were quite easy, it becomes a little bit more complex although when you have all the pieces of the puzzle, it’s not that hard and if I hadn’t had the debugger problem, I would have been much faster. Anyway, you might also have problems with the debugger because I’m not convinced it is something specific to my environment (freshly installed BTW). As I told you in the introduction, in order to return a custom type, you need to implement a ValueTypeConverter. So let’s first declare a very basic type:

[Serializable]
[ClientCallableType(
 ClientLibraryTargets = ClientLibraryTargets.RESTful, Name = "TestType", ServerTypeId = "{E1BB82E8-0D1E-4e52-B90C-684802AB4EF7}",
 ValueObject = true)]
public class TestType
{

    public TestType() { }
    [ClientCallableProperty(Name = "TestProp", ClientLibraryTargets = ClientLibraryTargets.RESTful)]
    public string TestProp { get; set; }
}

We decorate it with the ClientCallableType attribute, say it’s a RESTFul one and give it a name. We declare a dummy property, just to see if we’re able to return an object of that type with something in it. Now, it’s time to implement the converter:

[ValueTypeConverter(typeof(TestType), TargetTypeId = "{F6D0EA4C-F8BE-4283-867F-DA717E861581}")]
public class TestTypeConvertor : ValueTypeConverter
{
    protected override string TargetTypeScriptClientFullName
    {
        get
        {
            return "SilverIT.TestType";
        }
    }
    protected override IEnumerable<PropertyInformation> GetProperties(ProxyContext proxyContext)
    {

        if (proxyContext == null)
            throw new ArgumentNullException("proxyContext");

        PropertyInformation p1 = new PropertyInformation
        {
            Name = "TestProp",
            IsStatic = false,
            PropertyODataType = ODataType.Primitive,
            ExcludeFromDefaultRetrieval = false,
            PropertyType = typeof(string),
            ReadOnly = false,
            RequiredForHttpPutRequest = false,
            DefaultValue = false,
            OriginalName = "TestProp",
            RESTfulPropertyType = null,
            RequiredRight = ResourceRight.Default
        };

        yield return p1;

    }

    protected override object GetProperty(object target, string propName, ProxyContext proxyContext)
    {
        if (proxyContext == null)
            throw new ArgumentNullException("proxyContext");
        if (target == null)
            throw new ArgumentNullException("target");
        TestType t = (TestType)target;
        if (propName == null)
            throw new ArgumentNullException("propName");

        switch (propName)
        {
            case "TestProp":
                return (object)t.TestProp;              

            default:
                return base.GetProperty(target, propName, proxyContext);
        }
    }
}

A very important note here is that in the TargetTypeScriptClientFullName property, make sure you return something with the following pattern “prefix.something”. If you don’t use a prefix, you won’t be able to use your type in a collection such as List…good to know since I had the problem :). Also important : avoid overriding the TargetBaseType property or if you do make sure to to it well. I made a mistake there which lead me to StackOverflow exceptions (in the system)…until I decided not to override it but that was again, taken from Reflector and just modified to my needs but it seems I’d better have not to override it :).

Afterwards, we implement GetProperties() that is called by the system. For each of them, the system calls the GetProperty method where we tell it how to proceed. In this case, I just cast the target parameter and call the corresponding property.Once we’ve done that, we can start thinking of using our custom type as a return type of a function or as the type of a given property. Similarly to the previous example with our function, we can now just declare a function like this:

[ClientCallableMethod]
public TestType ReturnTestType()
{
    TestType t = new TestType();
    t.TestProp = "test value";
    return t;
}

and the in the GetMethods method, we describe our method like this:

MethodInformation m0 = new MethodInformation
{
    Name = "ReturnTestType",
    IsStatic = false,
    OperationType = OperationType.Default,
    ClientLibraryTargets = ClientLibraryTargets.All,
    OriginalName = "ReturnTestType",
    WildcardPath = false,
    ReturnType = typeof(TestType),
    ReturnODataType = ODataType.ComplexType,
    RESTfulExtensionMethod = true,
    ResourceUsageHints = ResourceUsageHints.None,
    RequiredRight = ResourceRight.Default
};
yield return m0;

We’ve adjusted the ReturnType & ReturnODataType properties. So, now if you hit the _api/silverit/ReturnTestType() function, you’ll get this:

<d:ReturnTestType xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"    xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"    xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml"    m:type="SilverIT.TestType">
<d:TestProp>test value</d:TestProp>
</d:ReturnTestType>

If instead of a method, you want to return a custom type as a property (included in the sample solution), you just need to decorate a property with the ClientCallableProperty attribute and to declare a PropertyInformation object instead of a MethodInformation object.Let’s now answer the third question.

How to work with collections
Well, there are several ways and several kind of collections, ReadOnlyCollection, List, Dictionary etc…If we want to return a List, we can keep the TestType as developed before taking into account the important remark I made about the prefix. Then, we can just declare either a method or a property with a return type of List:

[ClientCallableProperty(Name = "SubTypeList", ClientLibraryTargets = ClientLibraryTargets.RESTful)]
public List<TestType> SubTypeList
{
    get
    {
        List<TestType> ReturnedList = new List<TestType>();
        for(int i =0;i<5;i++)
        {
            TestType item = new TestType();
            item.TestProp = i.ToString();
            ReturnedList.Add(item);
        }

        return ReturnedList;
    }
    set
    {

    }
}

So, If I add the above property to my SilverIT class defined earlier, and if I add the following property information to its server stub, I’ll be able to work with a collection of my custom type. Remember that i’ve already implemented the converter:

PropertyInformation p0 = new PropertyInformation
{
    Name = "SubTypeList",
    IsStatic = true,
    PropertyODataType = ODataType.MultiValue,
    ExcludeFromDefaultRetrieval = false,
    PropertyType = typeof(List<TestType>),
    RequiredForHttpPutRequest = false,
    OriginalName = "SubTypeList",
    RequiredRight = ResourceRight.Default
};

yield return p0;

So this time, I had to specify ODataType.MultiValue to return a collection. Note that if you implement your own type derived from a collection (included as an example in the code), you’ll have to specify ODataType.ComplexType. If you want to return a list of primitive types, it’s even easier since you won’t have to implement your own converter. Dictionaries work fine as well but what I noticed so far is that if you don’t specify string as type for the key, it doesn’t work…So, if you specify

Dictionary<string,T> // works fine
Dictionary<Guid or whatever,T> // doesn't work

I don’t know yet why.If you download the example I wrote, you’ll see what I explained here and how to nest types etc..Basically, here is a screenshot of what’s under the ReturnTestType() function:

<d:ReturnTestType xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"   xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"   xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml"   m:type="SilverIT.TestType">
<d:TestProp>test value</d:TestProp>
<d:ValueCollection>...</d:ValueCollection>
<d:StringCollection m:type="SilverITStringCollection">...</d:StringCollection>
<d:SubType m:type="SilverIT.SilverITStringCollectionItem">...</d:SubType>
<d:SubTypeList m:type="Collection(SilverIT.SilverITStringCollectionItem)">...</d:SubTypeList>
</d:ReturnTestType>

when you see dots (…) it’s because I’ve collapsed the nodes but they’re all collections or complex types. I’ve also included 5 other functions as an example. You can download the sample here. Note that it’s only the begining of exploring this API since we’ve just started to implement the GET verb but the API is also there to implement POST/PUT/DELETE, so still a lot to do but GET is already not that bad!

Happy Coding!

Advertisements

About Stephane Eyskens

Office 365, Azure PaaS and SharePoint platform expert
This entry was posted in SharePoint and tagged , . Bookmark the permalink.

2 Responses to Extending SharePoint 2013’s REST _api endpoints with Complex Types & Collections

  1. thanks. So much ritual code…

    Like

  2. is there any way to return 404 from client callable method?

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s