Making requests is dependent on understanding Hosts. The functionality described here is common for all types of hosts but is inaccessible without instantiating or retrieving a host depending on the type of host in use. It is recommended that you review the material on Hosts, Client Credentials REST Host and Default REST Host before continuing with this topic.
For the purposes of the examples in this topic we will be using the Client Credentials REST Host. However its important to note that the process of making requests to REST is identical for any host once you have obtained an instance of the host.
[toc]
Types Of Requests
The SDK allows for the same types of requests that the REST API supports. These include GET, POST, PUT, DELETE as well as issuing Batch and Widget rendering requests. You can access any REST Url your community currently supports. If you try to make a request to a URL that your community doesn't support, for instance one only available in a newer version, it will fail. The important reason for mentioning this is the SDK itself doesn't understand what an endpoint is or does, it simply sees it as a REST Url that will respond with the appropriate payload.
Types Of Responses
This is where the SDK earns its stripes. When it comes to the community REST API, there are only 4 types of responses. The 2 most common are XML or JSON. Additionally some endpoints can return you raw binary data or even html data. The SDK can handle all of these and in some cases can give you a helping hand by parsing the response into a more useable format. These are the response types that are available in the SDK and when or how they can be used:
- String - This works for either a JSON or XML request. In this case the JSON or XML payload is returned as a string. This is also used if you are using an html based endpoint like the widget rendering endpoint.
- Stream - This basically is returning you the response stream. It works for all types of calls. You especially need this if your payload is raw binary data. Otherwise you can read the stream into any appropriate form.
- XElement - This is only available when the REST API is responding with XML. In this case the XML is read into an easy to use .NET XElement object.
- dynamic - This can only be used with JSON response types because it's the JSON that gets parsed into a dynamic object. A dynamic object essentially allows you to work with the response like its was a strongly typed object without the actual need to create one.
For each request type, you will find a method for each response type, with the exception of widget rendering which is essentially a GET request.
Getting Started
As has been mentioned it's a host that gives you access to make REST calls. For the purposes of example this topic uses the Client Credentials REST Host but it is important to re-iterate that the calls illustrated in this topic are identical regardless of host, the only variance is how the host is retrieved. If you would like to try any of these examples you can create a new console application in Visual Studio then install the SDK as described here.
Loading up a Host
In our examples we have a simple method to load our host to avoid repetitive code. You can do this inline as well if you choose. You will also need to ensure you have setup an Oauth client that is private with a Client Credentials grant type. The urls can be whatever you want, they are not relevant using client credentials. If you are unsure how to do this refer to the OAuth section of the REST API's Authentication topic.
private static ClientCredentialsRestHost GetHost() { return new ClientCredentialsRestHost("admin" ,"http://yoursiteurl.com" , "[Your Client Id]" , "[Your Client Secret]"); }
If you were using the Default REST Host you can use the same pattern with a small adjustment, assuming you have set it up correctly. After this all the REST requests work exactly the same.
private static Host GetHost() { return Host.Get("default"); }
Building a Request
The way you make the types of requests with the types of responses we have discussed is through invoking particular methods on the host. The naming pattern is constant for all of them so for each request type, there is an option for a different response type.
Given this list of request types, essentially here is the available methods:
- Host.GetTo[Dynamic|String|Stream|XElement](...)
- Host.PostTo[Dynamic|String|Stream|XElement](...)
- Host.PutTo[Dynamic|String|Stream|XElement](...)
- Host.DeleteTo[Dynamic|String|Stream|XElement](...)
Additionally each of those has an async counterpart which we will cover more in depth later:
- Host.GetTo[Dynamic|String|Stream|XElement]Async(...)
- Host.PostTo[Dynamic|String|Stream|XElement]Async(...)
- Host.PutTo[Dynamic|String|Stream|XElement]Async(...)
- Host.DeleteTo[Dynamic|String|Stream|XElement]Async(...)
Anatomy of a Request
Every request requires a minimum of 2 pieces of information with the exception of the batch request which will be covered separately. These are:
- REST Version - This is the endpoint version. If you were making a request manually it is part of the url (/api.ashx/v2/...). In the SDK the number from this required as the first argument.
- Url - This is the actual endpoint you are trying to contact WITHOUT /api.ashx/v2/. The SDK will build this part of the url automatically using the version in the first argument.
Given this if we wanted to produce a GET request that required no parameters it would look like this:
var host = GetHost(); var response = host.GetToDynamic(2, "info.json");
Impersonation
Each request method gives you the ability to opt-in or out of impersonation. This however only works if the host you are using supports impersonation. While it's recommended that a host supports impersonation, it may not. You should refer to your host's documentation on how it handles impersonation of another user if at all. If you know you do not wish to impersonate, you should pass in false for the enableImpersonation parameter. The enableImpersonation argument is optional and when not supplied defaults to true.
Consult the topic on the Client Credentials REST Host and Default REST Host for how this parameter is used and how they support impersonation.
The following calls both will tell the host to use impersonation if its supported:
var host = GetHost(); var response = host.GetToDynamic(2, "info.json")
OR
var host = GetHost(); var response = host.GetToDynamic(2, "info.json",true); //addition of "enableImpersonation" parameter
Options Objects
Options objects are classes passed in to each type of request that can have several optional arguments. This is also where additional optional arguments would be added in the future. Options themselves are also optional.
Querystring Parameters
Querystring parameters are parameters appended to the REST Url after a '?' symbol. They are the most common on GET requests but are supported on all the request types. You do not need to actually add them to the url yourself, instead if you evaluate the options object on the method you are calling you should find a property to add query string parameters.
The following example illustrates a GET call using querystring parameters. The pattern is the same for POST,PUT and DELETE requests as well.
var host = GetHost(); var querystring = new NameValueCollection(); querystring.Add("pageSize","20"); querystring.Add("pageIndex","1"); var response = host.GetToDynamic(2, "users.json",true,new RestGetOptions() { QueryStringParameters = querystring });
Path Parameters
Like querystring parameters path parameters are used to add dynamic data to a request however unlike querystring parameters these aren't added to anything, rather they are substitution values, ususally meant to replace a value in the url.
The substitution value is a key name surrounded by {}. For example the key userid is a path parameter in the following url:
users/{userid}.json
A path parameter can be at any position in the url string. To define a value for a parameter you pass it in to the PathParameters of the options object. PathParameters are supported on all GET,POST,PUT and DELETE requests. You can define your own path parameter as well, as long as it is provided a value in the PathParameters collection.
This example uses PathParameters in a simple GET.
var host = GetHost(); var pathParameters = new NameValueCollection(); pathParameters.Add("userid", "2100"); var response = host.GetToDynamic(2, "users/{userid}.json", true, new RestGetOptions() { PathParameters = pathParameters });
Post Parameters
Unlike the other parameter types, PostParameters are added to the request body rather than be part of the url. These types of parameters are only supported for POST and PUT requests. The following user creation method passes PostParameters:
var host = GetHost(); var postParameters = new NameValueCollection(); postParameters.Add("Username","somerandomusername"); postParameters.Add("PrivateEmail", "somerandomusername@localhost.com"); postParameters.Add("Password","SuperComplexPassword"); var response = host.PostToDynamic(2, "users.json", true, new RestPostOptions() { PostParameters = postParameters });
Custom Headers
If for some reason you require custom headers from your application you can specify them on the options object. This will also overwrite existing headers. This should be supported on all request options objects.
var customHeaders = new NameValueCollection() { {"X-App-Name","MyCustomApp"} }; dynamic response = host.GetToDynamic(2, "info.json", true, new RestGetOptions() { AdditionalHeaders = customHeaders });
Handling a Response
Most of the response types are fairly straight forward and dealing with them is not really specific to using the SDK. Here are the response types and how your code can potentially handle them:
- XElement - This gives you a .NET XElement object representing XML. Using this element you can read the data using LINQ to Xml queries.
- Stream - There are different paths for streams depending on the payload. For XML or JSON the stream can be read into memory and then used to deserialize the data into classes you have created in your application. If the payload is a file you can read the stream into the appropriate file and save it to disk. Generally you would not use stream to convert to a string because that option is already available.
- String - This is most appropriate for Html or JSON return types. You can use the string to output html directly or it can be used for JSON serialization or manipulation.
The last probably most powerful response is dynamic and while it is also not an SDK specific response type, its worth spending a bit more time on since its relatively new. The downside with these is that unlike strongly typed classes, there is no Intellisense help when writing code.
You interact wth dynamics just like a class, by accessing its properties. The difference however is that the properties aren't actually defined, they are dynamically bound at runtime.
A simple example would be if you had a plain old object called "Foo" and you in your code you called the property "Name". If your Foo object does not have a property called "Name", your code will not compile. This is because the compiler checks for these types of errors when compiling. If Foo is type dynamic and you called Foo.Name the code would compile without error. This is because this reference is ignored by the compiler for dynamics until runtime. Using this concept we do not have to write a class for every possible response from the REST API. We simply take the response and convert it to a type of dynamic. This means you can code against the properties even though the code does not yet know if a property exists yet. Only after its evaluated at runtime if the property does not exist will it throw an error.
We base the dynamic binding on the hierarchal structure of the JSON response so you can only use dynamic responses with JSON requests. Review this JSON response(this response has been shortened for example purposes):
{ "InfoResult": { "SiteName": "sitename", "SiteDescription": "sitedescription", }, "AccessingUser": { "ContentId": "49fec544-6df7-4a82-872b-f8be586d5e9e", "ContentTypeId": "9f5a6721-639d-4e1d-ab6a-ce63b7750f46", "DisplayName": "displayname", "Username": "username", "Id": 6 }, "Errors": [ "string", "string" ] }
In this example, InfoResult would be a property of the dynamic object and Sitename would be a property of InfoResult. Similarly AccessingUser is a property on the dynamic object and Username is a property of Accessing User.
var host = GetHost(); var response = host.GetToDynamic(2, "info.json"); Console.WriteLine("Site Name:" + response.InfoResult.SiteName); Console.WriteLine("Accessing Username:" + response.AccessingUser.Username);
Uploading a File
The SDK also supports uploading files to use as attachments to other pieces of contents. The advantage of the SDK upload is it is similar to how you upload files in the UI. If the file is under 15mb the whole file is sent, otherwise it is broken up into 15mb chunks and transmitted individually. This bypasses request limits and helps avoid timeouts by spreading the file out over multiple requests. Unlike other requests this will give you a strongly typed UploadedFileInfo argument back in lieu of the other types of responses already discussed.
Here is an example of a file upload in its most basic form
using (var fileStream = new FileStream("c:\\test.png", FileMode.Open, FileAccess.Read)) { fileStream.Position = 0; Guid uploadContextId = Guid.NewGuid(); UploadedFile file = new UploadedFile(uploadContextId, "test.png", "image/png", fileStream); UploadedFileInfo response = host.UploadFile(file); if (!response.IsError) Console.WriteLine(response.UploadContext); else Console.WriteLine(response.Message); }
You can also track the progress of each chunk using the UploadProgess action on the delegate that will give you FileUploadProgress object to interact with. Below we will print the percent complete.
using (var fileStream = new FileStream("c:\\test.png", FileMode.Open, FileAccess.Read)) { fileStream.Position = 0; Guid uploadContextId = Guid.NewGuid(); UploadedFile file = new UploadedFile(uploadContextId, "test.png", "image/png", fileStream); UploadedFileInfo response = host.UploadFile(file,new RestFileOptions() { UploadProgress = (p) => { Console.WriteLine("Percent Complete:" + p.PercentComplete); } }); if (!response.IsError) Console.WriteLine(response.UploadContext); else Console.WriteLine(response.Message); }
You can use that file to create an attachment to something like a forum thread using the context Id you specified:
using (var fileStream = new FileStream("c:\\test.png", FileMode.Open, FileAccess.Read)) { fileStream.Position = 0; Guid uploadContextId = Guid.NewGuid(); UploadedFile file = new UploadedFile(uploadContextId, "test.png", "image/png", fileStream); UploadedFileInfo fileInfo = host.UploadFile(file,new RestFileOptions() { UploadProgress = (p) => { Console.WriteLine("Percent Complete:" + p.PercentComplete); } }); f (!fileInfo.IsError) { var pathParameters = new NameValueCollection(); pathParameters.Add("forumid","2958"); var postParameters = new NameValueCollection(); postParameters.Add("Subject","This is a cool Forum"); postParameters.Add("Body","I think I will post a thread in it"); //Now add the file postParameters.Add("FileName", "test.png"); postParameters.Add("ContentType", "image/png"); postParameters.Add("FileUploadContext",fileInfo.UploadContext.ToString());//Use the upload context Id from our file upload dynamic threadResponse = host.PostToDynamic(2, "forums/{forumid}/threads.json", true, new RestPostOptions() { PathParameters = pathParameters, PostParameters = postParameters }); } }
Batching
Batch requests are making several REST requests in a single call. This reduces overhead because only the batch request is processed over HTTP, the containing requests are executed in process on the server. The SDK makes batch requests easier as well since you don't have to know the request format like you do making a batch request against REST directly.
A batch request is a collection of batch request objects translated into individual requests before being sent to community.. Batch request objects contain similar information to a single request method with a couple of exceptions.
- The default APIVersion will always be 2 though there is an APIVersion property to override this value.
- Each request must have a sequence specified in the constructor, the sequence is a zero based so your first sequence value should be 0.**
- You must specify that a request is GET,POST etc in the RestMethod property since a batch request itself is a POST.
- QueryString and PostParameters that you are used to in previous requests are collapsed into a single collection called RequestParameters and are used querystring or body parameters depending on the RestMethod. PathParameters are separate and work the same.
**Sequence is always required but only honored when a batch request is set to be sequential. Otherwise the requests are processed in parallel on the server.
The following example executes a GET and POST request in a single request in parallel:
var batchRequestList = new List<BatchRequest>(); var userRequest = new BatchRequest("users/{username}.json", 0); userRequest.RestMethod = RestMethod.GET; serRequest.PathParameters = new NameValueCollection() { {"username","pmason"} }; batchRequestList.Add(userRequest); var threadRequest = new BatchRequest("forums/{forumid}/threads.json", 1); threadRequest.RestMethod = RestMethod.POST; threadRequest.PathParameters = new NameValueCollection() { {"forumid","2958"} }; threadRequest.RequestParameters = new NameValueCollection() { {"Subject","This is a great thread"}, {"Body","I love this forum so much!"} }; batchRequestList.Add(threadRequest); dynamic response = host.BatchRequestToDynamic(2, batchRequestList, true);
You can force this to run sequentially, meaning each request will be run in order of its sequence value, by adding the options argument and setting the RunSequentially flag:
dynamic response = host.BatchRequestToDynamic(2, batchRequestList, true,new BatchRequestOptions() { RunSequentially = true });
Batch responses are similar to the request, where you will get back a collection of the responses. Each response has metadata about the response including the response code and then a BatchResponse property which is the response data returned from the request endpoint. This data is identical to the format of the endpoint had it been done in a single request.
{ "BatchResponses":[ { "Url": "api.ashx/v2/users/pmason.json", "StatusCode": 200, "Sequence": 0, "BatchResponse": { "User": { //This looks like the user SHOW response } } }, { "Url":"api.ashx/v2/forums/2958/threads.json", "StatusCode":200, "Sequence":1, "BatchResponse": { "Thread": { //This looks like the Thread Create response } } } ] }
Now in the SDK you don't have to work with the raw response. Like other requests with the exception of the file request it supports all the previously mentioned response types. Understanding the structure does help however understand how to use the dynamic response. So given the request above here is how you interact with the response which will be an array:
Console.WriteLine("User Request Response Code:" + response.BatchResponses[0].StatusCode); Console.WriteLine("User Id:" + response.BatchResponses[0].BatchResponse.User.Id); Console.WriteLine("Thread Create Request Response Code:" + response.BatchResponses[1].StatusCode); Console.WriteLine("Thread Id:" + response.BatchResponses[1].BatchResponse.Thread.Id);
An important note is that if you ran the request sequentially, if 1 request fails it considers the entire request a failure and does not process further.
Using Async
If your app uses async programming you can use the SDK's async methods. For every REST request method there is a corresponding Async version. To learn more about the async pattern see MSDN.
Here is a simple async method that returns a user Id:
private async Task<int> GetUserId(string username) { var host = GetHost(); var pathParameters = new NameValueCollection() { {"username", username} }; dynamic response = await host.GetToDynamicAsync(2, "users/{username}.json", true, new RestGetOptions() { PathParameters = pathParameters }); return Task.FromResult(response.User.Id); }