I have recently been working on a website that integrates with various cloud storage sites including Dropbox, SkyDrive, and GoogleDocs. All of these provide great REST APIs (although GoogleDocs uses atom feeds which are ugly!); however, few provide a good SDK for .Net. There are a few good libraries for doing REST in .Net, and I decided to give RestSharp try. Although this is a great library, it did not do everything I needed, and it did a lot more than I needed as well.
I figure that there are many other people like me that want to take advantage of the great REST services that are available, so I’d like to share a simple REST class that I created that has been a HUGE help for me. I really like how RestSharp worked, so my API is similar; however, my version has only the features that I needed, so it is much smaller. If you are interested, feel free to give it a try. This code uses Json.NET to serialize the JSON objects. Here is the complete code as well as a few examples for connecting with popular REST services.
using System;
using System.IO;
using System.Net;
using System.ServiceModel.Syndication;
using System.Text;
using System.Xml;
using Newtonsoft.Json;
namespace Freedom.Data.Cloud.Rest
{
public enum Method
{
GET, POST, PUT
}
public struct ContentTypes
{
// Just a few content types that I used
// This struct can obviously be extended for whatever your needs are!
public const string APPLICATION_JSON = "application/json";
public const string APPLICATION_ATOM_PLUS_XML = "application/atom+xml";
public const string APPLICATION_X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded";
public const string IMAGE_JPEG = "image/jpeg";
}
public class RestException : Exception
{
public RestException() : base() { }
public RestException(string message) : base(message) { }
public RestException(string message, Exception ex) : base(message, ex) { }
}
/* This is a simple class that only exposes the methods and properties
* that I needed. Feel free to extend this for whatever your needs might be. */
public class RestRequest
{
/* I have not needed to expose the actual HttpWebRequest object
* however that would be simple to do if you needed to. Another
* option is to just wrap the properties that you need as I have
* done with the Uri. */
private HttpWebRequest _request;
public string Uri
{
get { return _request.Address.AbsoluteUri; }
}
public WebResponse Response { get; private set; }
public RestRequest(string uri, Method method)
{
_request = (HttpWebRequest)WebRequest.Create(uri);
_request.Method = method.ToString();
}
public void SetContentType(string value)
{
_request.ContentType = value;
}
public void SetContentLength(long value)
{
_request.ContentLength = value;
}
public void AddHeader(string name, string value)
{
_request.Headers.Add(name, value);
}
public void AddBody(string value)
{
byte[] bytes = Encoding.ASCII.GetBytes(value);
AddBody(bytes);
}
public void AddBody(byte[] bytes, int chunkSize = 1024)
{
using (Stream stream = _request.GetRequestStream())
{
int offset = 0;
while (offset < bytes.Length) { int count = (chunkSize > bytes.Length - offset ? bytes.Length - offset : chunkSize);
stream.Write(bytes, offset, count);
offset += count;
}
}
}
public void AddBody(object value)
{
AddBody(JsonConvert.SerializeObject(value));
}
public string GetResponseHeader(string name)
{
return Response.Headers[name];
}
public string Execute()
{
try
{
Response = _request.GetResponse();
string content = null;
using (StreamReader reader = new StreamReader(Response.GetResponseStream()))
{
content = reader.ReadToEnd();
}
return content;
}
catch (WebException ex)
{
Response = ex.Response;
string error = null;
using (StreamReader reader = new StreamReader(Response.GetResponseStream()))
{
error = reader.ReadToEnd();
}
throw new RestException(error, ex);
}
}
public T ExecuteJson<T>() where T : class
{
string content = Execute();
return JsonConvert.DeserializeObject(content);
}
public SyndicationFeed ExecuteSyndicationFeed()
{
string content = this.Execute();
SyndicationFeed feed = null;
using (StringReader sr = new StringReader(content))
{
using (XmlReader reader = XmlReader.Create(sr))
{
feed = SyndicationFeed.Load(reader);
}
}
return feed;
}
public SyndicationItem ExecuteSyndicationItem()
{
string content = this.Execute();
SyndicationItem item = null;
using (StringReader sr = new StringReader(content))
{
using (XmlReader reader = XmlReader.Create(sr))
{
item = SyndicationItem.Load(reader);
}
}
return item;
}
}
}
Now for a few examples. One of my main requirements was to upload files. This was typically done by setting the content of the request to the raw file bytes.
SkyDrive:
I created a simple object for the Json to serialize to (only using the properties I needed):
public class UploadResponse
{
public string Id { get; set; }
public string Source { get; set; }
}
And here is how I actually uploaded the file:
public string UploadFile(string accessToken, string folderId, string fileName, byte[] bytes)
{
RestRequest request = new RestRequest(
string.Format("{0}{1}/files/{2}?access_token={3}",
Settings.Default.APIUrl,
folderId,
fileName,
accessToken), Method.PUT);
request.AddBody(bytes);
UploadResponse ur = request.ExecuteJson<UploadResponse>();
return ur.Id;
}
Getting a list of files or folders is equally as easy. First I created a few classes for the Json to serialize to:
public class SkyDriveFileList
{
public List Data { get; set; }
}
public class SkyDriveFile
{
public string id { get; set; }
public string name { get; set; }
public string upload_location { get; set; }
public DateTime? when_taken { get; set; }
public string type { get; set; }
public string source { get; set; }
public DateTime created_time { get; set; }
public DateTime updated_time { get; set; }
public List images { get; set; }
}
public class SkyDriveImage
{
public string source { get; set; }
public int width { get; set; }
public int height { get; set; }
public SkyDriveImageType type { get; set; }
}
And here is the actual request:
public SkyDriveFileList GetFolders(string accessToken, string folderId)
{
RestRequest request = new RestRequest(
string.Format("{0}{1}/files?filter=folders&access_token={2}",
Settings.Default.APIUrl,
folderId,
accessToken), Method.GET);
return = request.ExecuteJson<SkyDriveFileList>();
}
GoogleDocs:
GoogleDocs isn’t quite as pretty because is uses atom, but here is the code to get and create a collection:
public SyndicationItem GetCollection(string collectionId)
{
RestRequest request = new RestRequest(string.Format("{0}/feeds/default/private/full/{1}", GoogleDocsAPIURL, HttpUtility.UrlEncode(collectionId)), Method.GET);
request.AddHeader("Gdata-Version", "3.0");
request.AddHeader("Authorization", string.Format("Bearer {0}", _accessToken));
return request.ExecuteSyndicationItem();
}
public SyndicationFeed GetCollection(string parentId, string name)
{
RestRequest request = new RestRequest(string.Format("{0}/feeds/default/private/full/{1}/{2}?title={3}", GoogleDocsAPIURL, HttpUtility.UrlEncode(parentId), parentId == "-" ? "folder" : "contents", HttpUtility.UrlEncode(name)), Method.GET);
request.AddHeader("Gdata-Version", "3.0");
request.AddHeader("Authorization", string.Format("Bearer {0}", _accessToken));
SyndicationFeed feed = request.ExecuteSyndicationFeed();
return feed.Items.FirstOrDefault();
}
public SyndicationFeed CreateCollection(string parentId, string name)
{
string uri = string.Format("{0}/feeds/default/private/full", GoogleDocsAPIURL);
if (parentId != null) uri = string.Format("{0}/{1}/contents", uri, parentId);
RestRequest request = new RestRequest(uri, Method.POST);
request.AddHeader("Gdata-Version", "3.0");
request.AddHeader("Authorization", string.Format("Bearer {0}", _accessToken));
request.SetContentType(ContentTypes.APPLICATION_ATOM_PLUS_XML);
request.AddBody(CreateCollectionAtom(name));
return request.ExecuteSyndicationItem();
}
private string CreateCollectionAtom(string title)
{
SyndicationItem item = new SyndicationItem() { Id = string.Empty };
item.Title = new TextSyndicationContent(title);
SyndicationCategory category = new SyndicationCategory() { Scheme = "http://schemas.google.com/g/2005#kind" };
category.AttributeExtensions.Add(new System.Xml.XmlQualifiedName("term"), "http://schemas.google.com/docs/2007#folder");
item.Categories.Add(category);
StringBuilder sb = new StringBuilder();
using (XmlWriter atomWriter = XmlWriter.Create(sb))
{
Atom10ItemFormatter atomFormatter = new Atom10ItemFormatter(item);
atomFormatter.WriteTo(atomWriter);
}
string atom = sb.ToString();
atom = atom.Remove(atom.IndexOf(""), 6);
return atom;
}
Uploading a file with GoogleDocs is a little more tricky because it uses a resumable link:
public string UploadImage(string collectionId, string fileName, byte[] bytes)
{
SyndicationItem item = GetCollection(collectionId);
string atom = CreateUploadAtom(fileName);
string resumableLink = item.Links.Where(p => p.RelationshipType.Contains("#resumable-create-media")).Single().Uri.ToString();
RestRequest request = new RestRequest(string.Format("{0}?convert=false", md.ResumableCreateMedia), Method.POST);
request.AddHeader("Gdata-Version", "3.0");
request.AddHeader("Authorization", string.Format("Bearer {0}", _accessToken));
request.SetContentLength(atom.Length);
request.SetContentType(ContentTypes.APPLICATION_ATOM_PLUS_XML);
request.AddHeader("X-Upload-Content-Type", ContentTypes.IMAGE_JPEG);
request.AddHeader("X-Upload-Content-Length", bytes.Length.ToString());
request.AddBody(atom);
string content = request.Execute();
string location = request.GetResponseHeader("Location");
RestRequest putRequest = new RestRequest(location, Method.PUT);
putRequest.SetContentLength(bytes.Length);
putRequest.SetContentType(ContentTypes.IMAGE_JPEG);
putRequest.AddBody(bytes);
SyndicationItem item = putRequest.ExecuteSyndicationItem();
return item.Id.Substring(item.Id.LastIndexOf("/") + 1);
}
private string CreateUploadAtom(string title)
{
SyndicationItem item = new SyndicationItem() { Id = string.Empty };
item.Title = new TextSyndicationContent(title);
item.AttributeExtensions.Add(new XmlQualifiedName("docs", XNamespace.Xmlns.ToString()), "http://schemas.google.com/docs/2007");
StringBuilder sb = new StringBuilder();
using (XmlWriter atomWriter = XmlWriter.Create(sb))
{
Atom10ItemFormatter atomFormatter = new Atom10ItemFormatter(item);
atomFormatter.WriteTo(atomWriter);
}
string atom = sb.ToString();
atom = atom.Remove(atom.IndexOf(""), 6);
return atom;
}
Obviously these are just a few examples of what you may need; however, hopefully it is enough to get you going. Actually, just in case you were interested, I’ll give you one more example! All of these APIs require authentication, typically OAuth V1 or V2. Here is the authentication code for connecting with SkyDrive:
First a class to represent the OAuth token:
public class OAuthV2Token
{
public string Access_Token { get; set; }
public string Refresh_Token { get; set; }
public string Authentication_Token { get; set; }
public int Expires_In { get; set; }
public string Scope { get; set; }
}
Here is a typical process for authorizing a client with OAuth V2:
- Send client to service for them to authorize your application.
- Service responds by redirecting back to your size with an authorization code
- Use authorization code to request a refresh token and access token
- Store refresh token to use for future authorization
Here is the code I used to accomplish this feat:
public OAuthToken GetOAuthTokenFromRefreshToken(string refreshToken, string callback)
{
string parameters = String.Format("client_id={0}&redirect_uri={1}&client_secret={2}&refresh_token={3}&grant_type=refresh_token",
HttpUtility.UrlEncode(Settings.Default.ClientId),
HttpUtility.UrlEncode(callback),
HttpUtility.UrlEncode(Settings.Default.ClientSecret),
HttpUtility.UrlEncode(refreshToken));
return GetAccessToken(parameters);
}
public OAuthToken GetOAuthTokenFromAuthCode(string authorizationCode, string callback)
{
string parameters = String.Format("client_id={0}&redirect_uri={1}&client_secret={2}&code={3}&grant_type=authorization_code",
HttpUtility.UrlEncode(Settings.Default.ClientId),
HttpUtility.UrlEncode(callback),
HttpUtility.UrlEncode(Settings.Default.ClientSecret),
HttpUtility.UrlEncode(authorizationCode));
return GetAccessToken(parameters);
}
public string GetAuthenticationURL(string callbackURL)
{
return string.Format("{0}{1}?client_id={2}&scope={3}&response_type=code&redirect_uri={4}",
Settings.Default.OAuthAPIUrl,
"authorize",
HttpUtility.UrlEncode(Settings.Default.ClientId),
HttpUtility.UrlEncode(Settings.Default.Scope),
HttpUtility.UrlEncode(callbackURL));
}
private static OAuthV2Token GetAccessToken(string parameters)
{
RestRequest request = new RestRequest(string.Format("{0}{1}?{2}", Settings.Default.OAuthAPIUrl, OAuthAction.token.ToString(), parameters), Method.GET);
return request.ExecuteJson<OAuthV2Token>();
}
Hopefully this has been helpful. As I mentioned, my Rest class is very simple and only served to fulfill my personal needs; although, I imagine this class will be easy to extend to fulfill your needs as well. Have fun!










