Thursday, August 6, 2015

Fix problem with returned xml instead of json from Sharepoint social API when JSON light is used

JSON light was introduced in Sharepoint Online in April 2014: JSON Light support in REST SharePoint API released. It allows to reduce traffic size by returning less metadata from the server when you use Sharepoint social API and as result perform actions faster. Sharepoint social API may return result in 2 formats: xml and json. In order to get results in json format we need to set Accept HTTP header to “application/json; odata=verbose”. Note on odata=verbose. It tells to the server that it should return maximum available metadata. Before JSON light it was the only option. After introducing JSON light feature the following options became available:

  • application/json; odata=verbose
  • application/json; odata=minimalmetadata
  • application/json; odata=nometadata

Using these options we may reduce metadata returned from server.

Everything works quite well in Sharepoint Online. However on-premise installations there may be problems. Let’s consider the following code which loads sites followed by current user:

   1: jQuery.ajax({
   2:     url: "http://example.com/_api/social.following/my/Followed(types=4)",
   3:     method: "GET",
   4:     headers: { "Accept": "application/json; odata=verbose" },
   5:     success: function (data) {
   6:         jQuery.each(data.d.Followed.results, function (index, item) {
   7:             // ...
   8:         });
   9:     },
  10:     error: function () {
  11:         // ...
  12:     }
  13: });

This example uses odata=verbose and returns result similar to this:

   1: {  
   2:    "d":{  
   3:       "Followed":{  
   4:          "__metadata":{  
   5:             "type":"Collection(SP.Social.SocialActor)"
   6:          },
   7:          "results":[  
   8:             {  
   9:                "AccountName":null,
  10:                "ActorType":2,
  11:                "CanFollow":true,
  12:                "ContentUri":"http://example.com/teams/test-1",
  13:                "EmailAddress":null,
  14:                "FollowedContentUri":null,
  15:                "Id":"...",
  16:                "ImageUri":null,
  17:                "IsFollowed":true,
  18:                "LibraryUri":null,
  19:                "Name":"test",
  20:                "PersonalSiteUri":null,
  21:                "Status":0,
  22:                "StatusText":null,
  23:                "TagGuid":"00000000-0000-0000-0000-000000000000",
  24:                "Title":null,
  25:                "Uri":"http://example.com/teams/test-1"
  26:             },
  27:             {  
  28:                "AccountName":null,
  29:                "ActorType":2,
  30:                "CanFollow":true,
  31:                "ContentUri":"http://example.com/teams/test-2",
  32:                "EmailAddress":null,
  33:                "FollowedContentUri":null,
  34:                "Id":"...",
  35:                "ImageUri":null,
  36:                "IsFollowed":true,
  37:                "LibraryUri":null,
  38:                "Name":"test",
  39:                "PersonalSiteUri":null,
  40:                "Status":0,
  41:                "StatusText":null,
  42:                "TagGuid":"00000000-0000-0000-0000-000000000000",
  43:                "Title":null,
  44:                "Uri":"http://example.com/teams/test-2"
  45:             },
  46:             ...
  47:         ]}
  48:     }
  49: }

If we just change odata=verbose to odata=nometadata we will get error, because server will return xml instead of json:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <d:Followed
   3:     xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
   4:     xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
   5:     xmlns:georss="http://www.georss.org/georss"
   6:     xmlns:gml="http://www.opengis.net/gml"
   7:     m:type="Collection(SP.Social.SocialActor)">
   8:     <d:element>
   9:         <d:AccountName m:null="true" />
  10:         <d:ActorType m:type="Edm.Int32">2</d:ActorType>
  11:         <d:CanFollow m:type="Edm.Boolean">true</d:CanFollow>
  12:         <d:ContentUri>http://example.com/teams/test-1</d:ContentUri>
  13:         <d:EmailAddress m:null="true" />
  14:         <d:FollowedContentUri m:null="true" />
  15:         <d:Id>...</d:Id>
  16:         <d:ImageUri m:null="true" />
  17:         <d:IsFollowed m:type="Edm.Boolean">true</d:IsFollowed>
  18:         <d:LibraryUri m:null="true" />
  19:         <d:Name>test</d:Name>
  20:         <d:PersonalSiteUri m:null="true" />
  21:         <d:Status m:type="Edm.Int32">0</d:Status>
  22:         <d:StatusText m:null="true" />
  23:         <d:TagGuid m:type="Edm.Guid">00000000-0000-0000-0000-000000000000</d:TagGuid>
  24:         <d:Title m:null="true" />
  25:         <d:Uri>http://example.com/teams/test-1</d:Uri>
  26:     </d:element>
  27:     <d:element>
  28:         <d:AccountName m:null="true" />
  29:         <d:ActorType m:type="Edm.Int32">2</d:ActorType>
  30:         <d:CanFollow m:type="Edm.Boolean">true</d:CanFollow>
  31:         <d:ContentUri>http://example.com/teams/test-2</d:ContentUri>
  32:         <d:EmailAddress m:null="true" />
  33:         <d:FollowedContentUri m:null="true" />
  34:         <d:Id>...</d:Id>
  35:         <d:ImageUri m:null="true" />
  36:         <d:IsFollowed m:type="Edm.Boolean">true</d:IsFollowed>
  37:         <d:LibraryUri m:null="true" />
  38:         <d:Name>test</d:Name>
  39:         <d:PersonalSiteUri m:null="true" />
  40:         <d:Status m:type="Edm.Int32">0</d:Status>
  41:         <d:StatusText m:null="true" />
  42:         <d:TagGuid m:type="Edm.Guid">00000000-0000-0000-0000-000000000000</d:TagGuid>
  43:         <d:Title m:null="true" />
  44:         <d:Uri>http://example.com/teams/test-2</d:Uri>
  45:     </d:element>
  46:     ...
  47: </d:Followed>

