├── .gitignore ├── APIRequest.cs ├── APIResponse.cs ├── DynaJSON.cs ├── DynaXML.cs ├── Dynamic.cs ├── Extensions.cs ├── FriendlyDynamic.cs ├── HastyAPI.csproj ├── HastyAPI.nuspec ├── HastyAPI.sln ├── Properties └── AssemblyInfo.cs ├── README.md ├── Tests ├── Data │ ├── client_get_response.xml │ ├── dummy_lists.xml │ ├── list_of_primitives.js │ ├── simple.js │ ├── simple_list.js │ ├── simple_nested.js │ └── with_hyphens.js ├── DynaJSONFacts.cs ├── DynaXMLFacts.cs ├── Examples.cs ├── FriendlyDynamicFacts.cs ├── Misc.cs ├── Properties │ └── AssemblyInfo.cs ├── Shared.cs ├── Tests.csproj ├── URLFacts.cs └── packages.config ├── URL.cs ├── license └── packages.config /.gitignore: -------------------------------------------------------------------------------- 1 | *.suo 2 | *.user 3 | *.nupkg 4 | \bin 5 | \obj 6 | \packages 7 | \.vs -------------------------------------------------------------------------------- /APIRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Net; 6 | using System.Web; 7 | using System.Web.Script.Serialization; 8 | using System.Security.Cryptography.X509Certificates; 9 | using System.Net.Security; 10 | 11 | namespace HastyAPI { 12 | public class APIRequest { 13 | private string _url; 14 | private object _headers; 15 | private string _agent; 16 | private string _data; 17 | private NetworkCredential _credentials; 18 | private Encoding _encoding = Encoding.UTF8; 19 | private string _contentType; 20 | private CookieCollection _cookies; 21 | private bool _autoRedirect = true; 22 | 23 | public APIRequest(string url) { 24 | _url = url; 25 | } 26 | 27 | public APIRequest WithHeaders(object headers) { 28 | _headers = headers; 29 | return this; 30 | } 31 | 32 | public APIRequest WithForm(object vars) { 33 | string data = ""; 34 | var dic = vars.AsDictionary(); 35 | foreach(var pair in dic) { 36 | if(data.Length > 0) data += "&"; 37 | var value = pair.Value; 38 | if(value == null) value = ""; 39 | data += HttpUtility.UrlEncode(pair.Key) + "=" + HttpUtility.UrlEncode(value); 40 | } 41 | _data = data; 42 | _contentType = "application/x-www-form-urlencoded"; 43 | return this; 44 | } 45 | 46 | public APIRequest WithJSON(object json) { 47 | _data = new JavaScriptSerializer().Serialize(json); 48 | _contentType = "application/json"; 49 | return this; 50 | } 51 | 52 | public APIRequest WithData(string data, string contentType = null) { 53 | _data = data; 54 | _contentType = contentType; 55 | return this; 56 | } 57 | 58 | public APIRequest WithBasicCredentials(string username, string password) { 59 | _credentials = new NetworkCredential(username, password); 60 | return this; 61 | } 62 | 63 | public APIRequest WithEncoding(Encoding encoding) { 64 | _encoding = encoding; 65 | return this; 66 | } 67 | 68 | public APIRequest WithUserAgent(string agent) { 69 | _agent = agent; 70 | return this; 71 | } 72 | 73 | public APIRequest WithCookies(CookieCollection cookies) { 74 | if(_cookies == null) { 75 | _cookies = cookies; 76 | } else { 77 | _cookies.Add(cookies); 78 | } 79 | return this; 80 | } 81 | 82 | public APIRequest NoAutoRedirect() { 83 | _autoRedirect = false; 84 | return this; 85 | } 86 | 87 | public APIResponse Post() { 88 | return Send("POST"); 89 | } 90 | 91 | public APIResponse Get() { 92 | return Send("GET"); 93 | } 94 | 95 | public APIResponse Put() { 96 | return Send("PUT"); 97 | } 98 | 99 | public APIResponse Send(string method) { 100 | HttpWebRequest req = null; 101 | 102 | if(_data != null) { 103 | if(method.Equals("GET", StringComparison.OrdinalIgnoreCase)) { 104 | req = (HttpWebRequest)WebRequest.Create(_url + "?" + _data); 105 | 106 | SetCommon(req, method); 107 | // note: don't send content type for get 108 | 109 | } else { 110 | req = (HttpWebRequest)WebRequest.Create(_url); 111 | SetCommon(req, method); 112 | 113 | req.ContentType = _contentType; 114 | 115 | var dataBytes = _encoding.GetBytes(_data); 116 | req.ContentLength = dataBytes.Length; 117 | 118 | var reqStream = req.GetRequestStream(); 119 | reqStream.Write(dataBytes, 0, dataBytes.Length); 120 | reqStream.Close(); 121 | } 122 | } else { 123 | req = (HttpWebRequest)WebRequest.Create(_url); 124 | if(method.Equals("POST", StringComparison.OrdinalIgnoreCase)) { 125 | req.ContentLength = 0; 126 | } 127 | SetCommon(req, method); 128 | } 129 | 130 | HttpWebResponse response; 131 | try { 132 | response = (HttpWebResponse)req.GetResponse(); 133 | } catch(WebException e) { 134 | if(e.Status == WebExceptionStatus.ConnectFailure) { 135 | throw new Exception("Couldn't connect to " + req.RequestUri.GetLeftPart(UriPartial.Authority), e); 136 | } else if(e.Status == WebExceptionStatus.TrustFailure) { 137 | throw new Exception("Bad SSL certificate for " + req.RequestUri.GetLeftPart(UriPartial.Authority), e); 138 | } 139 | return e.Response.ToAPIResponse(); 140 | } 141 | 142 | return response.ToAPIResponse(); 143 | } 144 | 145 | void SetCommon(HttpWebRequest req, string method) { 146 | req.WithCredentials(_credentials) 147 | .WithHeaders(_headers).Method = method; 148 | 149 | req.AllowAutoRedirect = _autoRedirect; 150 | 151 | req.CookieContainer = new CookieContainer(); 152 | if(_cookies != null) { 153 | req.CookieContainer.Add(_cookies); 154 | } 155 | 156 | if(_agent != null) { 157 | req.WithUserAgent(_agent); 158 | } 159 | } 160 | 161 | static HashSet forceAcceptHosts = new HashSet(StringComparer.OrdinalIgnoreCase); 162 | /// 163 | /// Always accept SSL certificates for the specified host name. 164 | /// 165 | /// 166 | /// if(HttpContext.Current.IsDebuggingEnabled) APIRequest.ForceAcceptCertificate("localhost"); 167 | /// 168 | /// 169 | public static void ForceAcceptCertificate(string host) { 170 | lock(forceAcceptHosts) { 171 | if(!forceAcceptHosts.Contains(host)) forceAcceptHosts.Add(host); 172 | } 173 | ServicePointManager.ServerCertificateValidationCallback = AcceptForcedCertificateCallback; 174 | } 175 | 176 | private static bool AcceptForcedCertificateCallback(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors errors) { 177 | var req = sender as HttpWebRequest; 178 | if(req == null) return errors == SslPolicyErrors.None; 179 | 180 | lock(forceAcceptHosts) { 181 | return forceAcceptHosts.Contains(req.RequestUri.Host); 182 | } 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /APIResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Net; 6 | using System.Xml.Linq; 7 | 8 | namespace HastyAPI { 9 | public class APIResponse { 10 | public WebHeaderCollection Headers { get; private set; } 11 | public int StatusCode { get; private set; } 12 | public string ContentType { get; private set; } 13 | public string Text { get; private set; } 14 | public CookieCollection Cookies { get; private set; } 15 | 16 | public APIResponse(WebHeaderCollection headers, int statusCode, string contentType, string text, CookieCollection cookies) { 17 | Headers = headers; 18 | StatusCode = statusCode; 19 | Text = text; 20 | Cookies = cookies; 21 | } 22 | 23 | public APIResponse EnsureStatus(int status) { 24 | if(this.StatusCode != status) { 25 | throw new Exception("Status code was " + StatusCode + ", expected " + status + "." + (Text != null ? " Server responded:\n" + Text : null)); 26 | } 27 | return this; 28 | } 29 | 30 | public dynamic AsJSON() { 31 | return DynaJSON.Parse(Text); 32 | } 33 | 34 | public dynamic AsXML() { 35 | return DynaXML.Parse(Text); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /DynaJSON.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Web.Script.Serialization; 6 | using System.Dynamic; 7 | using Newtonsoft.Json.Linq; 8 | 9 | namespace HastyAPI { 10 | public class DynaJSON { 11 | public static dynamic Parse(string text) { 12 | var js = Newtonsoft.Json.JsonConvert.DeserializeObject(text); 13 | return GetValue(js) ?? text; 14 | } 15 | 16 | private static dynamic GetObject(JObject jobj) { 17 | var obj = new FriendlyDynamic() as IDictionary; 18 | foreach(var pair in jobj) { 19 | obj.Add(pair.Key.Replace('-', '_'), GetValue(pair.Value)); 20 | } 21 | return obj; 22 | } 23 | 24 | private static object GetValue(object val) { 25 | if(val is JArray) { 26 | return GetList(val as JArray); 27 | } else if(val is JObject) { 28 | return GetObject(val as JObject); 29 | } else if(val is JValue) { 30 | return ((JValue)val).Value; 31 | } 32 | 33 | return val; // primitive 34 | } 35 | 36 | private static object GetToken(JToken token) { 37 | return token.GetType(); 38 | } 39 | 40 | private static dynamic GetList(JArray ary) { 41 | var list = new List(ary.Count); 42 | foreach(var e in ary) { 43 | list.Add(GetValue(e)); 44 | } 45 | return list; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /DynaXML.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Xml.Linq; 6 | using System.Dynamic; 7 | 8 | namespace HastyAPI { 9 | public class DynaXML { 10 | 11 | public static dynamic Parse(string text) { 12 | return Parse(XDocument.Parse(text)); 13 | } 14 | 15 | public static dynamic Parse(XDocument xml) { 16 | var result = new FriendlyDynamic() as IDictionary; 17 | 18 | var root = xml.Root; 19 | AddElement(root, result); 20 | 21 | return result; 22 | } 23 | 24 | private static void AddElement(XElement el, IDictionary parent) { 25 | var name = el.Name.LocalName; 26 | 27 | // check for multiple elements with the same name and convert to list 28 | var addToList = parent.ContainsKey(name); 29 | IList list = null; 30 | if(addToList) { 31 | var container = parent[name]; 32 | list = container as IList; 33 | if(list == null) { 34 | // convert to list 35 | list = new List(new dynamic[] { container }); 36 | parent[name] = list; 37 | } 38 | } 39 | 40 | if(el.HasElements || el.HasAttributes) { // complex object 41 | var obj = new FriendlyDynamic() as IDictionary; 42 | 43 | if(addToList) { 44 | list.Add(obj); 45 | } else { 46 | parent[name] = obj; 47 | } 48 | 49 | foreach(var attr in el.Attributes()) { 50 | obj[attr.Name.LocalName] = attr.Value; 51 | } 52 | 53 | foreach(var child in el.Elements()) { 54 | AddElement(child, obj); 55 | } 56 | 57 | if(!el.HasElements && !el.IsEmpty) { 58 | var valName = Char.IsUpper(name[0]) ? "Text" : "text"; // mimic case 59 | obj[valName] = el.Value; 60 | } 61 | } else { // simple value 62 | if(addToList) { 63 | list.Add(el.Value); 64 | } else { 65 | parent[name] = el.Value; 66 | } 67 | } 68 | 69 | } 70 | } 71 | 72 | } 73 | 74 | namespace System.Xml.Linq { 75 | 76 | public static class DynaXMLExtensions { 77 | public static dynamic ToDynamic(this XDocument doc) { 78 | return HastyAPI.DynaXML.Parse(doc); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Dynamic.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace HastyAPI { 7 | public static class Dynamic { 8 | public static List AsList(object obj) { 9 | var list = obj as List; 10 | if(list != null) return list; 11 | 12 | return new List(new dynamic[] { obj }); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Net; 6 | using System.IO; 7 | 8 | namespace HastyAPI { 9 | public static class Extensions { 10 | public static APIResponse ToAPIResponse(this WebResponse wr) { 11 | var hwr = wr as HttpWebResponse; 12 | if(hwr == null) throw new Exception("Not an HttpWebResponse"); 13 | 14 | var sr = new StreamReader(hwr.GetResponseStream()); 15 | var responseText = sr.ReadToEnd(); 16 | sr.Close(); 17 | 18 | return new APIResponse(hwr.Headers, (int)hwr.StatusCode, 19 | hwr.ContentType, responseText, hwr.Cookies); 20 | } 21 | 22 | public static HttpWebRequest WithCredentials(this HttpWebRequest request, NetworkCredential credentials) { 23 | if(credentials != null) { 24 | request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.Default.GetBytes(credentials.UserName + ":" + credentials.Password)); 25 | } 26 | return request; 27 | } 28 | 29 | public static HttpWebRequest WithUserAgent(this HttpWebRequest request, string agent) { 30 | request.UserAgent = agent; 31 | return request; 32 | } 33 | 34 | public static HttpWebRequest WithHeaders(this HttpWebRequest request, object headers) { 35 | if(headers != null) { 36 | foreach(var pair in headers.AsDictionary()) { 37 | request.Headers.Add(pair.Key, pair.Value); 38 | } 39 | } 40 | return request; 41 | } 42 | 43 | public static IDictionary AsDictionary(this object obj) { 44 | IDictionary vardic; 45 | if(obj is IDictionary) { 46 | vardic = (IDictionary)obj; 47 | } else { 48 | vardic = new Dictionary(); 49 | foreach(var prop in obj.GetType().GetProperties()) { 50 | var value = prop.GetValue(obj, null); 51 | vardic.Add(prop.Name, value?.ToString()); 52 | } 53 | } 54 | return vardic; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /FriendlyDynamic.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Dynamic; 6 | 7 | namespace HastyAPI { 8 | 9 | /// 10 | /// A javascript-like dynamic object that returns null for undefined properties 11 | /// 12 | public class FriendlyDynamic : DynamicObject, IDictionary { 13 | Dictionary _dictionary = new Dictionary(); 14 | 15 | public override bool TrySetMember(SetMemberBinder binder, object value) { 16 | _dictionary[binder.Name] = value; 17 | return true; 18 | } 19 | 20 | public override bool TryGetMember(GetMemberBinder binder, out object result) { 21 | if(!_dictionary.TryGetValue(binder.Name, out result)) 22 | result = null; 23 | return true; 24 | } 25 | 26 | public override string ToString() { 27 | return DynamicToString(this, 0, false); 28 | } 29 | 30 | private static string DynamicToString(FriendlyDynamic dyn, int level, bool insideList) { 31 | var canCompact = (level < 2 || insideList) && CanCompact(dyn); 32 | 33 | var str = "{"; 34 | str += canCompact ? " " : "\r\n"; 35 | 36 | var i = 0; 37 | foreach(var pair in dyn._dictionary) { 38 | str += PairToString(pair, level + 1, canCompact); 39 | if(i < dyn._dictionary.Count - 1) str += "," + (canCompact ? " " : ""); 40 | str += canCompact ? "" : "\r\n"; 41 | i++; 42 | } 43 | 44 | str += (canCompact ? " " : Indent(level)) + "}"; 45 | return str; 46 | } 47 | 48 | private static string PairToString(KeyValuePair pair, int level, bool canCompact) { 49 | var str = canCompact ? "" : Indent(level); 50 | str += "\"" + pair.Key + "\": " + ObjectToString(pair.Value, level, false); 51 | return str; 52 | } 53 | 54 | private static string ObjectToString(object obj, int level, bool insideList) { 55 | if(obj is IList) { 56 | return ListToString(obj as IList, level); 57 | } else if(obj is FriendlyDynamic) { 58 | return DynamicToString(obj as FriendlyDynamic, level, insideList); 59 | } else if(obj is string) { 60 | return "\"" + obj + "\""; 61 | } else if(obj is bool) { 62 | return obj.ToString().ToLower(); 63 | } else return obj.ToString(); 64 | } 65 | 66 | private static string ListToString(IList list, int level) { 67 | var canCompact = CanCompact(list); 68 | 69 | var str = "["; 70 | str += canCompact ? "" : "\r\n"; 71 | 72 | var i = 0; 73 | foreach(var obj in list) { 74 | str += (canCompact ? (i == 0 ? "" : " ") : Indent(level + 1)) + ObjectToString(obj, level + 1, true); 75 | if(i < list.Count - 1) str += ","; 76 | str += canCompact ? "" : "\r\n"; 77 | i++; 78 | } 79 | str += (canCompact ? "" : Indent(level)) + "]"; 80 | return str; 81 | } 82 | 83 | private static bool CanCompact(FriendlyDynamic dyn) { 84 | if(dyn._dictionary.Count > 5) return false; 85 | foreach(var pair in dyn._dictionary) { 86 | if(!CanCompact(pair.Value)) return false; 87 | } 88 | return true; 89 | } 90 | 91 | private static bool CanCompact(object obj) { 92 | if(obj is List) return false; 93 | if(obj is FriendlyDynamic) return false; 94 | return true; 95 | } 96 | 97 | private static bool CanCompact(IList list) { 98 | foreach(var obj in list) { 99 | if(!CanCompact(obj)) return false; 100 | } 101 | return true; 102 | } 103 | 104 | private static string Indent(int level) { 105 | return new string(' ', level * 4); 106 | } 107 | 108 | #region IDictionary implementation 109 | void IDictionary.Add(string key, object value) { 110 | _dictionary.Add(key, value); 111 | } 112 | 113 | bool IDictionary.ContainsKey(string key) { 114 | return _dictionary.ContainsKey(key); 115 | } 116 | 117 | ICollection IDictionary.Keys { 118 | get { return _dictionary.Keys; } 119 | } 120 | 121 | bool IDictionary.Remove(string key) { 122 | return _dictionary.Remove(key); 123 | } 124 | 125 | bool IDictionary.TryGetValue(string key, out object value) { 126 | return _dictionary.TryGetValue(key, out value); 127 | } 128 | 129 | ICollection IDictionary.Values { 130 | get { return _dictionary.Values; } 131 | } 132 | 133 | object IDictionary.this[string key] { 134 | get { 135 | return _dictionary[key]; 136 | } 137 | set { 138 | _dictionary[key] = value; 139 | } 140 | } 141 | 142 | void ICollection>.Add(KeyValuePair item) { 143 | ((ICollection>)_dictionary).Add(item); 144 | } 145 | 146 | void ICollection>.Clear() { 147 | ((ICollection>)_dictionary).Clear(); 148 | } 149 | 150 | bool ICollection>.Contains(KeyValuePair item) { 151 | return ((ICollection>)_dictionary).Contains(item); 152 | } 153 | 154 | void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { 155 | ((ICollection>)_dictionary).CopyTo(array, arrayIndex); 156 | } 157 | 158 | int ICollection>.Count { 159 | get { 160 | return ((ICollection>)_dictionary).Count; 161 | } 162 | } 163 | 164 | bool ICollection>.IsReadOnly { 165 | get { 166 | return ((ICollection>)_dictionary).IsReadOnly; 167 | } 168 | } 169 | 170 | bool ICollection>.Remove(KeyValuePair item) { 171 | return ((ICollection>)_dictionary).Remove(item); 172 | } 173 | 174 | IEnumerator> IEnumerable>.GetEnumerator() { 175 | return ((ICollection>)_dictionary).GetEnumerator(); 176 | } 177 | 178 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { 179 | return ((System.Collections.IEnumerable)_dictionary).GetEnumerator(); 180 | } 181 | #endregion 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /HastyAPI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {AC6ED39A-07BB-4192-89A0-30B07E0F7C2F} 9 | Library 10 | Properties 11 | HastyAPI 12 | HastyAPI 13 | v4.0 14 | 512 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | packages\Newtonsoft.Json.5.0.6\lib\net40\Newtonsoft.Json.dll 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | Designer 61 | 62 | 63 | 64 | 65 | 66 | 73 | -------------------------------------------------------------------------------- /HastyAPI.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | Luke Sampson 8 | Luke Sampson 9 | https://github.com/lukesampson/HastyAPI/blob/master/license 10 | http://github.com/lukesampson/HastyAPI 11 | false 12 | HastyAPI makes it easy to write code to interact with web APIs. 13 | HastyAPI makes it easy to write code to interact with web APIs. 14 | 15 | It'll save you writing lots of tedious boilerplate code to make HTTP web requests, and process XML and JSON. By providing high-level, dynamic functions, it lets you concentrate on the web API itself. 16 | Initial release. 17 | Copyright Luke Sampson 2012 18 | web api 19 | 20 | -------------------------------------------------------------------------------- /HastyAPI.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HastyAPI", "HastyAPI.csproj", "{AC6ED39A-07BB-4192-89A0-30B07E0F7C2F}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{A62093AE-981B-4DA7-B29F-DA4B633EBFD1}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {AC6ED39A-07BB-4192-89A0-30B07E0F7C2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {AC6ED39A-07BB-4192-89A0-30B07E0F7C2F}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {AC6ED39A-07BB-4192-89A0-30B07E0F7C2F}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {AC6ED39A-07BB-4192-89A0-30B07E0F7C2F}.Release|Any CPU.Build.0 = Release|Any CPU 18 | {A62093AE-981B-4DA7-B29F-DA4B633EBFD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {A62093AE-981B-4DA7-B29F-DA4B633EBFD1}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {A62093AE-981B-4DA7-B29F-DA4B633EBFD1}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {A62093AE-981B-4DA7-B29F-DA4B633EBFD1}.Release|Any CPU.Build.0 = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(SolutionProperties) = preSolution 24 | HideSolutionNode = FALSE 25 | EndGlobalSection 26 | EndGlobal 27 | -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("HastyAPI")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("HastyAPI")] 13 | [assembly: AssemblyCopyright("Copyright © 2012")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("9090dc7c-d62c-41d3-9296-7ef2e1610a2f")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.16")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | HastyAPI 2 | ======== 3 | 4 | HastyAPI makes it easy to write code to interact with web APIs. 5 | 6 | It'll save you writing lots of tedious boilerplate code to make HTTP web requests, and process XML and JSON. By providing high-level, dynamic functions, it lets you concentrate on the web API itself. 7 | 8 | Examples 9 | -------- 10 | 11 | __Getting a URL__ 12 | 13 | var text = new APIRequest("http://www.google.com").Get().Text; 14 | 15 | __Posting data to a URL__ 16 | 17 | var result = new APIRequest("https://www.googleapis.com/urlshortener/v1/url") 18 | .WithData(@"{ ""longUrl"":""http://www.google.com/"" }", "application/json") 19 | .Post() 20 | .Text; 21 | 22 | or, you could use the `WithJSON` shortcut to do the same thing: 23 | 24 | var result = new APIRequest("https://www.googleapis.com/urlshortener/v1/url") 25 | .WithJSON(new { longUrl = "http://www.google.com/" }) 26 | .Post() 27 | .Text; 28 | 29 | value of `result`: 30 | 31 | { 32 | "kind": "urlshortener#url", 33 | "id": "http://goo.gl/fbsS", 34 | "longUrl": "http://www.google.com/" 35 | } 36 | 37 | __Using JSON data returned from the API___ 38 | 39 | var clicks = new APIRequest("https://www.googleapis.com/urlshortener/v1/url") 40 | .WithForm(new { shortUrl = "http://goo.gl/fbsS", projection = "FULL" }) 41 | .Get() 42 | .AsJSON().analytics.allTime.shortUrlClicks; // <-- dynamic! 43 | 44 | // note: WithForm automatically issues querystring variables for GET requests 45 | // you could also do: 46 | // new APIRequest("https://www.googleapis.com/urlshortener/v1/url?shortUrl=http://goo.gl.fbsS&projection=FULL") -------------------------------------------------------------------------------- /Tests/Data/client_get_response.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 12 4 | Jane 5 | Austin 6 | jane.austin@cdu.edu.au 7 | janeaustin 8 | 9 | 10 | 11 | Charles Darwin University 12 | 08 8946 5555 13 | 14 | ABN 15 | 16 | Student Administration 17 | Charles Darwin University 18 | Darwin 19 | Northern Territory 20 | Australia 21 | 0909 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | en 30 | AUD 31 | 0 32 | https://sample.freshbooks.com/view/37Meha2NXCFhrVtc 33 | https://sample.freshbooks.com/clients/12 34 | active 35 | 2012-03-12 02:41:16 36 | 37 | 0 38 | 120.25 39 | 40 | 41 | https://sample.freshbooks.com/view/37Meha2NXCFhrVtc 42 | https://sample.freshbooks.com/clients/12 43 | https://sample.freshbooks.com/view/3z7ubiM2KSWUq2Wa 44 | 45 | 46 | 1 47 | 48 | 49 | -------------------------------------------------------------------------------- /Tests/Data/dummy_lists.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | one 4 | two 5 | 6 | 7 | 8 | one 9 | 10 | 11 | two 12 | 13 | 14 | three 15 | 16 | 17 | -------------------------------------------------------------------------------- /Tests/Data/list_of_primitives.js: -------------------------------------------------------------------------------- 1 | { 2 | "primes": [2, 3, 5, 7, 11, 13] 3 | } -------------------------------------------------------------------------------- /Tests/Data/simple.js: -------------------------------------------------------------------------------- 1 | { "simple": "one", "number": 42 } -------------------------------------------------------------------------------- /Tests/Data/simple_list.js: -------------------------------------------------------------------------------- 1 | { 2 | "list": [ 3 | { "item": "one" }, 4 | { "item": "two" } 5 | ] 6 | } -------------------------------------------------------------------------------- /Tests/Data/simple_nested.js: -------------------------------------------------------------------------------- 1 | { 2 | "base": { 3 | "example": "one", 4 | "sub": { 5 | "this_one_nested": true 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /Tests/Data/with_hyphens.js: -------------------------------------------------------------------------------- 1 | {"message-count":"1","messages":[{"to":"61415555555","message-price":"0.05000000","status":"0","message-id":"08EF786C","remaining-balance":"1.95000000"}]} -------------------------------------------------------------------------------- /Tests/DynaJSONFacts.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.IO; 6 | using HastyAPI; 7 | using System.Collections; 8 | using Microsoft.VisualStudio.TestTools.UnitTesting; 9 | 10 | namespace Tests { 11 | [TestClass] 12 | public class DynaJSONFacts { 13 | 14 | [TestMethod] 15 | public void Parse_Returns_Not_Null() { 16 | var json = DynaJSON.Parse("{ test: 1 }"); 17 | Microsoft.VisualStudio.TestTools.UnitTesting.Assert.IsNotNull(json); 18 | } 19 | 20 | [TestMethod] 21 | public void Simple_Number_Correct() { 22 | var json = DynaJSON.Parse("{ test: 1 }"); 23 | 24 | Assert.AreEqual(1, json.test); 25 | } 26 | 27 | [TestMethod] 28 | public void Simple_Multiple_Values_Correct() { 29 | var json = Shared.GetJSON("simple"); 30 | 31 | Assert.AreEqual("one", json.simple); 32 | Assert.AreEqual(42, json.number); 33 | } 34 | 35 | [TestMethod] 36 | public void Simple_List_Correct() { 37 | var json = Shared.GetJSON("simple_list"); 38 | 39 | Assert.IsInstanceOfType(json.list, typeof(IList)); 40 | //Assert.IsAssignableFrom(json.list); 41 | 42 | Assert.AreEqual(2, json.list.Count); 43 | } 44 | 45 | [TestMethod] 46 | public void Simple_Nested_Correct() { 47 | var json = Shared.GetJSON("simple_nested"); 48 | 49 | Assert.AreEqual("one", json.@base.example); 50 | Assert.IsTrue(json.@base.sub.this_one_nested); 51 | } 52 | 53 | [TestMethod] 54 | public void Hypens_In_Names_Converted_To_Underscores() { 55 | var json = Shared.GetJSON("with_hyphens"); 56 | 57 | Assert.AreEqual("1", json.message_count); 58 | } 59 | 60 | [TestMethod] 61 | public void Array_Of_Objects() { 62 | var json = DynaJSON.Parse("[ { item: 1 }, { item: 2 } ]"); 63 | 64 | Assert.AreEqual(2, json.Count); 65 | Assert.AreEqual(1, json[0].item); 66 | Assert.AreEqual(2, json[1].item); 67 | } 68 | 69 | [TestMethod] 70 | public void Empty_String() { 71 | var json = DynaJSON.Parse(""); 72 | 73 | Assert.AreEqual("", json); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Tests/DynaXMLFacts.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.IO; 6 | using System.Xml.Linq; 7 | using HastyAPI; 8 | using Microsoft.VisualStudio.TestTools.UnitTesting; 9 | 10 | namespace Tests { 11 | [TestClass] 12 | public class DynaXMLFacts { 13 | 14 | public static XDocument GetXML(string name) { 15 | var path = Path.Combine(Directory.GetCurrentDirectory(), @"..\..\Data\" + name + ".xml"); 16 | return XDocument.Load(path); 17 | } 18 | 19 | [TestMethod] 20 | public void Can_Get_Root() { 21 | var o = GetXML("client_get_response").ToDynamic(); 22 | Assert.IsNotNull(o.response); 23 | } 24 | 25 | [TestMethod] 26 | public void Can_Get_One_Child() { 27 | var o = GetXML("client_get_response").ToDynamic(); 28 | Assert.IsNotNull(o.response.client); 29 | } 30 | 31 | [TestMethod] 32 | public void Can_Get_Content_Value() { 33 | var o = GetXML("client_get_response").ToDynamic(); 34 | Assert.AreEqual("12", o.response.client.client_id); 35 | } 36 | 37 | [TestMethod] 38 | public void Can_Get_Attribute_Value_As_Well_As_Content_Value() { 39 | var o = GetXML("client_get_response").ToDynamic(); 40 | 41 | Assert.AreEqual("AUD", o.response.client.credit.currency); 42 | Assert.AreEqual("true", o.response.client.credit.deprecated); 43 | Assert.AreEqual("0", o.response.client.credit.text); 44 | } 45 | 46 | [TestMethod] 47 | public void Simple_Element_Lists_Work() { 48 | var o = GetXML("dummy_lists").ToDynamic(); 49 | 50 | Assert.AreEqual(2, o.lists.simple.item.Count); 51 | Assert.IsNull(o.lists.simple.text); 52 | Assert.AreEqual("one", o.lists.simple.item[0]); 53 | Assert.AreEqual("two", o.lists.simple.item[1]); 54 | } 55 | 56 | [TestMethod] 57 | public void Complex_Element_Lists_Work() { 58 | var o = GetXML("dummy_lists").ToDynamic(); 59 | 60 | Assert.AreEqual(3, o.lists.complex.item.Count); 61 | Assert.AreEqual("one", o.lists.complex.item[0].test); 62 | Assert.AreEqual("complex", o.lists.complex.item[1].type); 63 | Assert.AreEqual("very_complex", o.lists.complex.item[2].type); 64 | } 65 | 66 | [TestMethod] 67 | public void Can_Call_As_List_On_List() { 68 | var o = GetXML("dummy_lists").ToDynamic(); 69 | 70 | var list = Dynamic.AsList(o.lists.simple.item); 71 | Assert.AreEqual(2, list.Count); 72 | } 73 | 74 | [TestMethod] 75 | public void Can_Call_As_List_On_A_Non_List() { 76 | var o = GetXML("dummy_lists").ToDynamic(); 77 | 78 | var list = Dynamic.AsList(o.lists.simple); 79 | Assert.AreEqual(1, list.Count); 80 | } 81 | 82 | public class Serialization { 83 | 84 | public void Test_Dummy() { 85 | var o = GetXML("dummy_lists").ToDynamic(); 86 | Console.WriteLine(o.ToString()); 87 | } 88 | 89 | public void Test_Real() { 90 | var o = GetXML("client_get_response").ToDynamic(); 91 | 92 | Console.WriteLine(o.response.client.credit.ToString()); 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Tests/Examples.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using HastyAPI; 6 | 7 | namespace Tests { 8 | public class Examples { 9 | 10 | public void Get_A_URL() { 11 | var text = new APIRequest("https://www.googleapis.com/urlshortener/v1/url?shortUrl=http://goo.gl/fbsS").Get().Text; 12 | Console.WriteLine(text); 13 | } 14 | 15 | public void Post_Data() { 16 | var result = new APIRequest("https://www.googleapis.com/urlshortener/v1/url") 17 | .WithData(@"{ ""longUrl"":""http://www.google.com/"" }", "application/json") 18 | .Post() 19 | .Text; 20 | 21 | Console.WriteLine(result); 22 | } 23 | 24 | public void Post_JSON_Data() { 25 | var result = new APIRequest("https://www.googleapis.com/urlshortener/v1/url") 26 | .WithJSON(new { longUrl = "http://www.google.com/" }) 27 | .Post() 28 | .Text; 29 | 30 | Console.WriteLine(result); 31 | } 32 | 33 | public void Working_With_JSON_Data() { 34 | var clicks = new APIRequest("https://www.googleapis.com/urlshortener/v1/url") 35 | .WithForm(new { shortUrl = "http://goo.gl/fbsS", projection = "FULL" }) 36 | .Get() 37 | .AsJSON().analytics.allTime.shortUrlClicks; // <-- dynamic! 38 | 39 | // note: WithFormData data are automatically issued as querystring variables for GET requests 40 | // You could also do: 41 | // new APIRequest("https://www.googleapis.com/urlshortener/v1/url?shortUrl=http://goo.gl.fbsS&projection=FULL") 42 | 43 | Console.WriteLine(clicks); 44 | } 45 | 46 | public void With_User_Agent() { 47 | var req = new APIRequest($"https://en.wikipedia.org/w/index.php?action=raw&title=pizza") 48 | .WithUserAgent("HastyAPI/1.0 Example"); 49 | 50 | var res = req.Get(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Tests/FriendlyDynamicFacts.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using HastyAPI; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | namespace Tests { 9 | [TestClass] 10 | public class FriendlyDynamicFacts { 11 | 12 | [TestMethod] 13 | public void UndefinedPropertyReturnsNull() { 14 | dynamic obj = new FriendlyDynamic(); 15 | 16 | obj.one = "One"; 17 | 18 | Assert.AreEqual("One", obj.one); 19 | Assert.IsNull(obj.two); 20 | } 21 | 22 | [TestClass] 23 | public class Serialization { 24 | 25 | 26 | [TestMethod] 27 | public void Simple_ToString_Matches_JSON() { 28 | var json = Shared.GetJSON("simple"); 29 | var text = Shared.GetText("simple.js"); 30 | 31 | Assert.AreEqual(text, json.ToString()); 32 | } 33 | 34 | [TestMethod] 35 | public void List_Of_Primitives_Matches_JSON() { 36 | var json = Shared.GetJSON("list_of_primitives"); 37 | var text = Shared.GetText("list_of_primitives.js"); 38 | 39 | Assert.AreEqual(text, json.ToString()); 40 | } 41 | 42 | [TestMethod] 43 | public void Simple_List_Matches_JSON() { 44 | var json = Shared.GetJSON("simple_list"); 45 | var text = Shared.GetText("simple_list.js"); 46 | 47 | Assert.AreEqual(text, json.ToString()); 48 | } 49 | 50 | [TestMethod] 51 | public void Simple_Nested_Matches_JSON() { 52 | var json = Shared.GetJSON("simple_nested"); 53 | var text = Shared.GetText("simple_nested.js"); 54 | 55 | Assert.AreEqual(text, json.ToString()); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Tests/Misc.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using HastyAPI; 6 | 7 | namespace Tests { 8 | 9 | public class Misc { 10 | 11 | public void Test_Headers() { 12 | new APIRequest("http://google.com").Get(); 13 | } 14 | 15 | public void Test_Certificate() { 16 | APIRequest.ForceAcceptCertificate("control.windows"); 17 | 18 | new APIRequest("https://control.windows:44301/") 19 | .Get(); 20 | 21 | new APIRequest("https://localhost:44301/") 22 | .Get(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2012")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("78366d16-038b-438f-b1d1-ae78492e2d6a")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Tests/Shared.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.IO; 6 | using HastyAPI; 7 | 8 | namespace Tests { 9 | public class Shared { 10 | public static string GetText(string filename) { 11 | var path = Path.Combine(Directory.GetCurrentDirectory(), @"..\..\Data\" + filename); 12 | return File.ReadAllText(path); 13 | } 14 | 15 | public static dynamic GetJSON(string name) { 16 | return DynaJSON.Parse(GetText(name + ".js")); 17 | } 18 | 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Tests/Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {A62093AE-981B-4DA7-B29F-DA4B633EBFD1} 9 | Library 10 | Properties 11 | Tests 12 | Tests 13 | v4.0 14 | 512 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | {AC6ED39A-07BB-4192-89A0-30B07E0F7C2F} 57 | HastyAPI 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 82 | -------------------------------------------------------------------------------- /Tests/URLFacts.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using HastyAPI; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | namespace Tests { 9 | [TestClass] 10 | public class URLFacts { 11 | 12 | [TestMethod] 13 | public void Add_Operator_Works_On_Non_Root_Base_Paths() { 14 | URL basePath = "https://www.test.com/subdir/"; 15 | Assert.AreEqual("https://www.test.com/subdir/rel", basePath + "/rel"); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /URL.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.ComponentModel; 6 | 7 | namespace HastyAPI { 8 | /// 9 | /// A more practical version of Uri. 10 | /// 11 | [TypeConverter(typeof(URLConverter))] 12 | public class URL { 13 | string _value; 14 | 15 | public URL(string value) { 16 | _value = value; 17 | } 18 | 19 | // convert from URL to string 20 | public static implicit operator string(URL value) { 21 | return value._value; 22 | } 23 | 24 | // convert from string to URL 25 | public static implicit operator URL(String value) { 26 | if(!Uri.IsWellFormedUriString(value, UriKind.Absolute)) { 27 | throw new Exception("That doesn't look like a URL"); 28 | } 29 | return new URL(value); 30 | } 31 | 32 | public static string operator +(URL basePath, string relPath) { 33 | return basePath._value.TrimEnd('/') + '/' + relPath.TrimStart('/'); 34 | } 35 | 36 | public class URLConverter : TypeConverter { 37 | public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) { 38 | if(value is string) return new URL((string)value); 39 | 40 | return base.ConvertFrom(context, culture, value); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2012 Luke Sampson 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is furnished 9 | to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. -------------------------------------------------------------------------------- /packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | --------------------------------------------------------------------------------