Create a folder in SP2013 using REST and HTTPClient
Asked Answered
A

2

5

I am trying to create a folder in SP2013 using HTTPClient and REST.

The following are the requirements & constraints on the application

  1. I need to create a folder in a 2013 document library using REST. CSOM is not allowed.

  2. The program must be a C# Console Application.

  3. I MUST use HTTPClient ONLY to make all calls to web service. No other old class or library should be used.

  4. Integrated authentication is a MUST. you must not type in your user name and password here in code. it must use the identity of the process.

Based on these constraints I wrote this code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Formatting;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace RESTCreateFolder
{
    class Program
    {
        static void Main(string[] args)
        {
            Program p = new Program();
            int retVal = p.Process().Result;            
        }

        private async Task<int> Process()
        {
            string url = "http://bi.abhi.com/testweb/";
            using (HttpClient client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true }))
            {
                client.BaseAddress = new System.Uri(url);
                client.DefaultRequestHeaders.Add("Accept", "application/json; odata=verbose");
                string digest = await GetDigest(client);
                Console.WriteLine("Digest " + digest);
                try
                {
                    await CreateFolder(client, digest);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    Console.WriteLine(ex.StackTrace);
                }
            }
            return 0;
        }

        private object CreateRequest(string folderPath)
        {
            var type = new { type = "SP.Folder" };
            var request = new { __metadata = type, ServerRelativeUrl = folderPath };
            return request;
        }

        private async Task CreateFolder(HttpClient client, string digest)
        {
            client.DefaultRequestHeaders.Add("X-RequestDigest", digest);
            var request = CreateRequest("/test/foo");
            string json = JsonConvert.SerializeObject(request);
            StringContent strContent = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
            strContent.Headers.ContentLength = json.Length;
            HttpResponseMessage response = await client.PostAsync("_api/web/folders", strContent);
            //response.EnsureSuccessStatusCode();
            if (response.IsSuccessStatusCode)
            {
                string content = await response.Content.ReadAsStringAsync();
                Console.WriteLine(content);
            }
            else
            {
                Console.WriteLine(response.StatusCode);
                Console.WriteLine(response.ReasonPhrase);
                string content = await response.Content.ReadAsStringAsync();
                Console.WriteLine(content);
            }
        }

        public async Task<string> GetDigest(HttpClient client)
        {
            string retVal = null;
            string cmd = "_api/contextinfo";                       
            HttpResponseMessage response = await client.PostAsJsonAsync(cmd, "");
            if (response.IsSuccessStatusCode)
            {
                string content = await response.Content.ReadAsStringAsync();
                JToken t = JToken.Parse(content);
                retVal = t["d"]["GetContextWebInformation"]["FormDigestValue"].ToString();
            }
            return retVal;
        }
    }
}

But this code keeps failing with BAD REQUEST.

Can you make "this" code work and let me know how you fixed it. I found many articles on MSDN but they all use the OLD approach of WebRequest which I cannot use. I must use HttpClient.

EDIT::

Here is the raw request captured from fiddler

POST http://bi.abhi.com/testweb/_api/web/folders HTTP/1.1
Accept: application/json; odata=verbose
X-RequestDigest: 0x8B2A0904D5056E49DB886A72D59A86264A000F9AB14CE728407ECCD6F4369A7AD2585967BE9A57085344A5ACC99A4DA61D59E5EFA9A54B9B83564B2EA736F7F4,21 Aug 2014 20:24:15 -0000
Content-Type: application/json; charset=utf-8
Host: bi.abhi.com
Content-Length: 67
Expect: 100-continue

{"__metadata":{"type":"SP.Folder"},"ServerRelativeUrl":"/test/foo"}

Here is the raw response from fiddler

HTTP/1.1 400 Bad Request
Cache-Control: private, max-age=0
Transfer-Encoding: chunked
Content-Type: application/json;odata=verbose;charset=utf-8
Expires: Wed, 06 Aug 2014 20:24:15 GMT
Last-Modified: Thu, 21 Aug 2014 20:24:15 GMT
Server: Microsoft-IIS/8.0
X-SharePointHealthScore: 0
SPClientServiceRequestDuration: 4
X-AspNet-Version: 4.0.30319
SPRequestGuid: a9c6b09c-9340-10e2-0000-093d0491623a
request-id: a9c6b09c-9340-10e2-0000-093d0491623a
X-RequestDigest: 0x8B2A0904D5056E49DB886A72D59A86264A000F9AB14CE728407ECCD6F4369A7AD2585967BE9A57085344A5ACC99A4DA61D59E5EFA9A54B9B83564B2EA736F7F4,21 Aug 2014 20:24:15 -0000
X-FRAME-OPTIONS: SAMEORIGIN
X-Powered-By: ASP.NET
MicrosoftSharePointTeamServices: 15.0.0.4569
X-Content-Type-Options: nosniff
X-MS-InvokeApp: 1; RequireReadOnly
Date: Thu, 21 Aug 2014 20:24:14 GMT