It happens because in order to enable JSON light on-premise additional actions should be done: How to turn on and off the multiple metadata formats for JSON in SharePoint Server 2013. Briefly you need to install Service Pack 1 for Sharepoint 2013 and WCF Data Services 5.6. After that add the following assembly binding redirections to web.config of your web application:

   1: <runtime>
   2:   ...
   3:   <assemblyBinding>
   4:     <dependentAssembly>
   5:       <assemblyIdentity name="Microsoft.Data.Edm" publicKeyToken="31bf3856ad364e35"
   6: culture="neutral" />
   7:       <bindingRedirect oldVersion="5.0.0.0" newVersion="5.6.0.0" />
   8:     </dependentAssembly>
   9:     <dependentAssembly>
  10:       <assemblyIdentity name="Microsoft.Data.Odata" publicKeyToken="31bf3856ad364e35"
  11: culture="neutral" />
  12:       <bindingRedirect oldVersion="5.0.0.0" newVersion="5.6.0.0" />
  13:     </dependentAssembly>
  14:     <dependentAssembly>
  15:       <assemblyIdentity name="Microsoft.Data.Services.Client"
  16: publicKeyToken="31bf3856ad364e35" culture="neutral" />
  17:       <bindingRedirect oldVersion="5.0.0.0" newVersion="5.6.0.0" />
  18:     </dependentAssembly>
  19:     <dependentAssembly>
  20:       <assemblyIdentity name="Microsoft.Data.Services"
  21: publicKeyToken="31bf3856ad364e35" culture="neutral" />
  22:       <bindingRedirect oldVersion="5.0.0.0" newVersion="5.6.0.0" />
  23:     </dependentAssembly>
  24:     <dependentAssembly>
  25:       <assemblyIdentity name="System.Spatial" publicKeyToken="31bf3856ad364e35"
  26: culture="neutral" />
  27:       <bindingRedirect oldVersion="5.0.0.0" newVersion="5.6.0.0" />
  28:     </dependentAssembly>
  29:   </assemblyBinding>
  30: </runtime>

