Permalink
Browse files

Merge pull request #1 from MrBretticus/master

Initial enhancements by MrBretticus

Notes:
* I have removed a few tests that I felt were redundant.
* XML resource is not quite up-to-scratch with JSON resource as I don't use XML but I will get it up to scratch soon. JSON resource allows array indexing, looking up by ID etc.
* I decided against handling Silverlight with Portable Library Tools because of VS requirement.
  • Loading branch information...
2 parents 6cda711 + 7beb585 commit 54caa07b41cb65b7fa94049e1b84bf8f68c5f812 @mauricioaniche mauricioaniche committed Jan 2, 2012
Showing with 20,979 additions and 609 deletions.
  1. +23 −21 .gitignore
  2. +36 −0 RestfulieClient.sln
  3. +20 −1 RestfulieClient/RestfulieClient.csproj
  4. +69 −0 RestfulieClient/features/AutoRefresh.cs
  5. +33 −0 RestfulieClient/features/FollowRedirects.cs
  6. +11 −0 RestfulieClient/features/IRequestFeature.cs
  7. +10 −0 RestfulieClient/features/IResponseFeature.cs
  8. +23 −0 RestfulieClient/features/RetryWhenUnavailable.cs
  9. +90 −0 RestfulieClient/http/DefaultRequestDispatcher.cs
  10. +52 −0 RestfulieClient/request/AsynchHelper.cs
  11. +42 −0 RestfulieClient/request/AsynchRequest.cs
  12. +18 −0 RestfulieClient/request/IRequestDispatcher.cs
  13. +33 −0 RestfulieClient/request/Request.cs
  14. +20 −0 RestfulieClient/request/RequestChain.cs
  15. +37 −0 RestfulieClient/request/RequestStack.cs
  16. +29 −0 RestfulieClient/request/ResponseChain.cs
  17. +245 −0 RestfulieClient/resources/DynamicJsonResource.cs
  18. +61 −34 RestfulieClient/resources/DynamicXmlResource.cs
  19. +32 −0 RestfulieClient/resources/EmptyResource.cs
  20. +128 −96 RestfulieClient/resources/EntryPointService.cs
  21. +44 −0 RestfulieClient/resources/IResource.cs
  22. +45 −0 RestfulieClient/resources/Restfulie.cs
  23. +0 −36 RestfulieClient/resources/Restifulie.cs
  24. +11 −7 RestfulieClient/service/HttpRemoteResponse.cs
  25. +7 −3 RestfulieClient/service/HttpRemoteResponseFactory.cs
  26. +72 −6 RestfulieClient/service/IRemoteResourceService.cs
  27. +3 −2 RestfulieClient/service/RestfulieHttpVerbDiscovery.cs
  28. +87 −0 RestfulieClientTest/DynamicJsonResourceTest.cs
  29. +12 −94 RestfulieClientTest/DynamicXmlResourceTest.cs
  30. +158 −0 RestfulieClientTest/EntryPointServiceTest.cs
  31. +8 −66 RestfulieClientTest/EntryPointTests.cs
  32. +0 −92 RestfulieClientTest/ResourceServiceTest.cs
  33. +17 −16 RestfulieClientTest/RestfulieClientTests.csproj
  34. +12 −56 RestfulieClientTest/RestfulieHttpVerbDiscoveryTest.cs
  35. +2 −54 RestfulieClientTest/StringValueConverterTest.cs
  36. +30 −0 RestfulieClientTest/features/AutoRefreshTests.cs
  37. +30 −0 RestfulieClientTest/features/FollowRedirectsTests.cs
  38. +57 −0 RestfulieClientTest/helpers/EmbeddedFileResourceDispatcher.cs
  39. +15 −20 RestfulieClientTest/helpers/LoadDocument.cs
  40. +0 −5 RestfulieClientTest/helpers/RemoteResourceFactory.cs
  41. +104 −0 RestfulieClientTest/helpers/TestHelper.cs
  42. +12 −0 RestfulieClientTest/jsons/city.json
  43. +13 −0 RestfulieClientTest/jsons/order.json
  44. +11 −0 RestfulieClientTest/jsons/orderWithoutLinks.json
  45. +12 −0 RestfulieClientTest/jsons/otherCity.json
  46. +35 −0 RestfulieSilverlightClient/Properties/AssemblyInfo.cs
  47. +153 −0 RestfulieSilverlightClient/RestfulieSilverlightClient.csproj
  48. BIN RestfulieSilverlightClientTest.Web/ClientBin/RestfulieSilverlightClientTest.xap
  49. +35 −0 RestfulieSilverlightClientTest.Web/Properties/AssemblyInfo.cs
  50. +100 −0 RestfulieSilverlightClientTest.Web/RestfulieSilverlightClientTest.Web.csproj
  51. +74 −0 RestfulieSilverlightClientTest.Web/RestfulieSilverlightClientTestTestPage.aspx
  52. +73 −0 RestfulieSilverlightClientTest.Web/RestfulieSilverlightClientTestTestPage.html
  53. +2 −0 RestfulieSilverlightClientTest.Web/Silverlight.js
  54. +30 −0 RestfulieSilverlightClientTest.Web/Web.Debug.config
  55. +31 −0 RestfulieSilverlightClientTest.Web/Web.Release.config
  56. +13 −0 RestfulieSilverlightClientTest.Web/Web.config
  57. +12 −0 RestfulieSilverlightClientTest.Web/order.xml
  58. +8 −0 RestfulieSilverlightClientTest/App.xaml
  59. +60 −0 RestfulieSilverlightClientTest/App.xaml.cs
  60. +13 −0 RestfulieSilverlightClientTest/MainPage.xaml
  61. +67 −0 RestfulieSilverlightClientTest/MainPage.xaml.cs
  62. +6 −0 RestfulieSilverlightClientTest/Properties/AppManifest.xml
  63. +35 −0 RestfulieSilverlightClientTest/Properties/AssemblyInfo.cs
  64. +115 −0 RestfulieSilverlightClientTest/RestfulieSilverlightClientTest.csproj
  65. BIN libs/Moq-4.0/Moq.dll
  66. +5,120 −0 libs/Moq-4.0/Moq.xml
  67. BIN libs/Newtonsoft.Json-4.0/Newtonsoft.Json.Silverlight.dll
  68. +6,361 −0 libs/Newtonsoft.Json-4.0/Newtonsoft.Json.Silverlight.xml
  69. BIN libs/Newtonsoft.Json-4.0/Newtonsoft.Json.dll
  70. +6,862 −0 libs/Newtonsoft.Json-4.0/Newtonsoft.Json.xml
View
@@ -1,27 +1,29 @@
-*.dep
-*.aps
-*.vbw
-*.suo
-*.obj
-*.ncb
-*.plg
-*.bsc
-*.ilk
-*.exp
-*.sbr
-*.opt
-*.pdb
-*.idb
-*.pch
-*.res
-*.user
+*.dep
+*.aps
+*.vbw
+*.suo
+*.obj
+*.ncb
+*.plg
+*.bsc
+*.ilk
+*.exp
+*.sbr
+*.opt
+*.pdb
+*.idb
+*.pch
+*.res
+*.user
-*\obj
-*\bin
-*\Debug
-*\Release
+*\obj
+*\bin
+*\Debug
+*\Release
./Resharp*
_ReSharper.*/
Thumbs.db
*.exe
+
+/TestResults
View
@@ -15,6 +15,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RestfulieClientTests", "Res
{9C873FC6-81A7-432C-8541-7B18AE014209} = {9C873FC6-81A7-432C-8541-7B18AE014209}
EndProjectSection
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RestfulieSilverlightClient", "RestfulieSilverlightClient\RestfulieSilverlightClient.csproj", "{7051A42C-A5AB-448E-ABEA-1DE47C2454C9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RestfulieSilverlightClientTest", "RestfulieSilverlightClientTest\RestfulieSilverlightClientTest.csproj", "{EB9484F1-7C0D-4C70-815C-B60549C53588}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RestfulieSilverlightClientTest.Web", "RestfulieSilverlightClientTest.Web\RestfulieSilverlightClientTest.Web.csproj", "{8B5F787E-1D02-4B16-BEE2-10E9FA3EFEA4}"
+EndProject
Global
GlobalSection(TestCaseManagementSettings) = postSolution
CategoryFile = RestfulieClient.vsmdi
@@ -48,6 +54,36 @@ Global
{9057B26A-DBBE-4D79-8279-909A9FB5CAF8}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{9057B26A-DBBE-4D79-8279-909A9FB5CAF8}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{9057B26A-DBBE-4D79-8279-909A9FB5CAF8}.Release|x86.ActiveCfg = Release|Any CPU
+ {7051A42C-A5AB-448E-ABEA-1DE47C2454C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7051A42C-A5AB-448E-ABEA-1DE47C2454C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7051A42C-A5AB-448E-ABEA-1DE47C2454C9}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {7051A42C-A5AB-448E-ABEA-1DE47C2454C9}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {7051A42C-A5AB-448E-ABEA-1DE47C2454C9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7051A42C-A5AB-448E-ABEA-1DE47C2454C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7051A42C-A5AB-448E-ABEA-1DE47C2454C9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7051A42C-A5AB-448E-ABEA-1DE47C2454C9}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {7051A42C-A5AB-448E-ABEA-1DE47C2454C9}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {7051A42C-A5AB-448E-ABEA-1DE47C2454C9}.Release|x86.ActiveCfg = Release|Any CPU
+ {EB9484F1-7C0D-4C70-815C-B60549C53588}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EB9484F1-7C0D-4C70-815C-B60549C53588}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EB9484F1-7C0D-4C70-815C-B60549C53588}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {EB9484F1-7C0D-4C70-815C-B60549C53588}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {EB9484F1-7C0D-4C70-815C-B60549C53588}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {EB9484F1-7C0D-4C70-815C-B60549C53588}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EB9484F1-7C0D-4C70-815C-B60549C53588}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EB9484F1-7C0D-4C70-815C-B60549C53588}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {EB9484F1-7C0D-4C70-815C-B60549C53588}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {EB9484F1-7C0D-4C70-815C-B60549C53588}.Release|x86.ActiveCfg = Release|Any CPU
+ {8B5F787E-1D02-4B16-BEE2-10E9FA3EFEA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8B5F787E-1D02-4B16-BEE2-10E9FA3EFEA4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8B5F787E-1D02-4B16-BEE2-10E9FA3EFEA4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {8B5F787E-1D02-4B16-BEE2-10E9FA3EFEA4}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {8B5F787E-1D02-4B16-BEE2-10E9FA3EFEA4}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8B5F787E-1D02-4B16-BEE2-10E9FA3EFEA4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8B5F787E-1D02-4B16-BEE2-10E9FA3EFEA4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8B5F787E-1D02-4B16-BEE2-10E9FA3EFEA4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {8B5F787E-1D02-4B16-BEE2-10E9FA3EFEA4}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {8B5F787E-1D02-4B16-BEE2-10E9FA3EFEA4}.Release|x86.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -31,6 +31,9 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
+ <Reference Include="Newtonsoft.Json">
+ <HintPath>..\libs\Newtonsoft.Json-4.0\Newtonsoft.Json.dll</HintPath>
+ </Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
@@ -40,9 +43,25 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
+ <Compile Include="features\AutoRefresh.cs" />
+ <Compile Include="features\FollowRedirects.cs" />
+ <Compile Include="features\IRequestFeature.cs" />
+ <Compile Include="features\IResponseFeature.cs" />
+ <Compile Include="features\RetryWhenUnavailable.cs" />
+ <Compile Include="http\DefaultRequestDispatcher.cs" />
+ <Compile Include="request\AsynchHelper.cs" />
+ <Compile Include="request\AsynchRequest.cs" />
+ <Compile Include="request\IRequestDispatcher.cs" />
+ <Compile Include="request\Request.cs" />
+ <Compile Include="request\RequestChain.cs" />
+ <Compile Include="request\RequestStack.cs" />
+ <Compile Include="request\ResponseChain.cs" />
+ <Compile Include="resources\DynamicJsonResource.cs" />
<Compile Include="resources\DynamicXmlResource.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
- <Compile Include="resources\Restifulie.cs" />
+ <Compile Include="resources\EmptyResource.cs" />
+ <Compile Include="resources\IResource.cs" />
+ <Compile Include="resources\Restfulie.cs" />
<Compile Include="resources\EntryPointService.cs" />
<Compile Include="service\HttpRemoteResponseFactory.cs" />
<Compile Include="service\HttpRemoteResponse.cs" />
@@ -0,0 +1,69 @@
+using System;
+using System.Text.RegularExpressions;
+using System.Threading;
+using RestfulieClient.request;
+using RestfulieClient.service;
+
+namespace RestfulieClient.features
+{
+ /// <summary>
+ /// Automatically performs a resource refresh based on the Refresh header sent by the server
+ /// </summary>
+ public class AutoRefresh : IResponseFeature
+ {
+ protected class Refresh
+ {
+ public int Seconds { get; set; }
+ public string Uri { get; set; }
+ }
+
+ public HttpRemoteResponse Process(ResponseChain chain, HttpRemoteResponse response)
+ {
+ if (!chain.Service.IsAsynch)
+ throw new NotSupportedException("AutoRefresh is not supported on synchronous requests");
+
+ response = chain.Next(response);
+
+ Refresh refresh;
+
+ if (!ShouldRefresh(response, out refresh) || refresh == null)
+ return response;
+
+ Thread.Sleep(TimeSpan.FromSeconds(refresh.Seconds)); // TODO: use a better alternative
+
+ response = chain.Service.Dispatcher.Process(chain.Service, "GET", new Uri(refresh.Uri), null);
+ response = chain.Next(response);
+
+ return response;
+ }
+
+ private static readonly Regex RefreshPattern = new Regex(@"^(\d+)\;\s?url\=(.*)$",
+ RegexOptions.Singleline
+ | RegexOptions.IgnoreCase
+#if !SILVERLIGHT
+ | RegexOptions.Compiled
+#endif
+ );
+
+ protected bool ShouldRefresh(HttpRemoteResponse response, out Refresh refresh)
+ {
+ var should = (int)response.StatusCode / 100 == 2 && response.Headers.ContainsKey("Refresh");
+
+ refresh = null;
+
+ if (should)
+ {
+ var match = RefreshPattern.Match(response.Headers["Refresh"]);
+
+ if (match.Success && match.Groups.Count == 3)
+ refresh = new Refresh
+ {
+ Seconds = int.Parse(match.Groups[1].Value),
+ Uri = match.Groups[2].Value
+ };
+ }
+
+ return should;
+ }
+ }
+}
@@ -0,0 +1,33 @@
+using System;
+using System.Net;
+using RestfulieClient.request;
+using RestfulieClient.service;
+
+namespace RestfulieClient.features
+{
+ public class FollowRedirects : IResponseFeature
+ {
+ public HttpRemoteResponse Process(ResponseChain chain, HttpRemoteResponse response) {
+ response = chain.Next(response);
+
+ if (!ShouldRedirect(response))
+ return response;
+
+ string uri;
+
+ if (response.Headers.TryGetValue("Location", out uri) && !String.IsNullOrWhiteSpace(uri)) {
+ chain.Reset();
+
+ response = chain.Service.Dispatcher.Process(chain.Service, "GET", new Uri(uri), null);
+ response = chain.Next(response);
+ }
+
+ return response;
+ }
+
+ protected bool ShouldRedirect(HttpRemoteResponse response) {
+ return (int)response.StatusCode / 100 == 3 || (
+ response.StatusCode == HttpStatusCode.Created && response.HasNoContent());
+ }
+ }
+}
@@ -0,0 +1,11 @@
+using System;
+using RestfulieClient.request;
+using RestfulieClient.service;
+
+namespace RestfulieClient.features
+{
+ public interface IRequestFeature
+ {
+ HttpRemoteResponse Process(RequestChain chain, Request request, string verb, Uri uri, string content);
+ }
+}
@@ -0,0 +1,10 @@
+using RestfulieClient.request;
+using RestfulieClient.service;
+
+namespace RestfulieClient.features
+{
+ public interface IResponseFeature
+ {
+ HttpRemoteResponse Process(ResponseChain chain, HttpRemoteResponse response);
+ }
+}
@@ -0,0 +1,23 @@
+using System;
+using System.Net;
+using RestfulieClient.request;
+using RestfulieClient.service;
+
+namespace RestfulieClient.features
+{
+ public class RetryWhenUnavailable : IRequestFeature
+ {
+ public HttpRemoteResponse Process(RequestChain chain, Request request, string verb, Uri uri, string content) {
+ var response = chain.Next(request, verb, uri, content);
+
+ if (ShouldRetry(response))
+ response = chain.Next(request, verb, uri, content);
+
+ return response;
+ }
+
+ protected virtual bool ShouldRetry(HttpRemoteResponse response) {
+ return response.StatusCode == HttpStatusCode.ServiceUnavailable;
+ }
+ }
+}
@@ -0,0 +1,90 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Text;
+using RestfulieClient.request;
+using RestfulieClient.service;
+
+namespace RestfulieClient.http
+{
+ public class DefaultRequestDispatcher : IRequestDispatcher
+ {
+ private string GetContent(HttpWebResponse response) {
+ Stream stream = response.GetResponseStream();
+
+ if (stream == null || stream.Length == 0)
+ return null;
+
+ using (var reader = new StreamReader(stream))
+ return reader.ReadToEnd();
+ }
+
+ private Stream GetRequestStreamAsynch(HttpWebRequest request) {
+ return AsynchHelper.WaitForAsynchResponse(
+ c => request.BeginGetRequestStream(c, null),
+ (r, s) => request.EndGetRequestStream(r)
+ );
+ }
+
+ private void WriteContent(bool asynch, HttpWebRequest request, string content) {
+ byte[] byteArray = Encoding.UTF8.GetBytes(content);
+ request.ContentLength = byteArray.Length;
+ Stream bodyStream;
+ if (asynch)
+ bodyStream = GetRequestStreamAsynch(request);
+ #if SILVERLIGHT
+ else
+ throw new InvalidOperationException("Silverlight must be run in async mode");
+ #else
+ else
+ bodyStream = request.GetRequestStream();
+ #endif
+ bodyStream.Write(byteArray, 0, byteArray.Length);
+ bodyStream.Close();
+ }
+
+ private HttpRemoteResponse GetRemoteResponse(HttpWebResponse response) {
+ return new HttpRemoteResponse(
+ response.StatusCode,
+ response.Headers.AllKeys.ToDictionary(k => k, k => response.Headers[k], StringComparer.OrdinalIgnoreCase),
+ GetContent(response));
+ }
+
+ private HttpWebResponse GetResponseAsynch(HttpWebRequest request) {
+ return AsynchHelper.WaitForAsynchResponse(
+ c => request.BeginGetResponse(c, null),
+ (r, s) => (HttpWebResponse)request.EndGetResponse(r)
+ );
+ }
+
+ private HttpWebResponse GetResponse(bool asynch, HttpWebRequest request) {
+ if (asynch)
+ return GetResponseAsynch(request);
+
+#if SILVERLIGHT
+ throw new InvalidOperationException("Silverlight must be run in async mode");
+#else
+ return (HttpWebResponse)request.GetResponse();
+#endif
+ }
+
+ public HttpRemoteResponse Process(IRemoteResourceService service, string verb, Uri uri, string content) {
+ HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
+ try
+ {
+ foreach (var header in service.Headers)
+ request.Headers[header.Key] = header.Value;
+
+ request.Method = verb;
+ if (!String.IsNullOrWhiteSpace(content))
+ WriteContent(service.IsAsynch, request, content);
+ return GetRemoteResponse(GetResponse(service.IsAsynch, request));
+ }
+ catch (Exception ex)
+ {
+ throw new ArgumentException(string.Format("An error occurred while connecting to the resource in url {0} with message {1}", uri, ex.Message), ex);
+ }
+ }
+ }
+}
Oops, something went wrong.

0 comments on commit 54caa07

Please sign in to comment.