e6
{"error":{"code":"-1, System.InvalidOperationException","message":{"lang":"en-US","value":"The required version of WcfDataServices is missing. Please refer to http://go.microsoft.com/fwlink/?LinkId=321931 for more information."}}}
0

I have patched my server environment completely and there are no new updates to apply. so I don't know why it says the required wcfdata services is missing.

Amygdala answered 20/8, 2014 at 22:21 Comment(1)
Was it SharePoint online or on premise?Atypical
A
7

I got this resolved. Used the answer provided on

http://social.msdn.microsoft.com/Forums/en-US/a58a4bec-e936-48f9-b881-bc0a7ebb7f8a/create-a-folder-in-sp2013-document-library-using-rest-using-http-client?forum=appsforsharepoint

Apparently, when using StringContent you must set it to "application/json;odata=verbose" otherwise you get a 400 bad request. StringContent sets the Content-Type header.

StringContent strContent = new StringContent(json);
strContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json;odata=verbose");

My full code now looks like

class Program
{
    static void Main(string[] args)
    {
        Program p = new Program();
        int retVal = p.Process().Result;            
    }

    private async Task<int> Process()
    {
        string url = "http://bi.abhi.com/testweb/";
        using (HttpClient client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true }))
        {
            client.BaseAddress = new System.Uri(url);
            client.DefaultRequestHeaders.Add("Accept", "application/json; odata=verbose");
            string digest = await GetDigest(client);
            Console.WriteLine("Digest " + digest);
            try
            {
                await CreateFolder(client, digest);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
            }
        }
        return 0;
    }

    private object CreateRequest(string folderPath)
    {
        var type = new { type = "SP.Folder" };
        var request = new { __metadata = type, ServerRelativeUrl = folderPath };
        return request;
    }

    private async Task CreateFolder(HttpClient client, string digest)
    {
        client.DefaultRequestHeaders.Add("X-RequestDigest", digest);
        var request = CreateRequest("foo");
        string json = JsonConvert.SerializeObject(request);
        StringContent strContent = new StringContent(json);
        strContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json;odata=verbose");
        HttpResponseMessage response = await client.PostAsync("_api/web/getfolderbyserverrelativeurl('test/test123/')/folders", strContent);
        //response.EnsureSuccessStatusCode();
        if (response.IsSuccessStatusCode)
        {
            string content = await response.Content.ReadAsStringAsync();
            Console.WriteLine(content);
        }
        else
        {
            Console.WriteLine(response.StatusCode);
            Console.WriteLine(response.ReasonPhrase);
            string content = await response.Content.ReadAsStringAsync();
            Console.WriteLine(content);
        }
    }

    public async Task<string> GetDigest(HttpClient client)
    {
        string retVal = null;
        string cmd = "_api/contextinfo";                       
        HttpResponseMessage response = await client.PostAsJsonAsync(cmd, "");
        if (response.IsSuccessStatusCode)
        {
            string content = await response.Content.ReadAsStringAsync();
            JToken t = JToken.Parse(content);
            retVal = t["d"]["GetContextWebInformation"]["FormDigestValue"].ToString();
        }
        return retVal;
    }
}
Amygdala answered 26/8, 2014 at 14:57 Comment(0)
S
1

If this is SharePoint Online, with this 4 requirements I'm afraid you cannot do that. You'd need to send the Authentication header or cookie in your request. you could do it adding the SP Client libraries, creating a SharepointOnlineClientCredentials, getting the cookie with method

SharePointOnlineCredentials.GetAuthenticationCookie 

and adding the cookie to the httpclient cookiecontainer.

If you are in SP On premises, I think it won't work either, but you can try to add the NetworkCredentials to the httpclient request: HttpClient.GetAsync with network credentials use the option

UseDefaultCredentials = true 

as it's said in the comment of the Accepted answer. But again, I don't think will work.

Summerly answered 21/8, 2014 at 7:57 Comment(5)
I am already doing "UseDefaultCredentials = true". I am also able to get the digest correctly. so the credentials are fine. But the BAD REQUEST happens when I try to create the folder.Amygdala
OK sorry. Take a look to this link about the REST call. I think you have to call the Add method: msdn.microsoft.com/en-us/library/office/… something like: /_api/web/folders/add('/Shared Documents/Folder A/Folder B')?@target='<host web url>'Summerly
Probably you won't need the Target querystring param. Also, with your code, I think you're almost there, but the call should be: /_api/web/getfolderbyserverrelativeurl('/Shared Documents/Folder A')/folders('Folder B') using the getfolderbyserverrelativeurl method before the /foldersSummerly
does not work. the error is {"error":{"code":"-2147024809, System.ArgumentException","message":{"lang":"en-US","value":"Value does not fall within the ex pected range."}}} If possible, can you copy paste my code above in vs.net and try to make it work against your enviornment?Amygdala
sorry, but I don't have an On premises environment right now (working with SP Online). My suggestion: open your SP site using Google Chrome, open a new tab and using the Postman pluggin, try some variations of the REST API URL until it works. I'm pretty sure the problem is the URL that you're using, ensure the Library is in the site, etc.Summerly

© 2022 - 2024 — McMap. All rights reserved.