It can be also done using PowerShell script provided in technet article (in this case web.configs of all web applications in your farm will be changed):

   1: $configOwnerName = "JSONLightDependentAssembly"
   2:  
   3: $spWebConfigModClass ="Microsoft.SharePoint.Administration.SPWebConfigModification"
   4:  
   5: $dependentAssemblyPath =
   6: "configuration/runtime/*[local-name()='assemblyBinding' and namespace-uri()='urn:schemas-microsoft-com:asm.v1']"
   7:  
   8: $dependentAssemblyNameStart ="*[local-name()='dependentAssembly'][*/@name='"
   9: $dependentAssemblyNameEnd = "'][*/@publicKeyToken='31bf3856ad364e35'][*/@culture='neutral']"
  10:  
  11: $dependentAssemblyValueStart = "<dependentAssembly><assemblyIdentity name='"
  12: $dependentAssemblyValueEnd =
  13: "' publicKeyToken='31bf3856ad364e35' culture='neutral' /><bindingRedirect oldVersion='5.0.0.0' newVersion='5.6.0.0' /></dependentAssembly>"
  14:  
  15: $edmAssemblyName ="Microsoft.Data.Edm"
  16: $odataAssemblyName ="Microsoft.Data.Odata"
  17: $dataServicesAssemblyName ="Microsoft.Data.Services"
  18: $dataServicesClientAssemblyName ="Microsoft.Data.Services.Client"
  19: $spatialAssemblyName ="System.Spatial"
  20:  
  21:  
  22: $assemblyNamesArray = $edmAssemblyName,$odataAssemblyName,$dataServicesAssemblyName,
  23: $dataServicesClientAssemblyName,$spatialAssemblyName
  24:  
  25:  
  26: Add-PSSnapin Microsoft.SharePoint.Powershell
  27: $webService = [Microsoft.SharePoint.Administration.SPWebService]::ContentService
  28:  
  29:  
  30: ################ Adds individual assemblies ####################
  31:  
  32: For ($i=0; $i -lt 5; $i++)  
  33: {
  34:     echo "Adding Assembly..."$assemblyNamesArray[$i]
  35:  
  36:     $dependentAssembly = New-Object $spWebConfigModClass
  37:     $dependentAssembly.Path=$dependentAssemblyPath
  38:     $dependentAssembly.Sequence =0 # First item to be inserted
  39:     $dependentAssembly.Owner = $configOwnerName
  40:     $dependentAssembly.Name =$dependentAssemblyNameStart + $assemblyNamesArray[$i] +
  41: $dependentAssemblyNameEnd
  42:     $dependentAssembly.Type = 0 #Ensure Child Node
  43:     $dependentAssembly.Value = $dependentAssemblyValueStart + $assemblyNamesArray[$i] +
  44: $dependentAssemblyValueEnd
  45:  
  46:     $webService.WebConfigModifications.Add($dependentAssembly)
  47: }
  48:  
  49: ###############################################################
  50:  
  51: echo "Saving Web Config Modification"
  52:  
  53: $webService.Update()
  54: $webService.ApplyWebConfigModifications()
  55:  
  56: echo "Update Complete"

and restart IIS. After that we will be able to use odata=nometadata when make requests to the social API:

   1: jQuery.ajax({
   2:     url: "http://example.com/_api/social.following/my/Followed(types=4)",
   3:     method: "GET",
   4:     headers: { "Accept": "application/json; odata=nometadata" },
   5:     success: function (data) {
   6:         jQuery.each(data.value, function (index, item) {
   7:             // ...
   8:         });
   9:     },
  10:     error: function () {
  11:         // ...
  12:     }
  13: });

Note that we not only changed odata to nometadata in accept HTTP header, but also now iterate through data.value instead of data.d.Followed.results. If we will check response in fiddler we will get the following:

   1: {  
   2:    "value":[  
   3:       {  
   4:          "AccountName":null,
   5:          "ActorType":2,
   6:          "CanFollow":true,
   7:          "ContentUri":"http://example.com/teams/test-1",
   8:          "EmailAddress":null,
   9:          "FollowedContentUri":null,
  10:          "Id":"...",
  11:          "ImageUri":null,
  12:          "IsFollowed":true,
  13:          "LibraryUri":null,
  14:          "Name":"test",
  15:          "PersonalSiteUri":null,
  16:          "Status":0,
  17:          "StatusText":null,
  18:          "TagGuid":"00000000-0000-0000-0000-000000000000",
  19:          "Title":null,
  20:          "Uri":"http://example.com/teams/test-1"
  21:       },
  22:       {  
  23:          "AccountName":null,
  24:          "ActorType":2,
  25:          "CanFollow":true,
  26:          "ContentUri":"http://example.com/teams/test-2",
  27:          "EmailAddress":null,
  28:          "FollowedContentUri":null,
  29:          "Id":"...",
  30:          "ImageUri":null,
  31:          "IsFollowed":true,
  32:          "LibraryUri":null,
  33:          "Name":"test",
  34:          "PersonalSiteUri":null,
  35:          "Status":0,
  36:          "StatusText":null,
  37:          "TagGuid":"00000000-0000-0000-0000-000000000000",
  38:          "Title":null,
  39:          "Uri":"http://example.com/teams/test-2"
  40:       },
  41:       ...
  42:     ]
  43: }

If we will compare it with result which was returned for odata=verbose we will see that result is returned in different format (less nested objects) and there is no metadata section now:

   1: "__metadata":{  
   2:    "type":"Collection(SP.Social.SocialActor)"
   3: },

In our example with followed sites this is of course minimal overhead, but in other calls it may be significant. Hope that this information will help you if you will face with mentioned problem.

1 comment: