├── .gitattributes ├── .gitignore ├── Painless.Html ├── icon.png └── index.html ├── Painless.Serializer.JsonNet.NuGet └── Painless.Serializer.JsonNet.NuGet.nuproj ├── PainlessHttp.DevServer ├── App.config ├── Controllers │ ├── FeatureController.cs │ └── TodoController.cs ├── Data │ ├── ITodoRepository.cs │ ├── InMemoryTodoRepo.cs │ └── RequestRepo.cs ├── Model │ └── Todo.cs ├── PainlessHttp.DevServer.csproj ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── Startup.cs └── packages.config ├── PainlessHttp.IntegrationTests ├── Features │ ├── AuthenticationTests.cs │ ├── ContentNegotiationTests.cs │ ├── ErrorStatusCodeTests.cs │ ├── ModifiedSinceTests.cs │ └── RequestTimeoutTests.cs ├── Methods │ ├── DeleteTests.cs │ ├── GetTests.cs │ ├── PerformRawTests.cs │ ├── PostTests.cs │ └── PutTests.cs ├── PainlessHttp.IntegrationTests.csproj ├── Properties │ └── AssemblyInfo.cs ├── WebApiSetupFixture.cs └── packages.config ├── PainlessHttp.Nuget └── PainlessHttp.Nuget.nuproj ├── PainlessHttp.Sandbox ├── App.config ├── PainlessHttp.Sandbox.csproj ├── Program.cs ├── Properties │ └── AssemblyInfo.cs └── packages.config ├── PainlessHttp.Serializer.JsonNet.Tests ├── PainlessHttp.Serializer.JsonNet.Tests.csproj ├── PainlessJsonNetTests.cs ├── Properties │ └── AssemblyInfo.cs └── packages.config ├── PainlessHttp.Serializer.JsonNet ├── NewtonSoft.cs ├── NewtonsoftSettings.cs ├── PainlessHttp.Serializer.JsonNet.csproj ├── PainlessJsonNet.cs ├── Properties │ └── AssemblyInfo.cs └── packages.config ├── PainlessHttp.Tests ├── PainlessHttp.Tests.csproj ├── Properties │ └── AssemblyInfo.cs ├── Serializers │ ├── Custom │ │ ├── SerializeSettingsTests.cs │ │ └── SerializerTests.cs │ ├── SerializerTestClasses.cs │ └── Typed │ │ ├── DefaultJsonSerializerTests.cs │ │ └── DefaultXmlSerializerTests.cs ├── Utils │ ├── AcceptHeaderMapperTests.cs │ ├── ClientUtilsTests.cs │ ├── ContentNegotiatorTests.cs │ ├── HttpConverterTests.cs │ ├── ResponseTransformerTests.cs │ └── UrlBuilderTests.cs └── packages.config ├── PainlessHttp.sln ├── PainlessHttp ├── Cache │ ├── CacheBase.cs │ ├── CachedObject.cs │ ├── FileCache.cs │ ├── IModifiedSinceCache.cs │ ├── InMemoryCache.cs │ └── NoCache.cs ├── Client │ ├── Configuration.cs │ ├── HttpClient.cs │ └── IHttpClient.cs ├── Http │ ├── AcceptHeaderField.cs │ ├── ContentType.cs │ ├── Contracts │ │ ├── IHttpResponse.cs │ │ └── IHttpWebResponse.cs │ ├── HttpMethod.cs │ ├── HttpResponse.cs │ └── HttpWebResponse.cs ├── Integration │ ├── ResponseTransformer.cs │ └── WebRequester.cs ├── PainlessHttp.csproj ├── Properties │ └── AssemblyInfo.cs ├── Serializers │ ├── Contracts │ │ └── IContentSerializer.cs │ ├── Custom │ │ ├── CustomSerializer.cs │ │ ├── Serializer.cs │ │ └── SerializerBulider.cs │ ├── Defaults │ │ ├── ContentSerializers.cs │ │ ├── DefaultJson.cs │ │ └── DefaultXml.cs │ └── Typed │ │ ├── DefaultJsonSerializer.cs │ │ ├── DefaultNoActionSerializer.cs │ │ └── DefaultXmlSerializer.cs └── Utils │ ├── AcceptHeaderMapper.cs │ ├── ClientUtils.cs │ ├── ContentNegotiator.cs │ ├── HttpConverter.cs │ ├── UrlBuilder.cs │ └── WebRequestBuilder.cs └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # * text=auto 2 | 3 | *.csproj -text merge=union 4 | *.sln -text merge=union 5 | 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | !performance-tests/jmeter/bin/ 4 | [Oo]bj/ 5 | 6 | # mstest test results 7 | TestResults 8 | 9 | # NCrunch files 10 | _NCrunch_PainlessHttp 11 | **/*.v2.ncrunchproject 12 | *v2.ncrunchsolution 13 | 14 | ## Ignore Visual Studio temporary files, build results, and 15 | ## files generated by popular Visual Studio add-ons. 16 | 17 | # User-specific files 18 | *.suo 19 | *.user 20 | *.sln.docstates 21 | 22 | # Build results 23 | [Dd]ebug/ 24 | [Rr]elease/ 25 | x64/ 26 | *_i.c 27 | *_p.c 28 | *.ilk 29 | *.meta 30 | *.obj 31 | *.pch 32 | *.pdb 33 | *.pgc 34 | *.pgd 35 | *.rsp 36 | *.sbr 37 | *.tlb 38 | *.tli 39 | *.tlh 40 | *.tmp 41 | *.log 42 | *.vspscc 43 | *.vssscc 44 | .builds 45 | *.nupkg 46 | 47 | # Visual C++ cache files 48 | ipch/ 49 | *.aps 50 | *.ncb 51 | *.opensdf 52 | *.sdf 53 | 54 | # Visual Studio profiler 55 | *.psess 56 | *.vsp 57 | *.vspx 58 | 59 | # NuGet Packages Directory 60 | packages 61 | 62 | SeleniumTests/Reports 63 | SeleniumTests/*.jar 64 | -------------------------------------------------------------------------------- /Painless.Html/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pardahlman/PainlessHttp/796a0e5f49b844cfa95e0b93b1ebf982c05c4337/Painless.Html/icon.png -------------------------------------------------------------------------------- /Painless.Html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Painless Http 4 | 5 | 6 | 19 | 20 | 21 |
22 | 23 |

Painless Http Icon Painless Http HTTP has never been easier

24 |

25 | 26 | 27 |

28 |

New version available!

29 |
30 |

31 | PM> Install-Package PainlessHttp -Version 0.11.2 32 |

33 |
34 |
35 | Fork me on GitHub 36 |
37 | No external libraries! No over engineered method signatures! No uber verbose setup! Just a HTTP client that is so easy to use that it won't ever give you any headache! 38 |

39 | 46 |
47 |
	//instanciate client
 48 | 	var client = new HttpClient("http://painless.pardahlman.se");
 49 | 
 50 | 	// create new entity
 51 | 	var tomorrow = new Todo { Description = "Sleep in", IsCompleted = false};
 52 | 	var created = await client.PostAsync<Todo>("/api/todos", tomorrow);
 53 | 
 54 | 	// get it
 55 | 	var response = await client.GetAsync<Todo>("/api/todos/1");
 56 | 	var existing = response.Body;
 57 | 
 58 | 	// update it
 59 | 	existing.IsCompleted = true;
 60 | 	var updated = await client.PutAsync<Todo>("/api/todos/1", existing);
 61 | 
 62 | 	// delete it
 63 | 	var deleted = await client.DeleteAsync<string>("/api/todos/1");
 64 | 	if (deleted.StatusCode == HttpStatusCode.OK)
 65 | 	{
 66 | 		Console.Write("Successfully deleted todo");
 67 | 	}
 68 | 
69 | 70 | Want to have greater control over how things are done? Just instanciate the client with a Configuration object, and you'll have the posibility to change just about everything: 71 |
72 |
73 |
  //create config
 74 |   var config = new Configuration
 75 |   {
 76 |     BaseUrl = "http://painless.pardahlman.se",
 77 |     Advanced =
 78 |     {
 79 |       Serializers = new List { new PainlessJsonNet() },
 80 |       ModifiedSinceCache = new FileCache(cacheDirectory: Environment.CurrentDirectory),
 81 |       RequestTimeout = new TimeSpan(days:0, hours:0, minutes:0, seconds:2),
 82 |       ContentNegotiation = true,
 83 |       Credentials = new NetworkCredential("pardahlman", "strong-password"),
 84 |       WebrequestModifier = request => request.Headers.Add("X-Additional-Header", "For each request")
 85 |     }
 86 |   };
 87 |   var client = new HttpClient(config);
 88 | 
89 | 90 | 91 |
92 | 93 | 94 | 95 | 105 | 106 | -------------------------------------------------------------------------------- /Painless.Serializer.JsonNet.NuGet/Painless.Serializer.JsonNet.NuGet.nuproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 8 | 9 | Release 10 | AnyCPU 11 | 12 | 13 | 14 | e7411a4c-46a6-4270-b45e-d827518fe61c 15 | 16 | 17 | $(MSBuildExtensionsPath)\NuProj\ 18 | 19 | 20 | 21 | Painless.Serializer.JsonNet 22 | 0.11.1 23 | PainlessHttp: Json.Net Serializer 24 | par.dahlman 25 | par.dahlman 26 | The PainlessHttp client has plugable serialzers. This is a implementation of JsonNet's popular json serializer. 27 | The PainlessHttp client has plugable serialzers. This is a implementation of JsonNet's popular json serializer. For more information on usage, visit the project homepage. 28 | 29 | 30 | http://painless.pardahlman.se 31 | 32 | 33 | Copyright © par.dahlman 34 | Painless Serializer JsonNet 35 | http://painless.pardahlman.se/icon.png 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /PainlessHttp.DevServer/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /PainlessHttp.DevServer/Controllers/FeatureController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Net; 4 | using System.Net.Http; 5 | using System.Net.Http.Headers; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using System.Web.Http; 10 | using PainlessHttp.DevServer.Data; 11 | using PainlessHttp.DevServer.Model; 12 | using PainlessHttp.Http; 13 | using PainlessHttp.Serializers.Typed; 14 | 15 | namespace PainlessHttp.DevServer.Controllers 16 | { 17 | public class FeatureController : ApiController 18 | { 19 | private readonly DefaultXmlSerializer _xmlSerializer; 20 | private readonly DefaultJsonSerializer _jsonSerializer; 21 | private readonly RequestRepo _requestRepo; 22 | 23 | public FeatureController() 24 | { 25 | _xmlSerializer = new DefaultXmlSerializer(); 26 | _jsonSerializer = new DefaultJsonSerializer(); 27 | _requestRepo = RequestRepo.Instance; 28 | } 29 | 30 | #region Feature: Content-Type 31 | 32 | [Route("api/content-type")] 33 | [HttpGet] 34 | public HttpResponseMessage GetData(string prefered = "") 35 | { 36 | var todo = new Todo 37 | { 38 | Description = "Respond with a specific content type", 39 | IsCompleted = true 40 | }; 41 | 42 | 43 | var content = string.Empty; 44 | var contentType = string.Empty; 45 | 46 | if (string.Equals(prefered, "json", StringComparison.InvariantCultureIgnoreCase)) 47 | { 48 | content = _jsonSerializer.Serialize(todo); 49 | contentType = ContentTypes.ApplicationJson; 50 | } 51 | if (string.Equals(prefered, "xml", StringComparison.InvariantCultureIgnoreCase)) 52 | { 53 | content = _xmlSerializer.Serialize(todo); 54 | contentType = ContentTypes.ApplicationXml; 55 | } 56 | 57 | 58 | if (string.IsNullOrWhiteSpace(content)) 59 | { 60 | return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Expect a 'prefered' query parameter with value 'json' or 'xml' in request."); 61 | } 62 | 63 | var response = new HttpResponseMessage(HttpStatusCode.OK) 64 | { 65 | Content = new StringContent(content, Encoding.UTF8, contentType) 66 | }; 67 | return response; 68 | } 69 | 70 | [Route("api/content-type")] 71 | [HttpPost] 72 | public HttpResponseMessage ReciveData(string accept = "") 73 | { 74 | var contentType = Request.Content.Headers.ContentType.ToString(); 75 | var acceptHeader = ContentTypes.ApplicationJson; 76 | var matching = false; 77 | 78 | if (string.Equals(accept, "json", StringComparison.InvariantCultureIgnoreCase)) 79 | { 80 | matching = string.Equals(contentType, ContentTypes.ApplicationJson); 81 | acceptHeader = ContentTypes.ApplicationJson; 82 | } 83 | if (string.Equals(accept, "xml", StringComparison.InvariantCultureIgnoreCase)) 84 | { 85 | matching = string.Equals(contentType, ContentTypes.ApplicationXml); 86 | acceptHeader = ContentTypes.ApplicationXml; 87 | } 88 | 89 | if (matching) 90 | { 91 | return Request.CreateResponse(HttpStatusCode.OK, "Accepted"); 92 | } 93 | 94 | var response = new HttpResponseMessage(HttpStatusCode.UnsupportedMediaType); 95 | response.Headers.AcceptRanges.Clear(); 96 | response.Headers.Add("Accept", acceptHeader); 97 | return response; 98 | } 99 | 100 | #endregion 101 | 102 | #region Feature: Authentication 103 | 104 | [Route("api/authentication")] 105 | [HttpGet] 106 | public HttpResponseMessage GetProtectedResource(string user = null, string pwd = null) 107 | { 108 | _requestRepo.SaveRequestIfPossible(Request); 109 | var authHeader = Request.Headers.Authorization; 110 | if (authHeader == null) 111 | { 112 | var response = new HttpResponseMessage(HttpStatusCode.Unauthorized); 113 | response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Basic")); 114 | return response; 115 | } 116 | 117 | var data = Convert.FromBase64String(authHeader.Parameter); 118 | var decodedString = Encoding.UTF8.GetString(data).Split(':'); 119 | var providedUsername = decodedString[0]; 120 | var providedPassword = decodedString[1]; 121 | var authenticated = false; 122 | if (string.IsNullOrWhiteSpace(user) || string.IsNullOrWhiteSpace(pwd)) 123 | { 124 | authenticated = string.Equals(providedUsername, providedPassword); 125 | } 126 | else 127 | { 128 | authenticated = string.Equals(providedUsername, user) && string.Equals(providedPassword, pwd); 129 | } 130 | 131 | if (authenticated) 132 | { 133 | return Request.CreateResponse(HttpStatusCode.OK, "You are authenticated"); 134 | } 135 | else 136 | { 137 | return Request.CreateResponse(HttpStatusCode.Forbidden, "Authentication Failed"); 138 | } 139 | } 140 | 141 | 142 | #endregion 143 | 144 | #region Feature: ErrorReponse 145 | [Route("api/error-code")] 146 | [HttpGet] 147 | public HttpResponseMessage GetErrorRespnse(int statusCode = 400, string message = "add message query parameter to change this message") 148 | { 149 | var httpStatusCode = (HttpStatusCode) statusCode; 150 | var response = new HttpResponseMessage() 151 | { 152 | StatusCode = httpStatusCode, 153 | Content = new StringContent(message) 154 | }; 155 | response.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json"); 156 | return response; 157 | } 158 | 159 | #endregion 160 | 161 | #region Feature: Request Timeout 162 | [Route("api/timeout")] 163 | [HttpGet] 164 | public async Task GetWithTimeout(int delay = 0) 165 | { 166 | var stopWatch = new Stopwatch(); 167 | stopWatch.Start(); 168 | var waitForDelay = new TaskCompletionSource(); 169 | var timer = new Timer(state => waitForDelay.SetResult(true), null, delay, int.MaxValue); 170 | await waitForDelay.Task; 171 | stopWatch.Stop(); 172 | var response = Request.CreateResponse(HttpStatusCode.OK); 173 | response.Content = new StringContent("Performed request in " + stopWatch.ElapsedMilliseconds + " ms.", Encoding.UTF8, ContentTypes.TextPlain); 174 | return response; 175 | } 176 | #endregion 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /PainlessHttp.DevServer/Controllers/TodoController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Http; 6 | using System.Web.Http; 7 | using Microsoft.Owin; 8 | using PainlessHttp.DevServer.Data; 9 | using PainlessHttp.DevServer.Model; 10 | 11 | namespace PainlessHttp.DevServer.Controllers 12 | { 13 | public class TodoController : ApiController 14 | { 15 | private readonly ITodoRepository _repo; 16 | 17 | public TodoController() 18 | { 19 | _repo = InMemoryTodoRepo.Instance; 20 | } 21 | 22 | [Route("api/todos")] 23 | [HttpGet] 24 | public HttpResponseMessage GetAllTodos(bool includeCompeted = true) 25 | { 26 | var todos = _repo.GetAll(); 27 | if (includeCompeted == false) 28 | { 29 | todos = todos.Where(t => t.IsCompleted == false).ToList(); 30 | } 31 | return Request.CreateResponse(HttpStatusCode.OK, todos); 32 | } 33 | 34 | [Route("api/todos/{id}")] 35 | [HttpGet] 36 | public HttpResponseMessage GetTodo(int id) 37 | { 38 | var found = _repo.Get(id); 39 | if (found == null) 40 | { 41 | return Request.CreateResponse(HttpStatusCode.NotFound); 42 | } 43 | 44 | if (Request.Headers.IfModifiedSince.HasValue && Request.Headers.IfModifiedSince.Value <= found.UpdateDate) 45 | { 46 | return Request.CreateResponse(HttpStatusCode.NotModified); 47 | } 48 | 49 | var response = Request.CreateResponse(HttpStatusCode.OK, found); 50 | response.Content.Headers.LastModified = new DateTimeOffset(found.UpdateDate); 51 | return response; 52 | } 53 | 54 | [Route("api/todos")] 55 | [HttpPost] 56 | public HttpResponseMessage AddTodo(Todo todo) 57 | { 58 | if (todo == null) 59 | { 60 | return Request.CreateResponse(HttpStatusCode.BadRequest); 61 | } 62 | 63 | var created = _repo.Add(todo); 64 | return Request.CreateResponse(HttpStatusCode.Created, created); 65 | } 66 | 67 | [Route("api/todos")] 68 | [HttpPut] 69 | public HttpResponseMessage UpdateTodo(Todo todo) 70 | { 71 | if (todo == null) 72 | { 73 | return Request.CreateResponse(HttpStatusCode.BadRequest); 74 | } 75 | 76 | var found = _repo.Get(todo.Id); 77 | if (found == null) 78 | { 79 | return Request.CreateResponse(HttpStatusCode.NotFound); 80 | } 81 | 82 | var updated = _repo.Update(todo); 83 | return Request.CreateResponse(HttpStatusCode.NoContent, updated); 84 | } 85 | 86 | [Route("api/todos/{id}")] 87 | [HttpDelete] 88 | public HttpResponseMessage RemoveTodo(int id) 89 | { 90 | var found = _repo.Get(id); 91 | if (found == null) 92 | { 93 | return Request.CreateResponse(HttpStatusCode.NotFound); 94 | } 95 | _repo.Remove(found); 96 | return Request.CreateResponse(HttpStatusCode.OK); 97 | } 98 | 99 | 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /PainlessHttp.DevServer/Data/ITodoRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using PainlessHttp.DevServer.Model; 3 | 4 | namespace PainlessHttp.DevServer.Data 5 | { 6 | public interface ITodoRepository 7 | { 8 | Todo Add(Todo todo); 9 | Todo Update(Todo todo); 10 | void Remove(Todo todo); 11 | Todo Get(int id); 12 | List GetAll(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /PainlessHttp.DevServer/Data/InMemoryTodoRepo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using PainlessHttp.DevServer.Model; 5 | 6 | namespace PainlessHttp.DevServer.Data 7 | { 8 | public class InMemoryTodoRepo : ITodoRepository 9 | { 10 | #region Constructor & Singelton 11 | private static InMemoryTodoRepo _instance; 12 | public static InMemoryTodoRepo Instance 13 | { 14 | get { return _instance ?? (_instance = new InMemoryTodoRepo()); } 15 | } 16 | private InMemoryTodoRepo() 17 | { 18 | _todos = new List 19 | { 20 | new Todo { Id = 1, IsCompleted = true, Description = "Start writing awsome todo app", UpdateDate = DateTime.Now.AddDays(-1)}, 21 | new Todo { Id = 2, IsCompleted = false, Description = "Invent awsome idea that makes you a millionare", UpdateDate = DateTime.Now.AddDays(-1)}, 22 | new Todo { Id = 3, IsCompleted = true, Description = "Find out why Redbull sponsors sports event.", UpdateDate = DateTime.Now.AddDays(-1)}, 23 | new Todo { Id = 4, IsCompleted = true, Description = "Make a list of awsome books.", UpdateDate = DateTime.Now.AddDays(-1)}, 24 | }; 25 | } 26 | #endregion 27 | 28 | private readonly List _todos; 29 | 30 | public Todo Add(Todo todo) 31 | { 32 | if (todo == null) 33 | { 34 | return null; 35 | } 36 | if (_todos.Any(t => t.Id == todo.Id)) 37 | { 38 | Update(todo); 39 | } 40 | else 41 | { 42 | todo.Id = _todos.Count + 1; 43 | _todos.Add(todo); 44 | } 45 | return todo; 46 | } 47 | 48 | public Todo Update(Todo todo) 49 | { 50 | var found = _todos.FirstOrDefault(t => t.Id == todo.Id); 51 | if(found == null) 52 | return null; 53 | found.Description = todo.Description; 54 | found.IsCompleted = todo.IsCompleted; 55 | 56 | return found; 57 | } 58 | 59 | public void Remove(Todo todo) 60 | { 61 | var found = _todos.FirstOrDefault(t => t.Id == todo.Id); 62 | if (found == null) 63 | { 64 | return; 65 | } 66 | _todos.Remove(found); 67 | } 68 | 69 | public Todo Get(int id) 70 | { 71 | return _todos.FirstOrDefault(t => t.Id == id); 72 | } 73 | 74 | public List GetAll() 75 | { 76 | var copy = _todos 77 | .Select(t => new Todo {Id = t.Id, Description = t.Description, IsCompleted = t.IsCompleted}) 78 | .ToList(); 79 | 80 | return copy; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /PainlessHttp.DevServer/Data/RequestRepo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using System.Runtime.InteropServices.ComTypes; 6 | 7 | namespace PainlessHttp.DevServer.Data 8 | { 9 | public class RequestRepo 10 | { 11 | public const string RequestIdentifierHeader = "X-Request-Id"; 12 | private readonly Dictionary> _dictionary; 13 | 14 | private static RequestRepo instace; 15 | 16 | public static RequestRepo Instance 17 | { 18 | get 19 | { 20 | if (instace == null) 21 | { 22 | instace = new RequestRepo(); 23 | } 24 | return instace; 25 | } 26 | } 27 | 28 | private RequestRepo() 29 | { 30 | _dictionary = new Dictionary>(); 31 | } 32 | 33 | public void SaveRequestIfPossible(HttpRequestMessage request) 34 | { 35 | if (request == null) 36 | { 37 | return; 38 | } 39 | IEnumerable values; 40 | if (!request.Headers.TryGetValues(RequestIdentifierHeader, out values)) 41 | { 42 | return; 43 | } 44 | 45 | foreach (var value in values) 46 | { 47 | if (_dictionary.ContainsKey(value)) 48 | { 49 | _dictionary[value].Add(request); 50 | } 51 | else 52 | { 53 | _dictionary.Add(value, new List{ request}); 54 | } 55 | } 56 | } 57 | 58 | public IEnumerable GetRequestForSession(string sessionId) 59 | { 60 | if (_dictionary.ContainsKey(sessionId)) 61 | { 62 | return _dictionary[sessionId]; 63 | } 64 | return new List(); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /PainlessHttp.DevServer/Model/Todo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PainlessHttp.DevServer.Model 4 | { 5 | public class Todo 6 | { 7 | public int Id { get; set; } 8 | public string Description { get; set; } 9 | public bool IsCompleted { get; set; } 10 | public DateTime UpdateDate { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /PainlessHttp.DevServer/PainlessHttp.DevServer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {C7256164-4D19-40BE-9CA6-42C3CB8EB164} 8 | Exe 9 | Properties 10 | PainlessHttp.DevServer 11 | PainlessHttp.DevServer 12 | v4.5.1 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | ..\packages\Microsoft.Owin.2.0.2\lib\net45\Microsoft.Owin.dll 38 | 39 | 40 | ..\packages\Microsoft.Owin.Host.HttpListener.2.0.2\lib\net45\Microsoft.Owin.Host.HttpListener.dll 41 | 42 | 43 | ..\packages\Microsoft.Owin.Hosting.2.0.2\lib\net45\Microsoft.Owin.Hosting.dll 44 | 45 | 46 | False 47 | ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll 48 | 49 | 50 | ..\packages\Owin.1.0\lib\net40\Owin.dll 51 | 52 | 53 | 54 | 55 | 56 | False 57 | ..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll 58 | 59 | 60 | False 61 | ..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll 62 | 63 | 64 | ..\packages\Microsoft.AspNet.WebApi.Owin.5.2.2\lib\net45\System.Web.Http.Owin.dll 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | {1719F085-34F2-48B6-AD2E-98B67856B3B0} 90 | PainlessHttp 91 | 92 | 93 | 94 | 101 | -------------------------------------------------------------------------------- /PainlessHttp.DevServer/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Owin.Hosting; 3 | 4 | namespace PainlessHttp.DevServer 5 | { 6 | class Program 7 | { 8 | static void Main() 9 | { 10 | const string baseAddress = "http://localhost:1337/"; 11 | 12 | var app = WebApp.Start(baseAddress); 13 | Console.WriteLine("Painless Http Development Appserver is started at {0}.", baseAddress); 14 | Console.Write("Press enter to shut down client"); 15 | Console.ReadLine(); 16 | app.Dispose(); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /PainlessHttp.DevServer/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("PainlessHttp.DevServer")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("PainlessHttp.DevServer")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 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("cb26b9e8-f215-4e07-9b3d-966a6f52bc28")] 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 | -------------------------------------------------------------------------------- /PainlessHttp.DevServer/Startup.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Http; 2 | using Owin; 3 | 4 | namespace PainlessHttp.DevServer 5 | { 6 | public class Startup 7 | { 8 | public void Configuration(IAppBuilder appBuilder) 9 | { 10 | var config = new HttpConfiguration(); 11 | config.MapHttpAttributeRoutes(); 12 | appBuilder.UseWebApi(config); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /PainlessHttp.DevServer/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /PainlessHttp.IntegrationTests/Features/AuthenticationTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Net; 4 | using NUnit.Framework; 5 | using PainlessHttp.Client; 6 | using PainlessHttp.DevServer.Data; 7 | using PainlessHttp.Http; 8 | 9 | namespace PainlessHttp.IntegrationTests.Features 10 | { 11 | [TestFixture] 12 | public class AuthenticationTests 13 | { 14 | private HttpClient _client; 15 | private string _testRequestId; 16 | private RequestRepo _requestRepo; 17 | 18 | [SetUp] 19 | public void Setup() 20 | { 21 | _testRequestId = Guid.NewGuid().ToString(); 22 | _requestRepo = RequestRepo.Instance; 23 | var config = new Configuration 24 | { 25 | BaseUrl = WebApiSetupFixture.BaseAddress, 26 | Advanced = 27 | { 28 | WebrequestModifier = w => w.Headers.Add("X-Request-Id", _testRequestId), 29 | Credentials = new NetworkCredential("pardahlman", "password") 30 | } 31 | }; 32 | 33 | _client = new HttpClient(config); 34 | } 35 | 36 | [Test] 37 | public void Should_Return_Unauthorized_If_No_Credentials_Provided() 38 | { 39 | /* Setup */ 40 | _client = new HttpClient(WebApiSetupFixture.BaseAddress); 41 | 42 | /* Test */ 43 | var response = _client.Get("api/authentication"); 44 | 45 | /* Assert */ 46 | Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); 47 | } 48 | 49 | [Test] 50 | public async void Should_Return_Forbidden_If_Authentication_Failed() 51 | { 52 | /* Setup */ 53 | 54 | /* Test */ 55 | var response = await _client.GetAsync("api/authentication"); 56 | 57 | /* Assert */ 58 | Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); 59 | } 60 | 61 | [Test] 62 | public async void Should_Return_Ok_If_Authentication_Succeeded() 63 | { 64 | /* Setup */ 65 | 66 | /* Test */ 67 | var response = await _client.GetAsync("api/authentication", new { user = "pardahlman", pwd = "password"}); 68 | 69 | /* Assert */ 70 | Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK)); 71 | } 72 | 73 | [Test] 74 | public async void Should_Only_Authenticate_Once_Per_Domain() 75 | { 76 | /* Setup */ 77 | 78 | /* Test */ 79 | await _client.GetAsync("api/authentication", new { user = "pardahlman", pwd = "password" }); 80 | await _client.GetAsync("api/authentication", new { user = "pardahlman", pwd = "password" }); 81 | 82 | /* Asserts*/ 83 | var requests = _requestRepo.GetRequestForSession(_testRequestId).ToList(); 84 | Assert.That(requests, Has.Count.EqualTo(3)); 85 | Assert.That(requests[0].Headers.Authorization, Is.Null, "No Auth header in first request"); 86 | Assert.That(requests[1].Headers.Authorization, Is.Not.Null, "Second request contains auth headers."); 87 | Assert.That(requests[2].Headers.Authorization, Is.Not.Null, "Second request contains auth headers."); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /PainlessHttp.IntegrationTests/Features/ContentNegotiationTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using NUnit.Framework; 8 | using PainlessHttp.Client; 9 | using PainlessHttp.DevServer.Model; 10 | using PainlessHttp.Http; 11 | 12 | namespace PainlessHttp.IntegrationTests.Features 13 | { 14 | [TestFixture] 15 | public class ContentNegotiationTests 16 | { 17 | [TestCase(ContentType.ApplicationJson, "json", HttpStatusCode.OK)] 18 | [TestCase(ContentType.ApplicationJson, "xml", HttpStatusCode.UnsupportedMediaType)] 19 | [TestCase(ContentType.ApplicationXml, "xml", HttpStatusCode.OK)] 20 | [TestCase(ContentType.ApplicationXml, "json", HttpStatusCode.UnsupportedMediaType)] 21 | public async void Should_Not_Automatically_Negotiate_Content_If_Content_Type_Is_Defined(ContentType clientSends, string endpointAccepts, HttpStatusCode expectedStatusCode) 22 | { 23 | /* Setup */ 24 | var config = new Configuration 25 | { 26 | BaseUrl = WebApiSetupFixture.BaseAddress, 27 | Advanced = 28 | { 29 | ContentNegotiation = false 30 | } 31 | }; 32 | var client = new HttpClient(config); 33 | var newTodo = new Todo { Description = "Write tests" }; 34 | 35 | /* Test */ 36 | var created = await client.PostAsync("/api/content-type", newTodo, new { accept = endpointAccepts }, clientSends); 37 | 38 | /* Assert */ 39 | Assert.That(created.StatusCode, Is.EqualTo(expectedStatusCode)); 40 | } 41 | 42 | [TestCase(ContentType.ApplicationJson, "xml")] 43 | [TestCase(ContentType.ApplicationXml, "json")] 44 | public async void Should_Negotiate_Content_As_Default_Behaviour(ContentType clientSends, string endpointAccepts) 45 | { 46 | /* Setup */ 47 | var client = new HttpClient(WebApiSetupFixture.BaseAddress); 48 | var newTodo = new Todo { Description = "Write tests" }; 49 | 50 | /* Test */ 51 | var created = await client.PostAsync("/api/content-type", newTodo, new { accept = endpointAccepts }, clientSends); 52 | 53 | Assert.That(created.StatusCode, Is.EqualTo(HttpStatusCode.OK)); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /PainlessHttp.IntegrationTests/Features/ErrorStatusCodeTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | using PainlessHttp.Client; 4 | using PainlessHttp.DevServer.Model; 5 | 6 | namespace PainlessHttp.IntegrationTests.Features 7 | { 8 | [TestFixture] 9 | public class ErrorStatusCodeTests 10 | { 11 | private HttpClient _client; 12 | 13 | [SetUp] 14 | public void Setup() 15 | { 16 | var config = new Configuration 17 | { 18 | BaseUrl = WebApiSetupFixture.BaseAddress 19 | }; 20 | 21 | _client = new HttpClient(config); 22 | } 23 | 24 | [Test] 25 | public void Should_Return_Raw_Body_Upon_Failed_Request() 26 | { 27 | /* Setup */ 28 | const string expectedMessage = "Unable to perform request"; 29 | 30 | /* Test */ 31 | var response = _client.Get("api/error-code", new { message = expectedMessage }); 32 | 33 | /* Assert */ 34 | Assert.That(response.RawBody, Is.EqualTo(expectedMessage)); 35 | } 36 | 37 | [Test] 38 | public void Should_Throw_Exception_If_Response_Is_In_Acepted_Range_But_Not_Serializable() 39 | { 40 | /* Setup */ 41 | const string expectedMessage = "Request successful"; 42 | 43 | /* Test */ 44 | /* Assert */ 45 | Assert.Throws(() => _client.Get("api/error-code", new { message = expectedMessage, statusCode = 200 })); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /PainlessHttp.IntegrationTests/Features/ModifiedSinceTests.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using NUnit.Framework; 3 | using PainlessHttp.Cache; 4 | using PainlessHttp.Client; 5 | using PainlessHttp.DevServer.Model; 6 | 7 | namespace PainlessHttp.IntegrationTests.Features 8 | { 9 | [TestFixture] 10 | public class ModifiedSinceTests 11 | { 12 | private HttpClient _client; 13 | private Configuration _config; 14 | 15 | [SetUp] 16 | public void Setup() 17 | { 18 | _config = new Configuration 19 | { 20 | BaseUrl = WebApiSetupFixture.BaseAddress, 21 | }; 22 | 23 | _client = new HttpClient(_config); 24 | } 25 | 26 | [Test] 27 | public async void Should_Use_In_Memory_Content_Cache() 28 | { 29 | /* Setup */ 30 | _config.Advanced.ModifiedSinceCache = new InMemoryCache(); 31 | var firstResponse = await _client.GetAsync("api/todos/1"); 32 | 33 | /* Test */ 34 | var secondResponse = await _client.GetAsync("api/todos/1"); 35 | 36 | /* Assert */ 37 | Assert.That(firstResponse.StatusCode, Is.EqualTo(HttpStatusCode.OK)); 38 | Assert.That(secondResponse.StatusCode, Is.EqualTo(HttpStatusCode.NotModified)); 39 | Assert.That(firstResponse.Body.Id, Is.EqualTo(secondResponse.Body.Id)); 40 | } 41 | 42 | [Test] 43 | public async void Should_Use_File_Cache() 44 | { 45 | /* Setup */ 46 | var fileCache = new FileCache(); 47 | _config.Advanced.ModifiedSinceCache = fileCache; 48 | fileCache.Clear(); 49 | var firstResponse = await _client.GetAsync("api/todos/1"); 50 | 51 | /* Test */ 52 | var secondResponse = await _client.GetAsync("api/todos/1"); 53 | 54 | /* Assert */ 55 | Assert.That(firstResponse.StatusCode, Is.EqualTo(HttpStatusCode.OK)); 56 | Assert.That(secondResponse.StatusCode, Is.EqualTo(HttpStatusCode.NotModified)); 57 | Assert.That(firstResponse.Body.Id, Is.EqualTo(secondResponse.Body.Id)); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /PainlessHttp.IntegrationTests/Features/RequestTimeoutTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Threading.Tasks; 7 | using NUnit.Framework; 8 | using PainlessHttp.Client; 9 | using PainlessHttp.DevServer.Model; 10 | using PainlessHttp.Http; 11 | using PainlessHttp.Http.Contracts; 12 | 13 | namespace PainlessHttp.IntegrationTests.Features 14 | { 15 | [TestFixture] 16 | public class RequestTimeoutTests 17 | { 18 | private HttpClient _client; 19 | private Configuration _config; 20 | 21 | [SetUp] 22 | public void Setup() 23 | { 24 | _config = new Configuration 25 | { 26 | BaseUrl = WebApiSetupFixture.BaseAddress, 27 | Advanced = 28 | { 29 | RequestTimeout = new TimeSpan(0,0,0,0,1500) 30 | } 31 | }; 32 | 33 | _client = new HttpClient(_config); 34 | } 35 | 36 | [Test] 37 | public async void Should_Timeout_If_Response_Time_Is_Above_Configuration_Threshhold() 38 | { 39 | /* Setup */ 40 | /* Test */ 41 | var response = await _client.GetAsync("api/timeout", new {delay = 2001}); 42 | 43 | /* Assert */ 44 | Assert.That(response.Body, Is.Null); 45 | Assert.That(response.StatusCode, Is.Not.EqualTo(HttpStatusCode.OK)); 46 | Assert.That(response.RawBody, Contains.Substring("The time out can be increased by configuring the client's Request time-out at Configuration.Advanced.RequestTimeout")); 47 | } 48 | 49 | [Test] 50 | public async void Should_Not_Timeout_If_Response_Time_Is_Below_Configuration_Threshhold() 51 | { 52 | /* Setup */ 53 | /* Test */ 54 | var response = await _client.GetAsync("api/timeout", new { delay = 1 }); 55 | 56 | /* Assert */ 57 | Assert.That(response.RawBody, Is.Not.StringContaining("The time out can be increased by configuring the client's Request time-out at Configuration.Advanced.RequestTimeout")); 58 | Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK)); 59 | } 60 | 61 | [TestCase(new []{ 1 })] 62 | [TestCase(new []{ 1, 10 })] 63 | [TestCase(new []{ 1, 10, 100 })] 64 | [TestCase(new []{ 1, 10, 100, 200 })] 65 | [TestCase(new []{ 1, 10, 100, 200, 300 })] 66 | [TestCase(new []{ 1, 10, 100, 200, 300, 400 })] 67 | public async void Should_Not_Get_Performance_Issues_When_Performing_Several_Requests(int[] delays) 68 | { 69 | /* Warm up */ 70 | await _client.GetAsync("api/timeout", new { delay = 1 }); 71 | 72 | /* Setup */ 73 | var responses = new List>(); 74 | var maxResponseTime = delays.Aggregate(0, (accumilated, increment) => accumilated + increment) + delays.Length*20; // accumulated delays + 20 ms overhead/request 75 | var stopWatch = new Stopwatch(); 76 | /* Test */ 77 | stopWatch.Start(); 78 | foreach (var delay in delays) 79 | { 80 | var response = await _client.GetAsync("api/timeout", new { delay = delay }); 81 | responses.Add(response); 82 | } 83 | stopWatch.Stop(); 84 | 85 | /* Assert */ 86 | var errorResponses = responses.Where(r => r.StatusCode != HttpStatusCode.OK).ToList(); 87 | Assert.That(errorResponses, Is.Empty); 88 | Assert.That(stopWatch.ElapsedMilliseconds, Is.LessThanOrEqualTo(maxResponseTime)); 89 | } 90 | 91 | 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /PainlessHttp.IntegrationTests/Methods/DeleteTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using NUnit.Framework; 8 | using PainlessHttp.Client; 9 | using PainlessHttp.DevServer.Data; 10 | using PainlessHttp.DevServer.Model; 11 | 12 | namespace PainlessHttp.IntegrationTests.Methods 13 | { 14 | [TestFixture] 15 | public class DeleteTests 16 | { 17 | private HttpClient _client; 18 | private InMemoryTodoRepo _repo; 19 | 20 | [SetUp] 21 | public void Setup() 22 | { 23 | var config = new Configuration 24 | { 25 | BaseUrl = WebApiSetupFixture.BaseAddress 26 | }; 27 | 28 | _client = new HttpClient(config); 29 | _repo = InMemoryTodoRepo.Instance; 30 | } 31 | 32 | [Test] 33 | public void Should_Be_Able_To_Perform_Delete() 34 | { 35 | /* Setup */ 36 | var todo = new Todo { Description = "Do the dishes" }; 37 | var saved = _repo.Add(todo); 38 | 39 | /* Test */ 40 | var response = _client.Delete(string.Format("api/todos/{0}", saved.Id)); 41 | 42 | /* Assert */ 43 | Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK)); 44 | Assert.That(_repo.Get(saved.Id), Is.Null, "Todo should have been removed"); 45 | } 46 | 47 | [Test] 48 | public async void Should_Be_Able_To_Perform_Delete_Async() 49 | { 50 | /* Setup */ 51 | var todo = new Todo { Description = "Do the dishes" }; 52 | var saved = _repo.Add(todo); 53 | 54 | /* Test */ 55 | var response = await _client.DeleteAsync(string.Format("api/todos/{0}", saved.Id)); 56 | 57 | /* Assert */ 58 | Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK)); 59 | Assert.That(_repo.Get(saved.Id), Is.Null, "Todo should have been removed"); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /PainlessHttp.IntegrationTests/Methods/GetTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using NUnit.Framework; 4 | using PainlessHttp.Client; 5 | using PainlessHttp.DevServer.Data; 6 | using PainlessHttp.DevServer.Model; 7 | 8 | namespace PainlessHttp.IntegrationTests.Methods 9 | { 10 | [TestFixture] 11 | public class GetTests 12 | { 13 | private HttpClient _client; 14 | private InMemoryTodoRepo _repo; 15 | 16 | [SetUp] 17 | public void Setup() 18 | { 19 | var config = new Configuration 20 | { 21 | BaseUrl = WebApiSetupFixture.BaseAddress 22 | }; 23 | 24 | _client = new HttpClient(config); 25 | _repo = InMemoryTodoRepo.Instance; 26 | } 27 | 28 | [Test] 29 | public void Should_Be_Able_To_Get_Typed_Object() 30 | { 31 | /* Setup */ 32 | var expected = _repo.Get(1); 33 | 34 | /* Test */ 35 | var result = _client.Get("api/todos/1"); 36 | 37 | /* Assert */ 38 | Assert.That(result.Body.Id, Is.EqualTo(expected.Id)); 39 | Assert.That(result.Body.Description, Is.EqualTo(expected.Description)); 40 | Assert.That(result.Body.IsCompleted, Is.EqualTo(expected.IsCompleted)); 41 | } 42 | 43 | [Test] 44 | public async void Should_Be_Able_To_Get_Typed_Object_Async() 45 | { 46 | /* Setup */ 47 | var expected = _repo.Get(1); 48 | 49 | /* Test */ 50 | var result = await _client.GetAsync("api/todos/1"); 51 | 52 | /* Assert */ 53 | Assert.That(result.Body.Id, Is.EqualTo(expected.Id)); 54 | Assert.That(result.Body.Description, Is.EqualTo(expected.Description)); 55 | Assert.That(result.Body.IsCompleted, Is.EqualTo(expected.IsCompleted)); 56 | } 57 | 58 | [Test] 59 | public void Should_Be_Able_To_Get_Raw_Payload() 60 | { 61 | /* Setup */ 62 | 63 | /* Test */ 64 | var result = _client.Get("api/todos/1"); 65 | 66 | /* Assert */ 67 | Assert.That(result.Body, Is.StringStarting("{")); 68 | Assert.That(result.Body, Is.StringContaining("\"Id\":1")); 69 | Assert.That(result.Body, Is.StringEnding("}")); 70 | } 71 | 72 | [Test] 73 | public void Should_Be_Able_To_Affect_Response_With_Query() 74 | { 75 | /* Setup */ 76 | var allTodos = _repo.GetAll(); 77 | 78 | /* Test */ 79 | var result = _client.Get>("api/todos", new {includeCompeted = false}); 80 | 81 | /* Assert */ 82 | Assert.That(allTodos.Any(t => t.IsCompleted), Is.True, "There should at leaste be one competed todo in order to be sure this works"); 83 | Assert.That(result.Body, Is.Not.Empty, "There must be at leaste one todo in the response"); 84 | Assert.That(result.Body.Any(t => t.IsCompleted), Is.False, "All completed todos should be filtered"); 85 | } 86 | 87 | [TestCase("json")] 88 | [TestCase("xml")] 89 | public void Should_Be_Able_To_Parse_Json_And_Xml(string format) 90 | { 91 | /* Setup */ 92 | /* Test */ 93 | /* Assert */ 94 | Assert.DoesNotThrow(() =>_client.Get("api/content-type", new {prefered = format})); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /PainlessHttp.IntegrationTests/Methods/PerformRawTests.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using NUnit.Framework; 3 | using PainlessHttp.Client; 4 | using PainlessHttp.Http; 5 | 6 | namespace PainlessHttp.IntegrationTests.Methods 7 | { 8 | [TestFixture] 9 | public class PerformRawTests 10 | { 11 | private HttpClient _client; 12 | 13 | [SetUp] 14 | public void Setup() 15 | { 16 | _client = new HttpClient(WebApiSetupFixture.BaseAddress); 17 | } 18 | 19 | [Test] 20 | public void Should_Be_Able_To_Get_Objects_Syncronious() 21 | { 22 | /* Setup */ 23 | 24 | /* Test */ 25 | var result = _client.PerformRaw(HttpMethod.Get, "api/todos/1"); 26 | 27 | /* Assert */ 28 | Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.OK)); 29 | /* Don't need to expect on the payload, as this is an previously 30 | * internal method that is being tested in the generic methods. */ 31 | } 32 | 33 | [Test] 34 | public async void Should_Be_Able_To_Get_Objects_Asyncronious() 35 | { 36 | /* Setup */ 37 | 38 | /* Test */ 39 | var result = await _client.PerformRawAsync(HttpMethod.Get, "api/todos/1"); 40 | 41 | /* Assert */ 42 | Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.OK)); 43 | /* Don't need to expect on the payload, as this is an previously 44 | * internal method that is being tested in the generic methods. */ 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /PainlessHttp.IntegrationTests/Methods/PostTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using PainlessHttp.Client; 3 | using PainlessHttp.DevServer.Model; 4 | 5 | namespace PainlessHttp.IntegrationTests.Methods 6 | { 7 | [TestFixture] 8 | public class PostTests 9 | { 10 | private HttpClient _client; 11 | 12 | [SetUp] 13 | public void Setup() 14 | { 15 | var config = new Configuration 16 | { 17 | BaseUrl = WebApiSetupFixture.BaseAddress 18 | }; 19 | 20 | _client = new HttpClient(config); 21 | } 22 | 23 | [Test] 24 | public void Should_Be_Able_To_Post_Object() 25 | { 26 | /* Setup */ 27 | var newTodo = new Todo {Description = "Write tests"}; 28 | 29 | /* Test */ 30 | var created = _client.Post("/api/todos", newTodo); 31 | 32 | /* Assert */ 33 | Assert.That(created.Body.Description, Is.EqualTo(newTodo.Description)); 34 | } 35 | 36 | [Test] 37 | public async void Should_Be_Able_To_Post_Object_Async() 38 | { 39 | /* Setup */ 40 | var newTodo = new Todo { Description = "Write tests" }; 41 | 42 | /* Test */ 43 | var created = await _client.PostAsync("/api/todos", newTodo); 44 | 45 | /* Assert */ 46 | Assert.That(created.Body.Description, Is.EqualTo(newTodo.Description)); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /PainlessHttp.IntegrationTests/Methods/PutTests.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using NUnit.Framework; 3 | using PainlessHttp.Client; 4 | using PainlessHttp.DevServer.Data; 5 | using PainlessHttp.DevServer.Model; 6 | 7 | namespace PainlessHttp.IntegrationTests.Methods 8 | { 9 | [TestFixture] 10 | public class PutTests 11 | { 12 | private HttpClient _client; 13 | private InMemoryTodoRepo _repo; 14 | 15 | [SetUp] 16 | public void Setup() 17 | { 18 | var config = new Configuration 19 | { 20 | BaseUrl = WebApiSetupFixture.BaseAddress 21 | }; 22 | 23 | _client = new HttpClient(config); 24 | _repo = InMemoryTodoRepo.Instance; 25 | } 26 | 27 | [Test] 28 | public void Should_Be_Able_To_Put_Object() 29 | { 30 | /* Setup */ 31 | const string newDescription = "This is the new description"; 32 | var existingTodo = _repo.Get(2); 33 | existingTodo.Description = newDescription; 34 | 35 | /* Test */ 36 | var result = _client.Put("api/todos", existingTodo); 37 | 38 | /* Assert */ 39 | Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NoContent)); 40 | Assert.That(_repo.Get(2).Description, Is.EqualTo(newDescription)); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /PainlessHttp.IntegrationTests/PainlessHttp.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {F8ECB652-C9E0-446B-9A64-B6D920081376} 8 | Library 9 | Properties 10 | PainlessHttp.IntegrationTests 11 | PainlessHttp.IntegrationTests 12 | v4.5.1 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | ..\packages\Microsoft.Owin.2.0.2\lib\net45\Microsoft.Owin.dll 35 | True 36 | 37 | 38 | ..\packages\Microsoft.Owin.Host.HttpListener.2.0.2\lib\net45\Microsoft.Owin.Host.HttpListener.dll 39 | True 40 | 41 | 42 | ..\packages\Microsoft.Owin.Hosting.2.0.2\lib\net45\Microsoft.Owin.Hosting.dll 43 | True 44 | 45 | 46 | False 47 | ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll 48 | 49 | 50 | False 51 | ..\packages\NUnit.2.6.4\lib\nunit.framework.dll 52 | 53 | 54 | ..\packages\Owin.1.0\lib\net40\Owin.dll 55 | 56 | 57 | 58 | 59 | 60 | False 61 | ..\packages\Microsoft.AspNet.WebApi.Client.5.2.2\lib\net45\System.Net.Http.Formatting.dll 62 | 63 | 64 | False 65 | ..\packages\Microsoft.AspNet.WebApi.Core.5.2.2\lib\net45\System.Web.Http.dll 66 | 67 | 68 | ..\packages\Microsoft.AspNet.WebApi.Owin.5.2.2\lib\net45\System.Web.Http.Owin.dll 69 | True 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | {c7256164-4d19-40be-9ca6-42c3cb8eb164} 97 | PainlessHttp.DevServer 98 | 99 | 100 | {1719f085-34f2-48b6-ad2e-98b67856b3b0} 101 | PainlessHttp 102 | 103 | 104 | 105 | 112 | -------------------------------------------------------------------------------- /PainlessHttp.IntegrationTests/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("PainlessHttp.IntegrationTests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("PainlessHttp.IntegrationTests")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 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("044af561-5158-4470-9c56-de9eebe1c7d0")] 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 | -------------------------------------------------------------------------------- /PainlessHttp.IntegrationTests/WebApiSetupFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | using Microsoft.Owin.Hosting; 4 | using PainlessHttp.DevServer; 5 | 6 | namespace PainlessHttp.IntegrationTests 7 | { 8 | [SetUpFixture] 9 | public class WebApiSetupFixture 10 | { 11 | private IDisposable _app; 12 | public static string BaseAddress; 13 | 14 | [SetUp] 15 | public void WireUpWebApiEndpoint() 16 | { 17 | var randomPort = new Random().Next(49152, 65535); 18 | BaseAddress = string.Format("http://localhost:{0}/", randomPort); 19 | Console.Write("Development Server running at " + BaseAddress); 20 | _app = WebApp.Start(BaseAddress); 21 | } 22 | 23 | [TearDown] 24 | public void DisposeTheApiEndpoinnt() 25 | { 26 | if (_app != null) 27 | { 28 | _app.Dispose(); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /PainlessHttp.IntegrationTests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /PainlessHttp.Nuget/PainlessHttp.Nuget.nuproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 8 | 9 | Net45 10 | AnyCPU 11 | 12 | 13 | Release 14 | AnyCPU 15 | 16 | 17 | 18 | c7679876-b664-4f23-9671-12785dd1a523 19 | 20 | 21 | $(MSBuildExtensionsPath)\NuProj\ 22 | 23 | 24 | 25 | PainlessHttp 26 | 0.11.6 27 | PainlessHttp 28 | par.dahlman 29 | par.dahlman 30 | A dependency-free http client for async requests with typed responses, simple configuration and much, much more. For more information and source code, go to the projects homepage 31 | PainlessHttp is a really small library for performing http operations in an easy way. Configuration has never been easier, no manual needed. 32 | PainlessHttp $(Version) 33 | http://painless.pardahlman.se 34 | 35 | 36 | Copyright © par.dahlman 37 | PainlessHttp Http 38 | false 39 | http://painless.pardahlman.se/icon.png 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /PainlessHttp.Sandbox/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /PainlessHttp.Sandbox/PainlessHttp.Sandbox.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {E383E517-2F67-40E8-B9F3-A82473A5F006} 8 | Exe 9 | Properties 10 | PainlessHttp.Sandbox 11 | PainlessHttp.Sandbox 12 | v4.5.1 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | False 38 | ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | {C7256164-4D19-40BE-9CA6-42C3CB8EB164} 59 | PainlessHttp.DevServer 60 | 61 | 62 | {368F72B9-86DB-470B-BC76-2854E0CE7BF5} 63 | PainlessHttp.Serializer.JsonNet 64 | 65 | 66 | {1719F085-34F2-48B6-AD2E-98B67856B3B0} 67 | PainlessHttp 68 | 69 | 70 | 71 | 78 | -------------------------------------------------------------------------------- /PainlessHttp.Sandbox/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using Newtonsoft.Json; 6 | using Newtonsoft.Json.Serialization; 7 | using PainlessHttp.Client; 8 | using PainlessHttp.DevServer.Model; 9 | using PainlessHttp.Http; 10 | using PainlessHttp.Serializer.JsonNet; 11 | using PainlessHttp.Serializers.Contracts; 12 | using PainlessHttp.Serializers.Custom; 13 | using PainlessHttp.Serializers.Defaults; 14 | using PainlessHttp.Serializers.Typed; 15 | 16 | namespace PainlessHttp.Sandbox 17 | { 18 | class Program 19 | { 20 | static void Main(string[] args) 21 | { 22 | var config = new Configuration 23 | { 24 | BaseUrl = "http://localhost:1337/", 25 | Advanced = 26 | { 27 | Serializers = new List 28 | { 29 | new Serializer(ContentType.ApplicationJson), 30 | new Serializer(ContentType.ApplicationXml) 31 | } 32 | } 33 | }; 34 | 35 | var tomorrow = new Todo { Description = "Sleep in" }; 36 | 37 | var xmlSerializer = new Serializer(ContentType.ApplicationXml); 38 | var todoXml = xmlSerializer.Serialize(tomorrow); 39 | var xmlBack = xmlSerializer.Deserialize(todoXml); 40 | 41 | // Create a custom serializer 42 | var custom = SerializerBulider 43 | .For(ContentType.ApplicationJson) 44 | .Serialize(NewtonSoft.Serialize) 45 | .Deserialize(DefaultJson.Deserialize); 46 | 47 | var customJson = custom.Serialize(tomorrow); 48 | var customObj = custom.Deserialize(customJson); 49 | 50 | // Configure JsonNet however you want 51 | var newtonSoftSettings = new JsonSerializerSettings 52 | { 53 | ContractResolver = new CamelCasePropertyNamesContractResolver() 54 | }; 55 | 56 | //Register the change 57 | NewtonSoft.UpdateSettings(new NewtonsoftSettings {Settings = newtonSoftSettings}); 58 | 59 | // Go crazy, oh baby! 60 | var serializer = new Serializer(ContentType.ApplicationJson); 61 | var tomorrowJson = serializer.Serialize(tomorrow); 62 | var tommorowObj = serializer.Deserialize(tomorrowJson); 63 | 64 | var client = new HttpClient(config); 65 | var created = client.PostAsync("api/todos", tomorrow).Result; 66 | } 67 | } 68 | 69 | 70 | } 71 | -------------------------------------------------------------------------------- /PainlessHttp.Sandbox/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("PainlessHttp.Sandbox")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("PainlessHttp.Sandbox")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 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("8ae1acf6-9709-47ff-b9a6-72ef173e3b06")] 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 | -------------------------------------------------------------------------------- /PainlessHttp.Sandbox/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /PainlessHttp.Serializer.JsonNet.Tests/PainlessHttp.Serializer.JsonNet.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {681410D1-AA24-4706-B95C-EE03ACA9CDA6} 8 | Library 9 | Properties 10 | PainlessHttp.Serializer.JsonNet.Tests 11 | PainlessHttp.Serializer.JsonNet.Tests 12 | v4.5.1 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | False 35 | ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll 36 | 37 | 38 | False 39 | ..\packages\NUnit.2.6.4\lib\nunit.framework.dll 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | {C7256164-4D19-40BE-9CA6-42C3CB8EB164} 59 | PainlessHttp.DevServer 60 | 61 | 62 | {368f72b9-86db-470b-bc76-2854e0ce7bf5} 63 | PainlessHttp.Serializer.JsonNet 64 | 65 | 66 | {1719f085-34f2-48b6-ad2e-98b67856b3b0} 67 | PainlessHttp 68 | 69 | 70 | 71 | 78 | -------------------------------------------------------------------------------- /PainlessHttp.Serializer.JsonNet.Tests/PainlessJsonNetTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | using NUnit.Framework; 4 | using PainlessHttp.DevServer.Model; 5 | 6 | namespace PainlessHttp.Serializer.JsonNet.Tests 7 | { 8 | [TestFixture] 9 | public class PainlessJsonNetTests 10 | { 11 | private string _payloadAsString; 12 | private Todo _payload; 13 | 14 | [SetUp] 15 | public void Setup() 16 | { 17 | _payload = new Todo 18 | { 19 | Description = "Write a new JsonNet serialzer", 20 | Id = 1, 21 | IsCompleted = true, 22 | UpdateDate = new DateTime(2015,03,15) 23 | }; 24 | 25 | _payloadAsString = JsonConvert.SerializeObject(_payload); 26 | } 27 | 28 | [Test] 29 | public void Should_Serialize_Correctly_With_Default_Settings() 30 | { 31 | /* Setup */ 32 | var serializer = new PainlessJsonNet(); 33 | 34 | /* Test */ 35 | var result = serializer.Serialize(_payload); 36 | 37 | /* Assert */ 38 | Assert.That(result, Contains.Substring("{\"Id\":1,\"Description\":\"Write a new JsonNet serialzer\",\"IsCompleted\":true,\"UpdateDate\":\"2015-03-15T00:00:00\"}")); 39 | } 40 | 41 | [Test] 42 | public void Should_Deserialize_Correctly_With_Default_Settings() 43 | { 44 | /* Setup */ 45 | var serializer = new PainlessJsonNet(); 46 | 47 | /* Test */ 48 | var result = serializer.Deserialize(_payloadAsString); 49 | 50 | /* Assert */ 51 | Assert.That(result.Description, Is.EqualTo(_payload.Description)); 52 | Assert.That(result.Id, Is.EqualTo(_payload.Id)); 53 | Assert.That(result.IsCompleted, Is.EqualTo(_payload.IsCompleted)); 54 | } 55 | 56 | [Test] 57 | public void Should_Use_Overrides_If_Supplied() 58 | { 59 | /* Setup */ 60 | var deserializedCalled = false; 61 | var serializedCalled = false; 62 | Func serialize = o => 63 | { 64 | serializedCalled = true; 65 | return string.Empty; 66 | }; 67 | Func deserialize = (s, type) => 68 | { 69 | deserializedCalled = true; 70 | return null; 71 | }; 72 | var serializer = new PainlessJsonNet(serialize, deserialize); 73 | 74 | /* Test */ 75 | serializer.Serialize(_payload); 76 | serializer.Deserialize(_payloadAsString); 77 | 78 | /* Assert */ 79 | Assert.That(deserializedCalled, Is.True); 80 | Assert.That(serializedCalled, Is.True); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /PainlessHttp.Serializer.JsonNet.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("PainlessHttp.Serializer.JsonNet.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("PainlessHttp.Serializer.JsonNet.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 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("8557baed-7e6e-428c-ae34-5864c94d554e")] 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 | -------------------------------------------------------------------------------- /PainlessHttp.Serializer.JsonNet.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /PainlessHttp.Serializer.JsonNet/NewtonSoft.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace PainlessHttp.Serializer.JsonNet 5 | { 6 | public class NewtonSoft 7 | { 8 | private static NewtonsoftSettings _settings = new NewtonsoftSettings(); 9 | 10 | public static object Deserialize(string data, Type type) 11 | { 12 | if (_settings.Settings != null) 13 | { 14 | return JsonConvert.DeserializeObject(data, type, _settings.Settings); 15 | } 16 | if(_settings.JsonConverters != null) 17 | { 18 | return JsonConvert.DeserializeObject(data, type, _settings.JsonConverters); 19 | } 20 | 21 | return JsonConvert.DeserializeObject(data, type); 22 | } 23 | 24 | public static string Serialize(object data) 25 | { 26 | if (_settings.Settings != null) 27 | { 28 | return JsonConvert.SerializeObject(data, _settings.Settings); 29 | } 30 | if (_settings.JsonConverters != null) 31 | { 32 | return JsonConvert.SerializeObject(data, _settings.Formatting, _settings.JsonConverters); 33 | } 34 | 35 | return JsonConvert.SerializeObject(data, _settings.Formatting); 36 | } 37 | 38 | 39 | public static void UpdateSettings(NewtonsoftSettings settings) 40 | { 41 | _settings = settings; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /PainlessHttp.Serializer.JsonNet/NewtonsoftSettings.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace PainlessHttp.Serializer.JsonNet 4 | { 5 | public class NewtonsoftSettings 6 | { 7 | public Formatting Formatting { get; set; } 8 | public JsonSerializerSettings Settings { get; set; } 9 | public JsonConverter[] JsonConverters { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /PainlessHttp.Serializer.JsonNet/PainlessHttp.Serializer.JsonNet.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {368F72B9-86DB-470B-BC76-2854E0CE7BF5} 8 | Library 9 | Properties 10 | PainlessHttp.Serializer.JsonNet 11 | PainlessHttp.Serializer.JsonNet 12 | v4.5.1 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | False 35 | ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | {1719f085-34f2-48b6-ad2e-98b67856b3b0} 51 | PainlessHttp 52 | 53 | 54 | 55 | 62 | -------------------------------------------------------------------------------- /PainlessHttp.Serializer.JsonNet/PainlessJsonNet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Newtonsoft.Json; 4 | using PainlessHttp.Http; 5 | using PainlessHttp.Serializers.Contracts; 6 | 7 | namespace PainlessHttp.Serializer.JsonNet 8 | { 9 | public class PainlessJsonNet : IContentSerializer 10 | { 11 | private readonly Func _serialize; 12 | private readonly Func _deserialize; 13 | 14 | public PainlessJsonNet(Func serialize = null, Func deserialize = null ) 15 | { 16 | _serialize = serialize ?? JsonConvert.SerializeObject; 17 | _deserialize = deserialize ?? JsonConvert.DeserializeObject; 18 | ContentType = new List {Http.ContentType.ApplicationJson}; 19 | } 20 | public IEnumerable ContentType { get; private set; } 21 | 22 | public string Serialize(object data) 23 | { 24 | return _serialize(data); 25 | } 26 | 27 | public T Deserialize(string data) 28 | { 29 | var result = _deserialize(data, typeof (T)); 30 | return (T)result; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /PainlessHttp.Serializer.JsonNet/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("PainlessHttp.ContentSerializer.NewtonSoft")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("PainlessHttp.ContentSerializer.NewtonSoft")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 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("a5f1530b-a889-4b84-9c91-6197e44318b2")] 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("0.10.1.0")] 36 | [assembly: AssemblyFileVersion("0.10.1.0")] 37 | -------------------------------------------------------------------------------- /PainlessHttp.Serializer.JsonNet/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /PainlessHttp.Tests/PainlessHttp.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {3BCFB007-09A5-4FBD-9382-E2A5E364F970} 8 | Library 9 | Properties 10 | PainlessHttp.Tests 11 | PainlessHttp.Tests 12 | v4.5.1 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | ..\packages\Moq.4.2.1409.1722\lib\net40\Moq.dll 35 | 36 | 37 | False 38 | ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll 39 | 40 | 41 | False 42 | ..\packages\NUnit.2.6.4\lib\nunit.framework.dll 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | {1719F085-34F2-48B6-AD2E-98B67856B3B0} 73 | PainlessHttp 74 | 75 | 76 | 77 | 84 | -------------------------------------------------------------------------------- /PainlessHttp.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("PainlessHttp.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("PainlessHttp.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 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("614faeb7-3a8e-44d2-898a-7d39fd2ab76f")] 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 | -------------------------------------------------------------------------------- /PainlessHttp.Tests/Serializers/Custom/SerializeSettingsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | using PainlessHttp.Http; 4 | using PainlessHttp.Serializers.Custom; 5 | 6 | namespace PainlessHttp.Tests.Serializers.Custom 7 | { 8 | [TestFixture] 9 | public class SerializeSettingsTests 10 | { 11 | [Test] 12 | public void Should_Set_Correct_Content_Types() 13 | { 14 | /* Setup */ 15 | var expected = new[] {ContentType.ApplicationJson, ContentType.TextCsv}; 16 | 17 | /* Test */ 18 | var result = SerializerBulider 19 | .For(expected) 20 | .Serialize(o => string.Empty) 21 | .Deserialize((s, t) => new object()); 22 | 23 | /* Assert */ 24 | Assert.That(result.ContentType, Is.EqualTo(expected)); 25 | } 26 | 27 | [Test] 28 | public void Should_Call_Provided_Serialize_Func_Upon_Serialize() 29 | { 30 | /* Setup */ 31 | var serializedCalled = false; 32 | var serializer = SerializerBulider 33 | .For(ContentType.ApplicationJson) 34 | .Serialize(o => 35 | { 36 | serializedCalled = true; 37 | return string.Empty; 38 | }) 39 | .Deserialize((s, t) => new object()); 40 | 41 | /* Test */ 42 | serializer.Serialize(new object()); 43 | 44 | /* Assert */ 45 | Assert.That(serializedCalled, Is.True); 46 | } 47 | 48 | [Test] 49 | public void Should_Call_Provided_Deserialize_Func_Upon_Deserialization() 50 | { 51 | /* Setup */ 52 | var deserializedCalled = false; 53 | var serializer = SerializerBulider 54 | .For(ContentType.ApplicationJson) 55 | .Serialize(o => string.Empty) 56 | .Deserialize((s, t) => 57 | { 58 | deserializedCalled = true; 59 | return new object(); 60 | }); 61 | 62 | /* Test */ 63 | serializer.Deserialize(string.Empty); 64 | 65 | /* Assert */ 66 | Assert.That(deserializedCalled, Is.True); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /PainlessHttp.Tests/Serializers/Custom/SerializerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | using PainlessHttp.Http; 4 | using PainlessHttp.Serializers.Custom; 5 | 6 | namespace PainlessHttp.Tests.Serializers.Custom 7 | { 8 | [TestFixture] 9 | public class SerializerTests 10 | { 11 | [Test] 12 | public void Should_Throw_Exception_If_Generic_Type_Does_Not_Have_Serialize_Method() 13 | { 14 | /* Test, Setup & Assert */ 15 | Assert.Throws(() => new Serializer(ContentType.Unknown)); 16 | } 17 | 18 | [Test] 19 | public void Should_Throw_Exception_If_Generic_Type_Does_Not_Have_Deserialize_Method() 20 | { 21 | /* Test, Setup & Assert */ 22 | Assert.Throws(() => new Serializer(ContentType.Unknown)); 23 | } 24 | 25 | [Test] 26 | public void Should_Throw_Exception_If_Generic_Type_Has_Correct_Methods() 27 | { 28 | /* Test, Setup & Assert */ 29 | Assert.DoesNotThrow(() => new Serializer(ContentType.Unknown)); 30 | } 31 | 32 | [Test] 33 | public void Should_Call_Correct_Methods_On_Serialize() 34 | { 35 | /* Setup */ 36 | var serializedCalled = false; 37 | 38 | CorrectMethods.MockMethods( 39 | serializeMethod => 40 | { 41 | serializedCalled = true; 42 | return string.Empty; 43 | }, 44 | null 45 | ); 46 | 47 | var serializer = new Serializer(ContentType.Unknown); 48 | 49 | /* Test */ 50 | serializer.Serialize(string.Empty); 51 | 52 | /* Assert */ 53 | Assert.That(serializedCalled, Is.True); 54 | } 55 | 56 | [Test] 57 | public void Should_Call_Correct_Methods_On_Deserialize() 58 | { 59 | /* Setup */ 60 | var deserializedCalled = false; 61 | 62 | CorrectMethods.MockMethods( 63 | null, 64 | (deserializeMethod, type) => 65 | { 66 | deserializedCalled = true; 67 | return new object(); 68 | } 69 | ); 70 | 71 | var serializer = new Serializer(ContentType.Unknown); 72 | 73 | /* Test */ 74 | serializer.Deserialize(string.Empty); 75 | 76 | /* Assert */ 77 | Assert.That(deserializedCalled, Is.True); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /PainlessHttp.Tests/Serializers/SerializerTestClasses.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using Moq; 6 | using NUnit.Framework; 7 | 8 | namespace PainlessHttp.Tests.Serializers 9 | { 10 | public class SerializerTestClass 11 | { 12 | public string StringProp { get; set; } 13 | } 14 | 15 | public class SerializerDateTimeClass 16 | { 17 | public DateTime Time { get; set; } 18 | } 19 | 20 | public class AdvancedTestClass 21 | { 22 | public int IntProp { get; set; } 23 | public List ListProp { get; set; } 24 | public SerializerTestClass ObjectRef { get; set; } 25 | } 26 | 27 | /// 28 | /// Test class that has a Deserialize method, but no serialize method. 29 | /// This class is invalid as a Serializer generic argument. 30 | /// 31 | public class NoSerializeMethod 32 | { 33 | public static object Deserialize(string data, Type type) 34 | { 35 | return new object(); 36 | } 37 | } 38 | 39 | /// 40 | /// Test class that has a Deserialize method, but no serialize method. 41 | /// This class is invalid as a Serializer generic argument. 42 | /// 43 | public class NoDeserializeMethod 44 | { 45 | public static string Serialize(object data) 46 | { 47 | return string.Empty; 48 | } 49 | } 50 | 51 | /// 52 | /// Test class that has the correct method signature, and 53 | /// therefore is accepted as generic parameter to Serializer. 54 | /// 55 | public class CorrectMethods 56 | { 57 | private static Func _serialize; 58 | private static Func _deserialize; 59 | 60 | public static void MockMethods(Func mockSerialize, Func mockDeserialize) 61 | { 62 | _serialize = mockSerialize; 63 | _deserialize = mockDeserialize; 64 | } 65 | public static string Serialize(object data) 66 | { 67 | if (_serialize == null) 68 | { 69 | return string.Empty; 70 | } 71 | return _serialize(data); 72 | } 73 | 74 | public static object Deserialize(string data, Type type) 75 | { 76 | if (_deserialize == null) 77 | { 78 | return new object(); 79 | } 80 | return _deserialize(data, type); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /PainlessHttp.Tests/Serializers/Typed/DefaultJsonSerializerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization.Json; 4 | using Moq; 5 | using Newtonsoft.Json; 6 | using NUnit.Framework; 7 | using PainlessHttp.Serializers.Typed; 8 | 9 | namespace PainlessHttp.Tests.Serializers.Typed 10 | { 11 | [TestFixture] 12 | public class DefaultJsonSerializerTests 13 | { 14 | private DefaultJsonSerializer _serializer; 15 | private Mock> _cachedSerializers; 16 | 17 | [SetUp] 18 | public void Setup() 19 | { 20 | _cachedSerializers = new Mock>(); 21 | _serializer = new DefaultJsonSerializer(_cachedSerializers.Object); 22 | } 23 | 24 | [Test] 25 | public void Should_Save_Serializer_If_Type_Not_Allready_In_Cache() 26 | { 27 | /* Setup */ 28 | var obj = new SerializerTestClass { StringProp = Guid.NewGuid().ToString() }; 29 | _cachedSerializers 30 | .Setup(c => c.Add( 31 | It.Is(type => type == typeof(SerializerTestClass)), 32 | It.IsAny())) 33 | .Verifiable(); 34 | 35 | /* Test */ 36 | _serializer.Serialize(obj); 37 | 38 | /* Assert */ 39 | _cachedSerializers.VerifyAll(); 40 | } 41 | 42 | [Test] 43 | public void Should_Look_For_Existing_Serializer_Before_Creating_New() 44 | { 45 | /* Setup */ 46 | var obj = new SerializerTestClass { StringProp = Guid.NewGuid().ToString() }; 47 | DataContractJsonSerializer serializer = null; 48 | _cachedSerializers 49 | .Setup(c => c.TryGetValue( 50 | It.Is(type => type == typeof(SerializerTestClass)), 51 | out serializer)) 52 | .Verifiable(); 53 | 54 | /* Test */ 55 | _serializer.Serialize(obj); 56 | 57 | /* Assert */ 58 | _cachedSerializers.VerifyAll(); 59 | } 60 | 61 | [Test] 62 | public void Should_Be_Able_To_Serialize_Advanced_Objects() 63 | { 64 | /* Setup */ 65 | var advanced = new AdvancedTestClass 66 | { 67 | IntProp = 3, 68 | ListProp = new List { "One", "Two"}, 69 | ObjectRef = new SerializerTestClass 70 | { 71 | StringProp = "Sub-object property" 72 | } 73 | }; 74 | 75 | /* Test */ 76 | var result = _serializer.Serialize(advanced); 77 | 78 | /* Assert */ 79 | Assert.That(result, Is.StringStarting("{"), "Correct json object opening"); 80 | Assert.That(result, Is.StringEnding("}"), "Correct json closing"); 81 | Assert.That(result, Is.StringContaining("\"IntProp\":3")); 82 | Assert.That(result, Is.StringContaining("\"ObjectRef\":{\"StringProp\":\"Sub-object property\"}")); 83 | Assert.That(result, Is.StringContaining("\"ListProp\":[\"One\",\"Two\"]")); 84 | } 85 | 86 | [Test] 87 | public void Should_Be_Able_To_Deserialize_Objects() 88 | { 89 | /* Setup */ 90 | var expected = new AdvancedTestClass 91 | { 92 | IntProp = 3, 93 | ListProp = new List { "One", "Two" }, 94 | ObjectRef = new SerializerTestClass 95 | { 96 | StringProp = "Sub-object property" 97 | } 98 | }; 99 | var input = JsonConvert.SerializeObject(expected); 100 | 101 | /* Test */ 102 | var result = _serializer.Deserialize(input); 103 | 104 | /* Assert */ 105 | Assert.That(result.IntProp, Is.EqualTo(expected.IntProp)); 106 | Assert.That(result.ObjectRef.StringProp, Is.EqualTo(expected.ObjectRef.StringProp)); 107 | Assert.That(result.ListProp[0], Is.EqualTo(expected.ListProp[0])); 108 | Assert.That(result.ListProp[1], Is.EqualTo(expected.ListProp[1])); 109 | } 110 | 111 | [TestCase("2015-03-15T12:00:00+01:00")] 112 | [TestCase("2015-03-15T10:00:00-01:00")] 113 | [TestCase("2015-03-15T12:00:00")] 114 | [TestCase("2015-03-15T12:00:00.7206435+01:00")] 115 | [TestCase("2015-03-15T12:00:00.720643+01:00")] 116 | [TestCase("2015-03-15T12:00:00.72064+01:00")] 117 | [TestCase("2015-03-15T12:00:00.7206+01:00")] 118 | [TestCase("2015-03-15T12:00:00.720+01:00")] 119 | [TestCase("2015-03-15T12:00:00.72+01:00")] 120 | [TestCase("2015-03-15T12:00:00.7+01:00")] 121 | public void Should_Be_Able_To_Deserialize_Universal_Sortable_DateTime_Properties(string dateTimeString) 122 | { 123 | /* Setup */ 124 | var expected = new DateTime(2015, 03, 15, 12, 0, 0); 125 | 126 | /* Test */ 127 | var deserialized = _serializer.Deserialize(dateTimeString); 128 | 129 | /* Assert */ 130 | Assert.That(deserialized, Is.EqualTo(expected)); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /PainlessHttp.Tests/Serializers/Typed/DefaultXmlSerializerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Xml.Serialization; 4 | using Moq; 5 | using NUnit.Framework; 6 | using PainlessHttp.Serializers.Typed; 7 | 8 | namespace PainlessHttp.Tests.Serializers.Typed 9 | { 10 | [TestFixture] 11 | public class DefaultXmlSerializerTests 12 | { 13 | private DefaultXmlSerializer _serializer; 14 | private Mock> _cachedSerializers; 15 | 16 | [SetUp] 17 | public void Setup() 18 | { 19 | _cachedSerializers = new Mock>(); 20 | _serializer = new DefaultXmlSerializer(_cachedSerializers.Object); 21 | } 22 | [Test] 23 | public void Should_Save_Serializer_If_Type_Not_Allready_In_Cache() 24 | { 25 | /* Setup */ 26 | var obj = new SerializerTestClass { StringProp = Guid.NewGuid().ToString() }; 27 | _cachedSerializers 28 | .Setup(c => c.Add( 29 | It.Is(type => type == typeof(SerializerTestClass)), 30 | It.IsAny())) 31 | .Verifiable(); 32 | 33 | /* Test */ 34 | _serializer.Serialize(obj); 35 | 36 | /* Assert */ 37 | _cachedSerializers.VerifyAll(); 38 | } 39 | 40 | [Test] 41 | public void Should_Look_For_Existing_Serializer_Before_Creating_New() 42 | { 43 | /* Setup */ 44 | var obj = new SerializerTestClass { StringProp = Guid.NewGuid().ToString() }; 45 | XmlSerializer serializer = null; 46 | _cachedSerializers 47 | .Setup(c => c.TryGetValue( 48 | It.Is(type => type == typeof(SerializerTestClass)), 49 | out serializer)) 50 | .Verifiable(); 51 | 52 | /* Test */ 53 | _serializer.Serialize(obj); 54 | 55 | /* Assert */ 56 | _cachedSerializers.VerifyAll(); 57 | } 58 | 59 | [Test] 60 | public void Should_Be_Able_To_Serialize_Advanced_Objects() 61 | { 62 | /* Setup */ 63 | var advanced = new AdvancedTestClass 64 | { 65 | IntProp = 3, 66 | ListProp = new List { "One", "Two" }, 67 | ObjectRef = new SerializerTestClass 68 | { 69 | StringProp = "Sub-object property" 70 | } 71 | }; 72 | 73 | /* Test */ 74 | var result = _serializer.Serialize(advanced); 75 | 76 | /* Assert */ 77 | Assert.That(result, Is.StringStarting(""), "Correct xml object opening"); 78 | Assert.That(result, Is.StringContaining("3")); 79 | } 80 | 81 | [Test] 82 | public void Should_Be_Able_To_Deserialize_Objects() 83 | { 84 | /* Setup */ 85 | const string input = ""+ 86 | ""+ 87 | "Hello, world"+ 88 | ""; 89 | /* Test */ 90 | var result = _serializer.Deserialize(input); 91 | 92 | /* Assert */ 93 | Assert.That(result.StringProp, Is.EqualTo("Hello, world")); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /PainlessHttp.Tests/Utils/AcceptHeaderMapperTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Runtime.InteropServices; 3 | using System.Runtime.InteropServices.ComTypes; 4 | using NUnit.Framework; 5 | using PainlessHttp.Utils; 6 | 7 | namespace PainlessHttp.Tests.Utils 8 | { 9 | [TestFixture] 10 | public class AcceptHeaderMapperTests 11 | { 12 | private AcceptHeaderMapper _mapper; 13 | 14 | [SetUp] 15 | public void Setup() 16 | { 17 | _mapper = new AcceptHeaderMapper(); 18 | } 19 | 20 | [TestCase("")] 21 | [TestCase(null)] 22 | public void Should_Return_Empty_List_For_Null_Or_Empty(string header) 23 | { 24 | /* Setup */ 25 | /* Test */ 26 | var result = _mapper.Map(header).ToList(); 27 | 28 | /* Assert */ 29 | Assert.That(result, Is.Empty); 30 | } 31 | 32 | [TestCase("application/json; q=.8; mxs=5; mxb=100000")] 33 | [TestCase("application/json; Q=.8; mXs=5; mxB=100000")] 34 | [TestCase(" application/json ; q = .8; mxs = 5; mxb = 100000 ")] 35 | public void Should_Handle_Casing_And_Spacing(string header) 36 | { 37 | /* Setup */ 38 | /* Test */ 39 | var result = _mapper.Map(header).First(); 40 | 41 | /* Assert */ 42 | Assert.That(result.ContentType, Is.EqualTo("application/json").IgnoreCase); 43 | Assert.That(result.Q, Is.EqualTo(0.8f)); 44 | Assert.That(result.Mxs, Is.EqualTo(5)); 45 | Assert.That(result.Mxb, Is.EqualTo(100000)); 46 | } 47 | 48 | [TestCase("text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c", 4)] 49 | [TestCase("text/plain; q=0.5, text/html", 2)] 50 | [TestCase("text/plain; q=0.5", 1)] 51 | [TestCase("", 0)] 52 | public void Should_Handle_Several_Different_Accept_Types(string header, int expectedCount) 53 | { 54 | /* Setup */ 55 | /* Test */ 56 | var result = _mapper.Map(header).ToList(); 57 | 58 | /* Assert */ 59 | Assert.That(result, Has.Count.EqualTo(expectedCount)); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /PainlessHttp.Tests/Utils/ClientUtilsTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using PainlessHttp.Utils; 3 | 4 | namespace PainlessHttp.Tests.Utils 5 | { 6 | [TestFixture] 7 | public class ClientUtilsTests 8 | { 9 | public class When_Calling_GetUserAgent 10 | { 11 | [Test] 12 | public void ShouldReturnExpectedAgentName() 13 | { 14 | /* Setup */ 15 | const string version = "0.11.6.0"; 16 | var expected = string.Format("Painless Http Client {0}", version); 17 | /* Test */ 18 | var agent = ClientUtils.GetUserAgent(); 19 | 20 | /* Assert */ 21 | Assert.That(agent, Is.EqualTo(expected)); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /PainlessHttp.Tests/Utils/ContentNegotiatorTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net; 3 | using NUnit.Framework; 4 | using PainlessHttp.Http; 5 | using PainlessHttp.Utils; 6 | using HttpWebResponse = PainlessHttp.Http.HttpWebResponse; 7 | 8 | namespace PainlessHttp.Tests.Utils 9 | { 10 | [TestFixture] 11 | public class ContentNegotiatorTests 12 | { 13 | private IEnumerable _supported; 14 | private ContentNegotiator _negotiator; 15 | private HttpWebResponse _response; 16 | 17 | [SetUp] 18 | public void Setup() 19 | { 20 | _supported = new List {ContentType.ApplicationXml, ContentType.ApplicationJson}; 21 | _negotiator = new ContentNegotiator(_supported); 22 | _response = new HttpWebResponse 23 | { 24 | Headers = new WebHeaderCollection 25 | { 26 | {HttpRequestHeader.Accept, "application/json"} 27 | }, 28 | StatusCode = HttpStatusCode.UnsupportedMediaType 29 | }; 30 | } 31 | 32 | public class When_Calling_IsApplicable : ContentNegotiatorTests 33 | { 34 | [Test] 35 | public void Should_Be_False_If_Response_Is_Null() 36 | { 37 | /* Setup */ 38 | /* Test */ 39 | var result = _negotiator.IsApplicable(null); 40 | 41 | /* Assert */ 42 | Assert.That(result, Is.False); 43 | } 44 | 45 | [Test] 46 | public void Should_Be_True_If_Response_Has_Unsupported_Media_Type_And_Accept_Header_With_Supported_Type() 47 | { 48 | /* Setup */ 49 | //above 50 | 51 | /* Test */ 52 | var result = _negotiator.IsApplicable(_response); 53 | 54 | /* Assert */ 55 | Assert.That(result, Is.True); 56 | } 57 | 58 | [Test] 59 | public void Should_Be_False_If_No_Accept_Headers_In_Response() 60 | { 61 | /* Setup */ 62 | _response.Headers = new WebHeaderCollection(); 63 | 64 | /* Test */ 65 | var result = _negotiator.IsApplicable(_response); 66 | 67 | /* Assert */ 68 | Assert.That(result, Is.False); 69 | } 70 | 71 | [TestCase(HttpStatusCode.NoContent)] 72 | [TestCase(HttpStatusCode.Unauthorized)] 73 | [TestCase(HttpStatusCode.Forbidden)] 74 | public void Should_Be_False_If_HttpStatusCode_Is_Not_UnsupportedMediaType(HttpStatusCode code) 75 | { 76 | /* Setup */ 77 | _response.StatusCode = code; 78 | 79 | /* Test */ 80 | var result = _negotiator.IsApplicable(_response); 81 | 82 | /* Assert */ 83 | Assert.That(result, Is.False); 84 | } 85 | 86 | [Test] 87 | public void Should_Be_False_If_No_Content_Type_Is_Supported() 88 | { 89 | /* Setup */ 90 | _response.Headers = new WebHeaderCollection 91 | { 92 | {HttpRequestHeader.Accept, "application/unsupported"} 93 | }; 94 | 95 | /* Test */ 96 | var result = _negotiator.IsApplicable(_response); 97 | 98 | /* Assert */ 99 | Assert.That(result, Is.False); 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /PainlessHttp.Tests/Utils/HttpConverterTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | using PainlessHttp.Http; 4 | using PainlessHttp.Utils; 5 | 6 | namespace PainlessHttp.Tests.Utils 7 | { 8 | public class HttpConverterTests 9 | { 10 | [Test] 11 | public void ShouldThrowExceptionIfContentTypeIsUnknown() 12 | { 13 | /* Setup */ 14 | var type = ContentType.Unknown; 15 | 16 | /* Test & Assert */ 17 | Assert.Throws(() => HttpConverter.ContentType(type)); 18 | } 19 | 20 | [Test] 21 | public void ShouldThrowExceptionIfStringDoesNotMatchContentType() 22 | { 23 | /* Setup */ 24 | const string nonMatch = "This is not a content type"; 25 | 26 | /* Test & Assert */ 27 | Assert.Throws(() => HttpConverter.ContentType(nonMatch)); 28 | } 29 | 30 | [TestCase(ContentType.ApplicationJson, ContentTypes.ApplicationJson)] 31 | [TestCase(ContentType.ApplicationXml, ContentTypes.ApplicationXml)] 32 | [TestCase(ContentType.TextPlain, ContentTypes.TextPlain)] 33 | [TestCase(ContentType.TextCsv, ContentTypes.TextCsv)] 34 | [TestCase(ContentType.TextHtml, ContentTypes.TextHtml)] 35 | public void ShouldConvertContentTypeEnumsToStrings(ContentType type, string expectedResult) 36 | { 37 | /* Setup */ 38 | /* Test */ 39 | var result = HttpConverter.ContentType(type); 40 | 41 | /* Assert */ 42 | Assert.That(result, Is.EqualTo(expectedResult)); 43 | } 44 | 45 | [TestCase(ContentTypes.ApplicationJson, ContentType.ApplicationJson)] 46 | [TestCase(ContentTypes.ApplicationXml, ContentType.ApplicationXml)] 47 | [TestCase(ContentTypes.TextPlain, ContentType.TextPlain)] 48 | [TestCase(ContentTypes.TextCsv, ContentType.TextCsv)] 49 | [TestCase(ContentTypes.TextHtml, ContentType.TextHtml)] 50 | public void ShouldConvertContentTypeStringToEnum(string type, ContentType expectedResult) 51 | { 52 | /* Setup */ 53 | /* Test */ 54 | var result = HttpConverter.ContentType(type); 55 | 56 | /* Assert */ 57 | Assert.That(result, Is.EqualTo(expectedResult)); 58 | } 59 | 60 | [TestCase(HttpMethod.Get, HttpMethods.Get)] 61 | [TestCase(HttpMethod.Post, HttpMethods.Post)] 62 | [TestCase(HttpMethod.Put, HttpMethods.Put)] 63 | [TestCase(HttpMethod.Delete, HttpMethods.Delete)] 64 | public void ShouldConvertMethodEnumToString(HttpMethod method, string expectedResult) 65 | { 66 | /* Setup */ 67 | /* Test */ 68 | var result = HttpConverter.HttpMethod(method); 69 | 70 | /* Assert */ 71 | Assert.That(result, Is.EqualTo(expectedResult)); 72 | } 73 | 74 | [TestCase(HttpMethods.Get, HttpMethod.Get)] 75 | [TestCase(HttpMethods.Post, HttpMethod.Post)] 76 | [TestCase(HttpMethods.Put, HttpMethod.Put)] 77 | [TestCase(HttpMethods.Delete, HttpMethod.Delete)] 78 | public void ShouldConvertMethodStringToEnum(string method, HttpMethod expectedResult) 79 | { 80 | /* Setup */ 81 | /* Test */ 82 | var result = HttpConverter.HttpMethod(method); 83 | 84 | /* Assert */ 85 | Assert.That(result, Is.EqualTo(expectedResult)); 86 | } 87 | 88 | [TestCase(null)] 89 | [TestCase("")] 90 | [TestCase("NotKnownContentType")] 91 | public void ShouldRetrunFalseIfStringCantBeParsedAsContentType(string contentTypeString) 92 | { 93 | /* Setup */ 94 | ContentType contentType; 95 | 96 | /* Test */ 97 | var result = HttpConverter.TryParseContentType(contentTypeString, out contentType); 98 | 99 | /* Assert */ 100 | Assert.That(result, Is.False); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /PainlessHttp.Tests/Utils/ResponseTransformerTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Net; 5 | using Moq; 6 | using NUnit.Framework; 7 | using PainlessHttp.Http; 8 | using PainlessHttp.Http.Contracts; 9 | using PainlessHttp.Integration; 10 | using PainlessHttp.Serializers.Contracts; 11 | using PainlessHttp.Serializers.Defaults; 12 | using PainlessHttp.Utils; 13 | 14 | namespace PainlessHttp.Tests.Utils 15 | { 16 | public class ResponseTransformerTests 17 | { 18 | private ResponseTransformer _transformer; 19 | private List _serializers; 20 | private Mock _rawResponse; 21 | 22 | [SetUp] 23 | public void Setup() 24 | { 25 | _serializers = new List(); 26 | _transformer = new ResponseTransformer(_serializers); 27 | 28 | _rawResponse = new Mock(); 29 | _rawResponse 30 | .Setup(r => r.GetResponseStream()) 31 | .Returns(new MemoryStream()); 32 | } 33 | 34 | [TestCase(HttpStatusCode.OK)] 35 | [TestCase(HttpStatusCode.PartialContent)] 36 | public async void Should_Map_Http_Status_Code(HttpStatusCode code) 37 | { 38 | /* Setup */ 39 | _serializers.AddRange(ContentSerializers.Defaults); 40 | _rawResponse 41 | .Setup(r => r.StatusCode) 42 | .Returns(code); 43 | 44 | /* Test */ 45 | var result = await _transformer.TransformAsync(_rawResponse.Object); 46 | 47 | /* Assert */ 48 | Assert.That(result.StatusCode, Is.EqualTo(code)); 49 | } 50 | 51 | [TestCase(ContentType.ApplicationJson, ContentTypes.ApplicationJson)] 52 | [TestCase(ContentType.ApplicationXml, ContentTypes.ApplicationXml)] 53 | [TestCase(ContentType.TextHtml, ContentTypes.TextHtml)] 54 | public async void Should_Use_Serializer_Based_On_Content_Type(ContentType contentType, string contentHeaderValue) 55 | { 56 | /* Setup */ 57 | var mockedSerializers = new List> 58 | { 59 | new Mock(), 60 | new Mock(), 61 | new Mock() 62 | }; 63 | 64 | mockedSerializers[0] 65 | .Setup(s => s.ContentType) 66 | .Returns(new List {ContentType.ApplicationXml}); 67 | mockedSerializers[1] 68 | .Setup(s => s.ContentType) 69 | .Returns(new List {ContentType.ApplicationJson}); 70 | mockedSerializers[2] 71 | .Setup(s => s.ContentType) 72 | .Returns(new List {ContentType.TextHtml}); 73 | _serializers.AddRange(mockedSerializers.Select(s => s.Object)); 74 | 75 | var usedSerializer = mockedSerializers.First(s => s.Object.ContentType.Contains(contentType)); 76 | usedSerializer 77 | .Setup(s => s.Deserialize(It.IsAny())) 78 | .Verifiable(); 79 | 80 | var responseHeaders = new WebHeaderCollection {{HttpResponseHeader.ContentType, contentHeaderValue}}; 81 | _rawResponse 82 | .Setup(r => r.Headers) 83 | .Returns(responseHeaders); 84 | 85 | /* Test */ 86 | await _transformer.TransformAsync(_rawResponse.Object); 87 | 88 | /* Assert */ 89 | usedSerializer.VerifyAll(); 90 | } 91 | 92 | [Test] 93 | public void Should_Try_To_Get_Content_Type_From_Body() 94 | { 95 | /* Setup */ 96 | _rawResponse 97 | .Setup(r => r.ContentType) 98 | .Returns(ContentTypes.TextHtml); 99 | 100 | /* Test */ 101 | var contentType = _transformer.ExtractContentTypeFromResponse(_rawResponse.Object); 102 | 103 | /* Assert */ 104 | Assert.That(contentType, Is.EqualTo(ContentType.TextHtml)); 105 | } 106 | 107 | [Test] 108 | public void Should_Get_Content_Type_From_Headers_If_Body_Does_Not_Contain_Any_Content_Type() 109 | { 110 | /* Setup */ 111 | var responseHeaders = new WebHeaderCollection { { HttpResponseHeader.ContentType, ContentTypes.TextPlain } }; 112 | _rawResponse 113 | .Setup(r => r.Headers) 114 | .Returns(responseHeaders); 115 | 116 | /* Test */ 117 | var contentType = _transformer.ExtractContentTypeFromResponse(_rawResponse.Object); 118 | 119 | /* Assert */ 120 | Assert.That(contentType, Is.EqualTo(ContentType.TextPlain)); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /PainlessHttp.Tests/Utils/UrlBuilderTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Dynamic; 3 | using NUnit.Framework; 4 | using PainlessHttp.Utils; 5 | 6 | namespace PainlessHttp.Tests.Utils 7 | { 8 | [TestFixture] 9 | public class UrlBuilderTests 10 | { 11 | private UrlBuilder _builder; 12 | private const string _baseUrl = "https://www.github.com"; 13 | 14 | [SetUp] 15 | public void Setup() 16 | { 17 | _builder = new UrlBuilder(_baseUrl); 18 | } 19 | 20 | [Test] 21 | public void Should_Return_Url_That_Starts_With_Base_Url() 22 | { 23 | /* Setup */ 24 | /* Test */ 25 | var result = _builder.Build(); 26 | 27 | /* Assert */ 28 | Assert.That(result, Is.StringStarting(_baseUrl)); 29 | } 30 | 31 | [Test] 32 | public void Should_Add_Relative_Path_To_Base_Url() 33 | { 34 | /* Setup */ 35 | const string relative = "/pardahlman"; 36 | var expected = string.Format("{0}{1}", _baseUrl, relative); 37 | 38 | /* Test */ 39 | var result = _builder.Build(relative); 40 | 41 | /* Assert */ 42 | Assert.That(result, Is.EqualTo(expected)); 43 | } 44 | 45 | [Test] 46 | public void Should_Add_Query_Parameters() 47 | { 48 | /* Setup */ 49 | var query = new {firstParam = "firstValue", secondParam = "secondValue"}; 50 | var expected = string.Format("{0}?firstParam=firstValue&secondParam=secondValue", _baseUrl); 51 | 52 | /* Test */ 53 | var result = _builder.Build(string.Empty, query); 54 | 55 | /* Assert */ 56 | Assert.That(result, Is.EqualTo(expected)); 57 | } 58 | 59 | [Test] 60 | public void Should_Not_Add_Any_Query_If_Object_Is_Empty_ExpandoObject() 61 | { 62 | /* Setup */ 63 | IDictionary query = new ExpandoObject(); 64 | 65 | /* Test */ 66 | var result = _builder.Build(string.Empty, query); 67 | 68 | /* Assert */ 69 | Assert.That(result, Is.EqualTo(_baseUrl)); 70 | } 71 | 72 | [Test] 73 | public void Should_Add_Query_Parameters_Even_If_They_Are_Provided_As_Dictionary() 74 | { 75 | /* Setup */ 76 | IDictionary query = new ExpandoObject(); 77 | query["firstParam"] = "firstValue"; 78 | query["secondParam"] = "secondValue"; 79 | 80 | var expected = string.Format("{0}?firstParam=firstValue&secondParam=secondValue", _baseUrl); 81 | 82 | /* Test */ 83 | var result = _builder.Build(string.Empty, query); 84 | 85 | /* Assert */ 86 | Assert.That(result, Is.EqualTo(expected)); 87 | } 88 | 89 | [Test] 90 | public void Should_Be_Able_To_Handle_Relative_Url_And_Query() 91 | { 92 | /* Setup */ 93 | const string relative = "/pardahlman"; 94 | var query = new { id = 1}; 95 | var expected = string.Format("{0}{1}?id=1", _baseUrl, relative); 96 | 97 | /* Test */ 98 | var result = _builder.Build(relative, query); 99 | 100 | /* Assert */ 101 | Assert.That(result, Is.EqualTo(expected)); 102 | } 103 | 104 | [TestCase("http://www.github.com", "/pardahlman", "http://www.github.com/pardahlman")] 105 | [TestCase("http://www.github.com/", "pardahlman", "http://www.github.com/pardahlman")] 106 | [TestCase("http://www.github.com/", "/pardahlman", "http://www.github.com/pardahlman")] 107 | [TestCase("http://www.github.com", "pardahlman", "http://www.github.com/pardahlman")] 108 | public void Should_Not_Add_Double_Slash_Between_Base_And_Relative_Url(string baseUrl, string relativeUrl, string expectedUrl) 109 | { 110 | /* Setup */ 111 | var builder = new UrlBuilder(baseUrl); 112 | 113 | /* Test */ 114 | var result = builder.Build(relativeUrl); 115 | 116 | /* Assert */ 117 | Assert.That(result, Is.EqualTo(expectedUrl)); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /PainlessHttp.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /PainlessHttp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.30501.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PainlessHttp", "PainlessHttp\PainlessHttp.csproj", "{1719F085-34F2-48B6-AD2E-98B67856B3B0}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{650C8860-9DDF-42CB-B219-D0DC34F46BF6}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PainlessHttp.Tests", "PainlessHttp.Tests\PainlessHttp.Tests.csproj", "{3BCFB007-09A5-4FBD-9382-E2A5E364F970}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{78A5AA8A-146B-482A-A680-D6179FD129F5}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PainlessHttp.Sandbox", "PainlessHttp.Sandbox\PainlessHttp.Sandbox.csproj", "{E383E517-2F67-40E8-B9F3-A82473A5F006}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PainlessHttp.DevServer", "PainlessHttp.DevServer\PainlessHttp.DevServer.csproj", "{C7256164-4D19-40BE-9CA6-42C3CB8EB164}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PainlessHttp.Serializer.JsonNet", "PainlessHttp.Serializer.JsonNet\PainlessHttp.Serializer.JsonNet.csproj", "{368F72B9-86DB-470B-BC76-2854E0CE7BF5}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PainlessHttp.IntegrationTests", "PainlessHttp.IntegrationTests\PainlessHttp.IntegrationTests.csproj", "{F8ECB652-C9E0-446B-9A64-B6D920081376}" 21 | EndProject 22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NuGet", "NuGet", "{7C03918C-5313-4840-97D5-D5867728281A}" 23 | EndProject 24 | Project("{FF286327-C783-4F7A-AB73-9BCBAD0D4460}") = "PainlessHttp.Nuget", "PainlessHttp.Nuget\PainlessHttp.Nuget.nuproj", "{C7679876-B664-4F23-9671-12785DD1A523}" 25 | EndProject 26 | Project("{FF286327-C783-4F7A-AB73-9BCBAD0D4460}") = "Painless.Serializer.JsonNet.NuGet", "Painless.Serializer.JsonNet.NuGet\Painless.Serializer.JsonNet.NuGet.nuproj", "{E7411A4C-46A6-4270-B45E-D827518FE61C}" 27 | EndProject 28 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PainlessHttp.Serializer.JsonNet.Tests", "PainlessHttp.Serializer.JsonNet.Tests\PainlessHttp.Serializer.JsonNet.Tests.csproj", "{681410D1-AA24-4706-B95C-EE03ACA9CDA6}" 29 | EndProject 30 | Global 31 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 32 | Debug|Any CPU = Debug|Any CPU 33 | Release|Any CPU = Release|Any CPU 34 | EndGlobalSection 35 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 36 | {1719F085-34F2-48B6-AD2E-98B67856B3B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {1719F085-34F2-48B6-AD2E-98B67856B3B0}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {1719F085-34F2-48B6-AD2E-98B67856B3B0}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {1719F085-34F2-48B6-AD2E-98B67856B3B0}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {3BCFB007-09A5-4FBD-9382-E2A5E364F970}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {3BCFB007-09A5-4FBD-9382-E2A5E364F970}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {3BCFB007-09A5-4FBD-9382-E2A5E364F970}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {3BCFB007-09A5-4FBD-9382-E2A5E364F970}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {E383E517-2F67-40E8-B9F3-A82473A5F006}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {E383E517-2F67-40E8-B9F3-A82473A5F006}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {E383E517-2F67-40E8-B9F3-A82473A5F006}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {E383E517-2F67-40E8-B9F3-A82473A5F006}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {C7256164-4D19-40BE-9CA6-42C3CB8EB164}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {C7256164-4D19-40BE-9CA6-42C3CB8EB164}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {C7256164-4D19-40BE-9CA6-42C3CB8EB164}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {C7256164-4D19-40BE-9CA6-42C3CB8EB164}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {368F72B9-86DB-470B-BC76-2854E0CE7BF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {368F72B9-86DB-470B-BC76-2854E0CE7BF5}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {368F72B9-86DB-470B-BC76-2854E0CE7BF5}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {368F72B9-86DB-470B-BC76-2854E0CE7BF5}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {F8ECB652-C9E0-446B-9A64-B6D920081376}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {F8ECB652-C9E0-446B-9A64-B6D920081376}.Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {F8ECB652-C9E0-446B-9A64-B6D920081376}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {F8ECB652-C9E0-446B-9A64-B6D920081376}.Release|Any CPU.Build.0 = Release|Any CPU 60 | {C7679876-B664-4F23-9671-12785DD1A523}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 61 | {C7679876-B664-4F23-9671-12785DD1A523}.Debug|Any CPU.Build.0 = Debug|Any CPU 62 | {C7679876-B664-4F23-9671-12785DD1A523}.Release|Any CPU.ActiveCfg = Release|Any CPU 63 | {C7679876-B664-4F23-9671-12785DD1A523}.Release|Any CPU.Build.0 = Release|Any CPU 64 | {E7411A4C-46A6-4270-B45E-D827518FE61C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 65 | {E7411A4C-46A6-4270-B45E-D827518FE61C}.Debug|Any CPU.Build.0 = Debug|Any CPU 66 | {E7411A4C-46A6-4270-B45E-D827518FE61C}.Release|Any CPU.ActiveCfg = Release|Any CPU 67 | {E7411A4C-46A6-4270-B45E-D827518FE61C}.Release|Any CPU.Build.0 = Release|Any CPU 68 | {681410D1-AA24-4706-B95C-EE03ACA9CDA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 69 | {681410D1-AA24-4706-B95C-EE03ACA9CDA6}.Debug|Any CPU.Build.0 = Debug|Any CPU 70 | {681410D1-AA24-4706-B95C-EE03ACA9CDA6}.Release|Any CPU.ActiveCfg = Release|Any CPU 71 | {681410D1-AA24-4706-B95C-EE03ACA9CDA6}.Release|Any CPU.Build.0 = Release|Any CPU 72 | EndGlobalSection 73 | GlobalSection(SolutionProperties) = preSolution 74 | HideSolutionNode = FALSE 75 | EndGlobalSection 76 | GlobalSection(NestedProjects) = preSolution 77 | {3BCFB007-09A5-4FBD-9382-E2A5E364F970} = {650C8860-9DDF-42CB-B219-D0DC34F46BF6} 78 | {E383E517-2F67-40E8-B9F3-A82473A5F006} = {78A5AA8A-146B-482A-A680-D6179FD129F5} 79 | {C7256164-4D19-40BE-9CA6-42C3CB8EB164} = {78A5AA8A-146B-482A-A680-D6179FD129F5} 80 | {F8ECB652-C9E0-446B-9A64-B6D920081376} = {650C8860-9DDF-42CB-B219-D0DC34F46BF6} 81 | {C7679876-B664-4F23-9671-12785DD1A523} = {7C03918C-5313-4840-97D5-D5867728281A} 82 | {E7411A4C-46A6-4270-B45E-D827518FE61C} = {7C03918C-5313-4840-97D5-D5867728281A} 83 | {681410D1-AA24-4706-B95C-EE03ACA9CDA6} = {650C8860-9DDF-42CB-B219-D0DC34F46BF6} 84 | EndGlobalSection 85 | EndGlobal 86 | -------------------------------------------------------------------------------- /PainlessHttp/Cache/CacheBase.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Threading.Tasks; 3 | using PainlessHttp.Http.Contracts; 4 | 5 | namespace PainlessHttp.Cache 6 | { 7 | public abstract class CacheBase : IModifiedSinceCache 8 | { 9 | public abstract CachedObject Get(HttpWebRequest req); 10 | public abstract Task AddAsync(HttpWebRequest rawRequest, IHttpWebResponse rawResponse); 11 | 12 | protected static string GetCacheKey(IHttpWebResponse rawResponse) 13 | { 14 | return string.Format("{0}_{1}_{2}", rawResponse.Method, rawResponse.ResponseUri.AbsolutePath, rawResponse.ResponseUri.Query); 15 | } 16 | 17 | protected static string GetCacheKey(WebRequest rawRequest) 18 | { 19 | return string.Format("{0}_{1}_{2}", rawRequest.Method, rawRequest.RequestUri.AbsolutePath, rawRequest.RequestUri.Query); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /PainlessHttp/Cache/CachedObject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace PainlessHttp.Cache 5 | { 6 | public class CachedObject 7 | { 8 | private readonly Lazy _resposeStream; 9 | public DateTime ModifiedDate { get; set; } 10 | public Stream ResponseStream { get { return _resposeStream.Value; }} 11 | 12 | public CachedObject(Func resposeStream = null) 13 | { 14 | _resposeStream = new Lazy(resposeStream); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /PainlessHttp/Cache/FileCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using System.Security.Cryptography; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using PainlessHttp.Http; 8 | using PainlessHttp.Http.Contracts; 9 | using PainlessHttp.Serializers.Defaults; 10 | 11 | namespace PainlessHttp.Cache 12 | { 13 | public class FileCache : CacheBase 14 | { 15 | public string CacheDirectory { get; private set; } 16 | private readonly Type _cacheObjectType = typeof (CachedOnDisk); 17 | private static readonly SHA1CryptoServiceProvider HashProvider = new SHA1CryptoServiceProvider(); 18 | 19 | public FileCache(string cacheDirectory = null) 20 | { 21 | CacheDirectory = cacheDirectory ?? GetPathToCacheDirectory(); 22 | if (!Directory.Exists(CacheDirectory)) 23 | throw new DirectoryNotFoundException(string.Format("Cache directory '{0}' does not exist", CacheDirectory)); 24 | } 25 | 26 | public override CachedObject Get(HttpWebRequest req) 27 | { 28 | if (req == null) 29 | { 30 | return null; 31 | } 32 | if (!string.Equals(req.Method, HttpMethods.Get, StringComparison.InvariantCultureIgnoreCase)) 33 | { 34 | return null; 35 | } 36 | 37 | var key = GetCacheKey(req); 38 | var path = GetKeyPath(key); 39 | if (!File.Exists(path)) 40 | { 41 | return null; 42 | } 43 | var xmlString = File.ReadAllText(path); 44 | var cachedOnDisk = (CachedOnDisk)DefaultXml.Deserialize(xmlString, _cacheObjectType); 45 | return new CachedObject(() => new MemoryStream(Encoding.UTF8.GetBytes(cachedOnDisk.Value))) 46 | { 47 | ModifiedDate = cachedOnDisk.LastModified 48 | }; 49 | } 50 | 51 | public override async Task AddAsync(HttpWebRequest rawRequest, IHttpWebResponse rawResponse) 52 | { 53 | if (rawResponse == null) 54 | { 55 | return; 56 | } 57 | if (rawResponse.StatusCode != HttpStatusCode.OK) 58 | { 59 | return; 60 | } 61 | 62 | string content; 63 | using (var responseStream = rawResponse.GetResponseStream()) 64 | { 65 | using (var reader = new StreamReader(responseStream)) 66 | { 67 | content = await reader.ReadToEndAsync(); 68 | } 69 | } 70 | 71 | rawResponse.SetResponseStream(new MemoryStream(Encoding.UTF8.GetBytes(content))); 72 | 73 | var cacheTarget = DefaultXml.Serialize(new CachedOnDisk 74 | { 75 | PersistDate = DateTime.Now, 76 | LastModified = rawResponse.LastModified, 77 | Value = content 78 | }); 79 | 80 | var key = GetCacheKey(rawResponse); 81 | var path = GetKeyPath(key); 82 | File.WriteAllText(path, cacheTarget); 83 | } 84 | 85 | public void Clear() 86 | { 87 | var files = Directory.EnumerateFiles(CacheDirectory); 88 | foreach (var file in files) 89 | { 90 | var path = Path.Combine(CacheDirectory, file); 91 | File.Delete(path); 92 | } 93 | } 94 | 95 | private static string GetPathToCacheDirectory() 96 | { 97 | var defaultCacheDirectory = Environment.CurrentDirectory + "//PainlessCache"; 98 | if (Directory.Exists(defaultCacheDirectory)) 99 | { 100 | return defaultCacheDirectory; 101 | } 102 | Directory.CreateDirectory(defaultCacheDirectory); 103 | return defaultCacheDirectory; 104 | } 105 | 106 | string GetKeyPath(string key) 107 | { 108 | // Copy key to byte array 109 | var bytes = new byte[key.Length * sizeof(char)]; 110 | Buffer.BlockCopy(key.ToCharArray(), 0, bytes, 0, bytes.Length); 111 | 112 | // Calculate file name 113 | var hashBytes = HashProvider.ComputeHash(bytes); 114 | var fileName = BitConverter.ToString(hashBytes); 115 | 116 | // Finalize Path 117 | var path = Path.Combine(CacheDirectory, fileName); 118 | return path; 119 | } 120 | 121 | public class CachedOnDisk 122 | { 123 | public string Value { get; set; } 124 | public DateTime LastModified { get; set; } 125 | public DateTime PersistDate { get; set; } 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /PainlessHttp/Cache/IModifiedSinceCache.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Threading.Tasks; 3 | using PainlessHttp.Http.Contracts; 4 | 5 | namespace PainlessHttp.Cache 6 | { 7 | public interface IModifiedSinceCache 8 | { 9 | CachedObject Get(HttpWebRequest req); 10 | Task AddAsync(HttpWebRequest rawRequest, IHttpWebResponse rawResponse); 11 | } 12 | } -------------------------------------------------------------------------------- /PainlessHttp/Cache/InMemoryCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Net; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using PainlessHttp.Http; 8 | using PainlessHttp.Http.Contracts; 9 | 10 | namespace PainlessHttp.Cache 11 | { 12 | public class InMemoryCache : CacheBase 13 | { 14 | private readonly Dictionary _cache; 15 | 16 | public InMemoryCache() 17 | { 18 | _cache = new Dictionary(); 19 | } 20 | public override CachedObject Get(HttpWebRequest req) 21 | { 22 | if (req == null) 23 | { 24 | return null; 25 | } 26 | if (!string.Equals(req.Method, HttpMethods.Get, StringComparison.InvariantCultureIgnoreCase)) 27 | { 28 | return null; 29 | } 30 | var key = GetCacheKey(req); 31 | if (!_cache.ContainsKey(key)) 32 | { 33 | return null; 34 | } 35 | 36 | return _cache[key]; 37 | } 38 | 39 | public override async Task AddAsync(HttpWebRequest rawRequest, IHttpWebResponse rawResponse) 40 | { 41 | if (rawResponse == null) 42 | { 43 | return; 44 | } 45 | if (rawResponse.StatusCode != HttpStatusCode.OK) 46 | { 47 | return; 48 | } 49 | var key = GetCacheKey(rawResponse); 50 | 51 | var content = string.Empty; 52 | using (var responseStream = rawResponse.GetResponseStream()) 53 | { 54 | using (var reader = new StreamReader(responseStream)) 55 | { 56 | content = await reader.ReadToEndAsync(); 57 | } 58 | } 59 | 60 | rawResponse.SetResponseStream(new MemoryStream(Encoding.UTF8.GetBytes(content))); 61 | 62 | _cache[key] = new CachedObject(() => new MemoryStream(Encoding.UTF8.GetBytes(content))) 63 | { 64 | ModifiedDate = rawResponse.LastModified 65 | }; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /PainlessHttp/Cache/NoCache.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Threading.Tasks; 3 | using PainlessHttp.Http.Contracts; 4 | 5 | namespace PainlessHttp.Cache 6 | { 7 | public class NoCache : IModifiedSinceCache 8 | { 9 | public CachedObject Get(HttpWebRequest req) 10 | { 11 | return null; 12 | } 13 | 14 | public Task AddAsync(HttpWebRequest rawRequest, IHttpWebResponse rawResponse) 15 | { 16 | return Task.FromResult(true); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /PainlessHttp/Client/Configuration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using PainlessHttp.Cache; 5 | using PainlessHttp.Serializers.Contracts; 6 | 7 | namespace PainlessHttp.Client 8 | { 9 | public class Configuration 10 | { 11 | public string BaseUrl { get; set; } 12 | public AdvancedConfiguration Advanced { get; set; } 13 | 14 | public Configuration() 15 | { 16 | Advanced = new AdvancedConfiguration(); 17 | } 18 | 19 | public class AdvancedConfiguration 20 | { 21 | /// 22 | /// Override the default content serializers. 23 | /// 24 | public IList Serializers { get; set; } 25 | 26 | /// 27 | /// Specify if Content Negotiation should be performed. If set to true, a request with a body will be resent 28 | /// if the server does not support the current content type. 29 | /// 30 | public bool ContentNegotiation { get; set; } 31 | 32 | /// 33 | /// The Cache to be used for 'If-Modified-Since' and 'Last-Modified' header. 34 | /// Default to a 'NoCache' Implementation, that does not perform any caching. 35 | /// 36 | public IModifiedSinceCache ModifiedSinceCache { get; set; } 37 | 38 | /// 39 | /// The time to wait for a response before timing out. Defaults to 10 seconds. 40 | /// 41 | public TimeSpan RequestTimeout { get; set; } 42 | 43 | /// 44 | /// Action to be made on the underlying WebRequest before it is sent. 45 | /// E.g. request => request.Headers.Add("X-Additional-Header", "Additional Value") 46 | /// 47 | public Action WebrequestModifier { get; set; } 48 | public NetworkCredential Credentials { get; set; } 49 | 50 | public AdvancedConfiguration() 51 | { 52 | Serializers = new List(); 53 | ModifiedSinceCache = new NoCache(); 54 | ContentNegotiation = true; 55 | WebrequestModifier = (req) => { }; 56 | RequestTimeout = new TimeSpan(days: 0, hours:0, minutes:0, seconds:10); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /PainlessHttp/Client/HttpClient.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using PainlessHttp.Http; 5 | using PainlessHttp.Http.Contracts; 6 | using PainlessHttp.Integration; 7 | using PainlessHttp.Serializers.Defaults; 8 | 9 | namespace PainlessHttp.Client 10 | { 11 | [DebuggerDisplay("PainlessHttp, BaseUrl: {BaseUrl}")] 12 | public class HttpClient : IHttpClient 13 | { 14 | public string BaseUrl { get; private set; } 15 | 16 | private readonly ResponseTransformer _responseTransformer; 17 | private readonly WebRequester _webRequester; 18 | 19 | public HttpClient(string baseUrl = "") : this(new Configuration{BaseUrl = baseUrl}) 20 | { 21 | /* Don't duplicate code here.*/ 22 | } 23 | 24 | public HttpClient(Configuration config) 25 | { 26 | BaseUrl = config.BaseUrl; 27 | config.Advanced.Serializers = config.Advanced.Serializers.Concat(ContentSerializers.Defaults).ToList(); 28 | 29 | _webRequester = new WebRequester(config); 30 | _responseTransformer = new ResponseTransformer(config.Advanced.Serializers); 31 | } 32 | 33 | public IHttpResponse Get(string url, object query = null) where T : class 34 | { 35 | return GetAsync(url, query).Result; 36 | } 37 | 38 | public async Task> GetAsync(string url, object query = null) where T : class 39 | { 40 | var response = await PerformRequestAsync(HttpMethod.Get, url, query); 41 | return response; 42 | } 43 | 44 | public IHttpResponse Post(string url, object data, object query = null, ContentType type = ContentType.ApplicationJson) where T : class 45 | { 46 | return PostAsync(url, data, query, type).Result; 47 | } 48 | 49 | public async Task> PostAsync(string url, object data, object query = null, ContentType type = ContentType.ApplicationJson) where T : class 50 | { 51 | var response = await PerformRequestAsync(HttpMethod.Post, url, query, type, data); 52 | return response; 53 | } 54 | 55 | public IHttpResponse Put(string url, object data, object query = null, ContentType type = ContentType.ApplicationJson) where T : class 56 | { 57 | return PutAsync(url, data, query, type).Result; 58 | } 59 | 60 | public async Task> PutAsync(string url, object data, object query = null, ContentType type = ContentType.ApplicationJson) where T : class 61 | { 62 | var response = await PerformRequestAsync(HttpMethod.Put, url, query, type, data); 63 | return response; 64 | } 65 | 66 | public IHttpResponse Delete(string url, object data = null, object query = null, ContentType type = ContentType.ApplicationJson) where T : class 67 | { 68 | return DeleteAsync(url, data, query, type).Result; 69 | } 70 | 71 | public async Task> DeleteAsync(string url, object data = null, object query = null, ContentType type = ContentType.ApplicationJson) where T : class 72 | { 73 | var response = await PerformRequestAsync(HttpMethod.Delete, url, query, type, data); 74 | return response; 75 | } 76 | 77 | public IHttpWebResponse PerformRaw(HttpMethod method, string url, object data = null, object query = null, ContentType type = ContentType.ApplicationJson) 78 | { 79 | var response = GetRawResponseAsync(method, url, query, type, data).Result; 80 | return response; 81 | } 82 | 83 | public Task PerformRawAsync(HttpMethod method, string url, object data = null, object query = null, ContentType type = ContentType.ApplicationJson) 84 | { 85 | var response = GetRawResponseAsync(method, url, query, type, data); 86 | return response; 87 | } 88 | 89 | private async Task> PerformRequestAsync(HttpMethod method, string url, object query, ContentType type = ContentType.ApplicationJson, object data = null) where T : class 90 | { 91 | var response = await GetRawResponseAsync(method, url, query,type, data); 92 | return await _responseTransformer.TransformAsync(response); 93 | } 94 | 95 | private async Task GetRawResponseAsync(HttpMethod method, string url, object query, ContentType type, object data) 96 | { 97 | var response = await _webRequester 98 | .WithUrl(url, query) 99 | .WithMethod(method) 100 | .WithPayload(data, type) 101 | .PerformAsync(); 102 | return response; 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /PainlessHttp/Client/IHttpClient.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using PainlessHttp.Http; 3 | using PainlessHttp.Http.Contracts; 4 | using PainlessHttp.Integration; 5 | 6 | namespace PainlessHttp.Client 7 | { 8 | public interface IHttpClient 9 | { 10 | IHttpResponse Get(string url, object query = null) where T : class; 11 | Task> GetAsync(string url, object query = null) where T : class; 12 | 13 | IHttpResponse Post(string url, object data, object query = null, ContentType type = ContentType.ApplicationJson) where T : class; 14 | Task> PostAsync(string url, object data, object query = null, ContentType type = ContentType.ApplicationJson) where T : class; 15 | 16 | IHttpResponse Put(string url, object data, object query = null, ContentType type = ContentType.ApplicationJson) where T : class; 17 | Task> PutAsync(string url, object data, object query = null, ContentType type = ContentType.ApplicationJson) where T : class; 18 | 19 | IHttpResponse Delete(string url, object data = null, object query = null, ContentType type = ContentType.ApplicationJson) where T : class; 20 | Task> DeleteAsync(string url, object data = null, object query = null, ContentType type = ContentType.ApplicationJson) where T : class; 21 | 22 | /// 23 | /// Use this method if you want to get hold of the 'raw' HttpWebResponse. The response is wrapped in an interface, which makes it suitable for testing. 24 | /// 25 | IHttpWebResponse PerformRaw(HttpMethod method, string url, object data = null, object query = null, ContentType type = ContentType.ApplicationJson); 26 | Task PerformRawAsync(HttpMethod method, string url, object data = null, object query = null, ContentType type = ContentType.ApplicationJson); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /PainlessHttp/Http/AcceptHeaderField.cs: -------------------------------------------------------------------------------- 1 | namespace PainlessHttp.Http 2 | { 3 | public class AcceptHeaderField 4 | { 5 | public string ContentType { get; set; } 6 | public float Q { get; set; } 7 | public float Mxs { get; set; } 8 | public float Mxb { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /PainlessHttp/Http/ContentType.cs: -------------------------------------------------------------------------------- 1 | namespace PainlessHttp.Http 2 | { 3 | public enum ContentType 4 | { 5 | Unknown, 6 | ApplicationJson, 7 | ApplicationXml, 8 | TextPlain, 9 | TextCsv, 10 | TextHtml 11 | } 12 | 13 | public static class ContentTypes 14 | { 15 | public const string TextPlain = "text/plain"; 16 | public const string TextHtml = "text/html"; 17 | public const string TextCsv = "text/csv"; 18 | public const string ApplicationJson = "application/json"; 19 | public const string ApplicationXml = "application/xml"; 20 | } 21 | } -------------------------------------------------------------------------------- /PainlessHttp/Http/Contracts/IHttpResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | 4 | namespace PainlessHttp.Http.Contracts 5 | { 6 | public interface IHttpResponse 7 | { 8 | HttpStatusCode StatusCode { get; } 9 | T Body { get; } 10 | string RawBody { get; set; } 11 | 12 | /// 13 | /// LastModified is a DateTime that represents when the resource was last changed. This 14 | /// property can be used to set 'If-Modified-Since' headers for caching response payloads 15 | /// and decrease traffic. 16 | /// 17 | DateTime LastModified { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /PainlessHttp/Http/Contracts/IHttpWebResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | 5 | namespace PainlessHttp.Http.Contracts 6 | { 7 | public interface IHttpWebResponse : IDisposable 8 | { 9 | string CharacterSet { get; } 10 | string ContentEncoding { get; } 11 | long ContentLength { get; } 12 | string ContentType { get; } 13 | CookieCollection Cookies { get; set; } 14 | WebHeaderCollection Headers { get; } 15 | bool IsMutuallyAuthenticated { get; } 16 | DateTime LastModified { get; } 17 | string Method { get; } 18 | Version ProtocolVersion { get; } 19 | Uri ResponseUri { get; } 20 | string Server { get; } 21 | HttpStatusCode StatusCode { get; } 22 | string StatusDescription { get; } 23 | bool SupportsHeaders { get; } 24 | void Close(); 25 | string GetResponseHeader(string headerName); 26 | Stream GetResponseStream(); 27 | void SetResponseStream(Stream responseStream); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /PainlessHttp/Http/HttpMethod.cs: -------------------------------------------------------------------------------- 1 | namespace PainlessHttp.Http 2 | { 3 | public enum HttpMethod 4 | { 5 | Unknown, 6 | Get, 7 | Post, 8 | Put, 9 | Delete, 10 | Options 11 | } 12 | 13 | public class HttpMethods 14 | { 15 | public const string Get = "GET"; 16 | public const string Post = "POST"; 17 | public const string Delete = "DELETE"; 18 | public const string Put = "PUT"; 19 | public const string Options = "OPTIONS"; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /PainlessHttp/Http/HttpResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using PainlessHttp.Http.Contracts; 4 | 5 | namespace PainlessHttp.Http 6 | { 7 | public class HttpResponse : IHttpResponse 8 | { 9 | public HttpStatusCode StatusCode { get; set; } 10 | public T Body { get; set; } 11 | public string RawBody { get; set; } 12 | public DateTime LastModified { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /PainlessHttp/Http/HttpWebResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Net; 5 | using PainlessHttp.Http.Contracts; 6 | 7 | namespace PainlessHttp.Http 8 | { 9 | public class HttpWebResponse : IHttpWebResponse 10 | { 11 | private readonly System.Net.HttpWebResponse _raw; 12 | private Stream _responseStream; 13 | 14 | public string CharacterSet { get; set; } 15 | public string ContentEncoding { get; set; } 16 | public string Server { get; set; } 17 | public string ContentType { get; set; } 18 | public string StatusDescription { get; set; } 19 | public string Method { get; set; } 20 | public bool IsMutuallyAuthenticated { get; set; } 21 | public bool SupportsHeaders { get; set; } 22 | public long ContentLength { get; set; } 23 | public CookieCollection Cookies { get; set; } 24 | public WebHeaderCollection Headers { get; set; } 25 | public DateTime LastModified { get; set; } 26 | public Version ProtocolVersion { get; set; } 27 | public Uri ResponseUri { get; set; } 28 | public HttpStatusCode StatusCode { get; set; } 29 | 30 | public HttpWebResponse(System.Net.HttpWebResponse raw = null) 31 | { 32 | if (raw == null) 33 | { 34 | return; 35 | } 36 | _raw = raw; 37 | InitiateProperties(); 38 | } 39 | 40 | private void InitiateProperties() 41 | { 42 | var slave = GetType().GetProperties(); 43 | var master = _raw.GetType().GetProperties(); 44 | foreach (var property in slave) 45 | { 46 | var corresponding = master.FirstOrDefault(p => p.Name == property.Name && p.PropertyType == property.PropertyType); 47 | if (corresponding == null) 48 | continue; 49 | 50 | property.SetValue(this, corresponding.GetValue(_raw)); 51 | } 52 | } 53 | 54 | public void SetResponseStream(Stream responseStream) 55 | { 56 | _responseStream = responseStream; 57 | } 58 | 59 | public void Close() 60 | { 61 | if(_raw != null) 62 | _raw.Close(); 63 | } 64 | 65 | public string GetResponseHeader(string headerName) 66 | { 67 | if (_raw == null) 68 | { 69 | throw new NotImplementedException("Can not call method without existing underlying HttpWebResponse"); 70 | } 71 | return _raw.GetResponseHeader(headerName); 72 | } 73 | 74 | public Stream GetResponseStream() 75 | { 76 | if (_responseStream != null) 77 | { 78 | return _responseStream; 79 | } 80 | if (_raw == null) 81 | { 82 | throw new NotImplementedException("Can not call method without existing underlying HttpWebResponse"); 83 | } 84 | return _raw.GetResponseStream(); 85 | } 86 | 87 | 88 | 89 | public void Dispose() 90 | { 91 | if (_raw != null) 92 | _raw.Dispose(); 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /PainlessHttp/Integration/ResponseTransformer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Management.Instrumentation; 6 | using System.Net; 7 | using System.Threading.Tasks; 8 | using PainlessHttp.Http; 9 | using PainlessHttp.Http.Contracts; 10 | using PainlessHttp.Serializers.Contracts; 11 | using PainlessHttp.Utils; 12 | 13 | namespace PainlessHttp.Integration 14 | { 15 | public class ResponseTransformer 16 | { 17 | private readonly IEnumerable _serializers; 18 | 19 | public ResponseTransformer(IEnumerable serializers) 20 | { 21 | _serializers = serializers; 22 | } 23 | 24 | public async Task> TransformAsync(IHttpWebResponse raw) where T : class 25 | { 26 | var rawBody = await ReadBodyAsync(raw); 27 | 28 | var result = new HttpResponse 29 | { 30 | StatusCode = raw.StatusCode, 31 | LastModified = raw.LastModified, 32 | RawBody = rawBody, 33 | Body = Deserialize(rawBody, raw) 34 | }; 35 | 36 | return result; 37 | } 38 | 39 | private T Deserialize(string body, IHttpWebResponse raw) where T : class 40 | { 41 | var contentType = ExtractContentTypeFromResponse(raw); 42 | var serializer = _serializers.FirstOrDefault(s => s.ContentType.Contains(contentType)); 43 | if (serializer == null) 44 | { 45 | throw new InstanceNotFoundException(string.Format("No registered serializer found for content type '{0}'.", contentType)); 46 | } 47 | 48 | if (typeof(T) == typeof(string)) 49 | { 50 | return body as T; 51 | } 52 | try 53 | { 54 | var typedBody = serializer.Deserialize(body); 55 | return typedBody; 56 | } 57 | catch (Exception e) 58 | { 59 | if ((int)raw.StatusCode < 400) 60 | { 61 | // 2XX or 3XX responses should be serializable. throw to make the user aware of the problem. 62 | throw; 63 | } 64 | // the response is within the faulty range, no need to throw on serialization 65 | return default(T); 66 | } 67 | } 68 | 69 | internal ContentType ExtractContentTypeFromResponse(IHttpWebResponse response) 70 | { 71 | ContentType result; 72 | if (HttpConverter.TryParseContentType(response.ContentType, out result)) 73 | { 74 | return result; 75 | } 76 | 77 | var headers = response.Headers; 78 | const ContentType fallback = ContentType.ApplicationJson; 79 | if (headers == null) 80 | { 81 | return fallback; 82 | } 83 | 84 | var contentTypeHeader = headers[HttpResponseHeader.ContentType]; 85 | if (contentTypeHeader == null) 86 | { 87 | return fallback; 88 | } 89 | 90 | var contentType = HttpConverter.ContentType(contentTypeHeader); 91 | return contentType; 92 | } 93 | 94 | public static async Task ReadBodyAsync(IHttpWebResponse response) 95 | { 96 | using (var responseStream = response.GetResponseStream()) 97 | { 98 | if (responseStream == null) 99 | { 100 | throw new ArgumentNullException("response"); 101 | } 102 | 103 | string raw; 104 | using (var reader = new StreamReader(responseStream)) 105 | { 106 | raw = await reader.ReadToEndAsync(); 107 | } 108 | return raw; 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /PainlessHttp/Integration/WebRequester.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using PainlessHttp.Client; 10 | using PainlessHttp.Http; 11 | using PainlessHttp.Http.Contracts; 12 | using PainlessHttp.Utils; 13 | using HttpWebResponse = PainlessHttp.Http.HttpWebResponse; 14 | using Timer = System.Timers.Timer; 15 | 16 | namespace PainlessHttp.Integration 17 | { 18 | public class WebRequester 19 | { 20 | private readonly Configuration _config; 21 | private readonly UrlBuilder _urlBuilder; 22 | private readonly string _accept = String.Join(",", ContentTypes.ApplicationJson, ContentTypes.ApplicationXml); 23 | 24 | private Func _requestInit; 25 | private Action _methodModifier; 26 | private Func _payloadModifilerAsync; 27 | private Func _contentTypeProvider; 28 | private readonly IContentNegotiator _contentNegotiator; 29 | private const string _timeOutResponse = "Oh noes! The request for {0} timed out after {1} ms. The time out can be increased by configuring the client's Request time-out at Configuration.Advanced.RequestTimeout."; 30 | 31 | public WebRequester(Configuration config) 32 | { 33 | _config = config; 34 | _contentNegotiator = new ContentNegotiator(config.Advanced.Serializers.SelectMany(s => s.ContentType)); 35 | _urlBuilder = new UrlBuilder(config.BaseUrl); 36 | ResetModifiers(); 37 | } 38 | 39 | public WebRequester WithUrl(string url, object query) 40 | { 41 | var requestUrl = _urlBuilder.Build(url, query); 42 | _requestInit = () => (HttpWebRequest) WebRequest.Create(requestUrl); 43 | return this; 44 | } 45 | 46 | public WebRequester WithMethod(HttpMethod method) 47 | { 48 | _methodModifier = req => req.Method = method.ToString().ToUpper(); 49 | return this; 50 | } 51 | 52 | public WebRequester WithPayload(object data, ContentType type) 53 | { 54 | _contentTypeProvider = () => type; 55 | _payloadModifilerAsync = async req => 56 | { 57 | if (data == null) 58 | { 59 | return; 60 | } 61 | var serializer = _config.Advanced.Serializers.FirstOrDefault(s => s.ContentType.Contains(_contentTypeProvider())); 62 | if (serializer == null) 63 | { 64 | throw new Exception(string.Format("No Serializer registered for {0}", type)); 65 | } 66 | var serializedPayload = serializer.Serialize(data); 67 | var payloadAsBytes = Encoding.UTF8.GetBytes(serializedPayload); 68 | req.ContentLength = payloadAsBytes.Length; 69 | req.ContentType = HttpConverter.ContentType(_contentTypeProvider()); 70 | using (var stream = await Task.Factory.FromAsync(req.BeginGetRequestStream, req.EndGetRequestStream, req)) 71 | { 72 | await stream.WriteAsync(payloadAsBytes, 0, payloadAsBytes.Length); 73 | } 74 | }; 75 | return this; 76 | } 77 | 78 | public async Task PerformAsync() 79 | { 80 | var rawRequest = await PrepareAsync(); 81 | var rawResponse = await GetResponseAsync(rawRequest); 82 | 83 | if (_config .Advanced.ContentNegotiation && _contentNegotiator.IsApplicable(rawResponse)) 84 | { 85 | _contentTypeProvider = () => _contentNegotiator.GetSupportedContentType(rawResponse); 86 | rawRequest = await PrepareAsync(); 87 | rawResponse = await GetResponseAsync(rawRequest); 88 | } 89 | 90 | if (rawResponse.StatusCode == HttpStatusCode.NotModified) 91 | { 92 | var cached = _config.Advanced.ModifiedSinceCache.Get(rawRequest); 93 | rawResponse.SetResponseStream(cached.ResponseStream); 94 | } 95 | else 96 | { 97 | await _config.Advanced.ModifiedSinceCache.AddAsync(rawRequest, rawResponse); 98 | } 99 | 100 | ResetModifiers(); 101 | return rawResponse; 102 | } 103 | 104 | private async Task GetResponseAsync(WebRequest rawRequest) 105 | { 106 | var timer = new Timer(_config.Advanced.RequestTimeout.TotalMilliseconds); 107 | timer.Elapsed += (sender, args) => rawRequest.Abort(); 108 | try 109 | { 110 | timer.Start(); 111 | var response = 112 | await Task.Factory.FromAsync(rawRequest.BeginGetResponse, rawRequest.EndGetResponse, rawRequest); 113 | return new HttpWebResponse((System.Net.HttpWebResponse) response); 114 | } 115 | catch (WebException e) 116 | { 117 | if (e.Status == WebExceptionStatus.RequestCanceled) 118 | { 119 | var timeoutResponse = new HttpWebResponse 120 | { 121 | ContentType = ContentTypes.TextPlain 122 | }; 123 | timeoutResponse.SetResponseStream(new MemoryStream(Encoding.UTF8.GetBytes(string.Format(_timeOutResponse, rawRequest.RequestUri.AbsolutePath, _config.Advanced.RequestTimeout.TotalMilliseconds)))); 124 | return timeoutResponse; 125 | } 126 | return new HttpWebResponse((System.Net.HttpWebResponse) e.Response); 127 | } 128 | finally 129 | { 130 | timer.Close(); 131 | timer.Stop(); 132 | timer.Dispose(); 133 | } 134 | } 135 | 136 | private async Task PrepareAsync() 137 | { 138 | var req = _requestInit(); 139 | 140 | // Overrideable props up here 141 | req.AllowAutoRedirect = true; 142 | req.PreAuthenticate = true; 143 | req.Accept = _accept; 144 | 145 | // Modifications from configuration 146 | _config.Advanced.WebrequestModifier(req); 147 | 148 | // Fixed props down here. 149 | req.Credentials = _config.Advanced.Credentials; 150 | req.UserAgent = ClientUtils.GetUserAgent(); 151 | _methodModifier(req); 152 | await _payloadModifilerAsync(req); 153 | 154 | var cached = _config.Advanced.ModifiedSinceCache.Get(req); 155 | if (cached != null) 156 | { 157 | req.IfModifiedSince = cached.ModifiedDate; 158 | } 159 | 160 | return req; 161 | } 162 | 163 | private void ResetModifiers() 164 | { 165 | _requestInit = () => { throw new Exception("Request can not be performed without a Url. Set Url by calling the 'WithUrl' method."); }; 166 | _contentTypeProvider = () => ContentType.Unknown; 167 | _payloadModifilerAsync = request => Task.FromResult(true); 168 | _methodModifier = request => { }; 169 | } 170 | } 171 | } -------------------------------------------------------------------------------- /PainlessHttp/PainlessHttp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {1719F085-34F2-48B6-AD2E-98B67856B3B0} 8 | Library 9 | Properties 10 | PainlessHttp 11 | PainlessHttp 12 | v4.5.1 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 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 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 82 | -------------------------------------------------------------------------------- /PainlessHttp/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("PainlessHttp")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("PainlessHttp")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | [assembly: InternalsVisibleTo("PainlessHttp.Tests")] 17 | 18 | // Setting ComVisible to false makes the types in this assembly not visible 19 | // to COM components. If you need to access a type in this assembly from 20 | // COM, set the ComVisible attribute to true on that type. 21 | [assembly: ComVisible(false)] 22 | 23 | // The following GUID is for the ID of the typelib if this project is exposed to COM 24 | [assembly: Guid("2decda8a-70f7-4991-bdd2-c37a2b3a1a8f")] 25 | 26 | // Version information for an assembly consists of the following four values: 27 | // 28 | // Major Version 29 | // Minor Version 30 | // Build Number 31 | // Revision 32 | // 33 | // You can specify all the values or you can default the Build and Revision Numbers 34 | // by using the '*' as shown below: 35 | // [assembly: AssemblyVersion("1.0.*")] 36 | [assembly: AssemblyVersion("0.11.6")] 37 | [assembly: AssemblyFileVersion("0.11.6")] 38 | -------------------------------------------------------------------------------- /PainlessHttp/Serializers/Contracts/IContentSerializer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using PainlessHttp.Http; 3 | 4 | namespace PainlessHttp.Serializers.Contracts 5 | { 6 | public interface IContentSerializer 7 | { 8 | IEnumerable ContentType { get; } 9 | string Serialize(object data); 10 | T Deserialize(string data); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /PainlessHttp/Serializers/Custom/CustomSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using PainlessHttp.Http; 4 | using PainlessHttp.Serializers.Contracts; 5 | 6 | namespace PainlessHttp.Serializers.Custom 7 | { 8 | public class CustomSerializer : IContentSerializer 9 | { 10 | private readonly Func _serialize; 11 | private readonly Func _deserialize; 12 | public IEnumerable ContentType { get; private set; } 13 | 14 | public CustomSerializer(IEnumerable types, Func serialize, Func deserialize) 15 | { 16 | _serialize = serialize; 17 | _deserialize = deserialize; 18 | ContentType = types; 19 | } 20 | 21 | public string Serialize(object data) 22 | { 23 | return _serialize(data); 24 | } 25 | 26 | public T Deserialize(string data) 27 | { 28 | var obj = _deserialize(data, typeof(T)); 29 | return (T) obj; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /PainlessHttp/Serializers/Custom/Serializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using PainlessHttp.Http; 6 | using PainlessHttp.Serializers.Contracts; 7 | 8 | namespace PainlessHttp.Serializers.Custom 9 | { 10 | public class Serializer : IContentSerializer 11 | { 12 | private readonly MethodInfo _serializeMethod; 13 | private readonly MethodInfo _deserialize; 14 | 15 | public Serializer(params ContentType[] contentTypes) 16 | { 17 | if (contentTypes == null || contentTypes.Length == 0) 18 | { 19 | throw new ArgumentException("The serializer must at leaste contain one ContentType."); 20 | } 21 | ContentType = contentTypes; 22 | 23 | var methods = typeof(TSerializer).GetMethods(); 24 | _serializeMethod = methods.FirstOrDefault(m => 25 | { 26 | var correctArguments = ArgumentsMatch(m.GetParameters(), new[] { typeof(object) }); 27 | var correctReturn = ReturnTypeIs(m.ReturnType); 28 | 29 | return correctArguments && correctReturn; 30 | }); 31 | _deserialize = methods.FirstOrDefault(m => 32 | { 33 | var correctArguments = ArgumentsMatch(m.GetParameters(), new[] { typeof(string), typeof(Type) }); 34 | var correctReturn = ReturnTypeIs(m.ReturnType); 35 | 36 | return correctArguments && correctReturn; 37 | }); 38 | 39 | if (_serializeMethod == null) 40 | { 41 | throw new MissingMethodException(string.Format("The type {0} does not contain a method with signature 'string Method(object obj)'. Please make sure that this is a serializer.", typeof(TSerializer).Name)); 42 | } 43 | if (_deserialize == null) 44 | { 45 | throw new MissingMethodException(string.Format("The type {0} does not contain a method with signature 'object Method(string data, Type type)'. Please make sure that this is a serializer.", typeof(TSerializer).Name)); 46 | } 47 | } 48 | 49 | private static bool ArgumentsMatch(IList parameterInfos, IList types) 50 | { 51 | if (parameterInfos.Count != types.Count) 52 | { 53 | return false; 54 | } 55 | 56 | for (var i = 0; i < parameterInfos.Count; i++) 57 | { 58 | if (parameterInfos[i].ParameterType != types[i]) 59 | { 60 | return false; 61 | } 62 | } 63 | return true; 64 | } 65 | 66 | private static bool ReturnTypeIs(Type type) 67 | { 68 | return type == typeof(TReturnType); 69 | } 70 | 71 | public IEnumerable ContentType { get; private set; } 72 | public string Serialize(object data) 73 | { 74 | var res = _serializeMethod.Invoke(null, new[] { data }); 75 | return (string)res; 76 | } 77 | 78 | public TObject Deserialize(string data) 79 | { 80 | var res = _deserialize.Invoke(null, new object[] { data, typeof(TObject) }); 81 | return (TObject)res; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /PainlessHttp/Serializers/Custom/SerializerBulider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using PainlessHttp.Http; 3 | using PainlessHttp.Serializers.Contracts; 4 | 5 | namespace PainlessHttp.Serializers.Custom 6 | { 7 | public class SerializerBulider 8 | { 9 | public static FluentSerializerBuilder For(params ContentType[] applicationJson) 10 | { 11 | return new FluentSerializerBuilder(applicationJson); 12 | } 13 | } 14 | 15 | public class FluentSerializerBuilder 16 | { 17 | private readonly ContentType[] _contentTypes; 18 | 19 | public FluentSerializerBuilder(ContentType[] contentTypes) 20 | { 21 | _contentTypes = contentTypes; 22 | } 23 | 24 | public FluentDeserializeBuilder Serialize(Func serialize) 25 | { 26 | return new FluentDeserializeBuilder(_contentTypes, serialize); 27 | } 28 | } 29 | 30 | public class FluentDeserializeBuilder 31 | { 32 | private readonly ContentType[] _contentType; 33 | private readonly Func _serialize; 34 | 35 | public FluentDeserializeBuilder(ContentType[] contentType, Func serialize) 36 | { 37 | _contentType = contentType; 38 | _serialize = serialize; 39 | } 40 | 41 | public IContentSerializer Deserialize(Func deserialize) 42 | { 43 | return new CustomSerializer(_contentType, _serialize, deserialize); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /PainlessHttp/Serializers/Defaults/ContentSerializers.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using PainlessHttp.Serializers.Contracts; 3 | using PainlessHttp.Serializers.Typed; 4 | 5 | namespace PainlessHttp.Serializers.Defaults 6 | { 7 | public static class ContentSerializers 8 | { 9 | public static readonly List Defaults = new List 10 | { 11 | new DefaultJsonSerializer(), 12 | new DefaultXmlSerializer(), 13 | new DefaultNoActionSerializer() 14 | }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /PainlessHttp/Serializers/Defaults/DefaultJson.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Runtime.Serialization.Json; 5 | using System.Text; 6 | using PainlessHttp.Http; 7 | using PainlessHttp.Serializers.Contracts; 8 | using PainlessHttp.Serializers.Custom; 9 | 10 | namespace PainlessHttp.Serializers.Defaults 11 | { 12 | public class DefaultJson 13 | { 14 | private static readonly Dictionary CachedSerializers = new Dictionary(); 15 | 16 | public static IContentSerializer GetSerializer() 17 | { 18 | return SerializerBulider 19 | .For(ContentType.ApplicationJson) 20 | .Serialize(Serialize) 21 | .Deserialize(Deserialize); 22 | } 23 | 24 | public static object Deserialize(string data, Type type) 25 | { 26 | var serializer = GetSerializer(type); 27 | 28 | object result; 29 | using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(data))) 30 | { 31 | result = serializer.ReadObject(stream); 32 | } 33 | return result; 34 | } 35 | 36 | public static string Serialize(object data) 37 | { 38 | var type = data.GetType(); 39 | var serializer = GetSerializer(type); 40 | 41 | var stream = new MemoryStream(); 42 | serializer.WriteObject(stream, data); 43 | stream.Position = 0; 44 | 45 | string result; 46 | using (var reader = new StreamReader(stream)) 47 | { 48 | result = reader.ReadToEnd(); 49 | } 50 | stream.Dispose(); 51 | return result; 52 | } 53 | 54 | private static DataContractJsonSerializer GetSerializer(Type type) 55 | { 56 | DataContractJsonSerializer serializer; 57 | if (!CachedSerializers.TryGetValue(type, out serializer)) 58 | { 59 | serializer = new DataContractJsonSerializer(type); 60 | CachedSerializers.Add(type, serializer); 61 | } 62 | return serializer; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /PainlessHttp/Serializers/Defaults/DefaultXml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using System.Xml.Serialization; 6 | using PainlessHttp.Http; 7 | using PainlessHttp.Serializers.Contracts; 8 | using PainlessHttp.Serializers.Custom; 9 | 10 | namespace PainlessHttp.Serializers.Defaults 11 | { 12 | public class DefaultXml 13 | { 14 | private static readonly Dictionary CachedSerializers = new Dictionary(); 15 | 16 | public static IContentSerializer GetSerializer() 17 | { 18 | return SerializerBulider 19 | .For(ContentType.ApplicationXml) 20 | .Serialize(Serialize) 21 | .Deserialize(Deserialize); 22 | } 23 | 24 | public static string Serialize(object data) 25 | { 26 | var type = data.GetType(); 27 | var serializer = GetSerializer(type); 28 | 29 | var stream = new MemoryStream(); 30 | serializer.Serialize(stream, data); 31 | stream.Position = 0; 32 | 33 | string result; 34 | using (var reader = new StreamReader(stream)) 35 | { 36 | result = reader.ReadToEnd(); 37 | } 38 | stream.Dispose(); 39 | return result; 40 | } 41 | 42 | public static XmlSerializer GetSerializer(Type type) 43 | { 44 | XmlSerializer serializer; 45 | if (!CachedSerializers.TryGetValue(type, out serializer)) 46 | { 47 | serializer = new XmlSerializer(type); 48 | CachedSerializers.Add(type, serializer); 49 | } 50 | return serializer; 51 | } 52 | 53 | public static object Deserialize(string data, Type type) 54 | { 55 | var serializer = GetSerializer(type); 56 | 57 | object result; 58 | using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(data))) 59 | { 60 | result = serializer.Deserialize(stream); 61 | } 62 | return result; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /PainlessHttp/Serializers/Typed/DefaultJsonSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Runtime.Serialization; 6 | using System.Runtime.Serialization.Json; 7 | using System.Text; 8 | using System.Text.RegularExpressions; 9 | using PainlessHttp.Http; 10 | using PainlessHttp.Serializers.Contracts; 11 | 12 | namespace PainlessHttp.Serializers.Typed 13 | { 14 | public class DefaultJsonSerializer : IContentSerializer 15 | { 16 | private static IDictionary cachedSerializers; 17 | private readonly IEnumerable _supportedTypes = new List { Http.ContentType.ApplicationJson }; 18 | 19 | private readonly Regex _dateTime = new Regex(@"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{0,7})?[-\+]\d{2}:\d{2}", RegexOptions.Compiled); 20 | private readonly DateTimeFormat _dateTimeFormater = new DateTimeFormat("yyyy-MM-ddTHH:mm:ss"); 21 | private const string _localTimePattern = "yyyy-MM-ddTHH:mm:ss.FFFFFFFzzz"; 22 | 23 | public DefaultJsonSerializer() : this(new Dictionary()) 24 | { /* Do not dublicate code here */} 25 | 26 | public DefaultJsonSerializer(IDictionary preCachedSerializers) 27 | { 28 | cachedSerializers = preCachedSerializers ?? new Dictionary(); 29 | } 30 | 31 | public IEnumerable ContentType 32 | { 33 | get { return _supportedTypes; } 34 | } 35 | 36 | public string Serialize(object data) 37 | { 38 | var type = data.GetType(); 39 | var serializer = GetSerializer(type); 40 | 41 | var stream = new MemoryStream(); 42 | serializer.WriteObject(stream, data); 43 | stream.Position = 0; 44 | 45 | string result; 46 | using (var reader = new StreamReader(stream)) 47 | { 48 | result = reader.ReadToEnd(); 49 | } 50 | stream.Dispose(); 51 | return result; 52 | } 53 | 54 | private DataContractJsonSerializer GetSerializer(Type type) 55 | { 56 | DataContractJsonSerializer serializer; 57 | if (!cachedSerializers.TryGetValue(type, out serializer)) 58 | { 59 | serializer = new DataContractJsonSerializer(type, new DataContractJsonSerializerSettings { DateTimeFormat = _dateTimeFormater }); 60 | cachedSerializers.Add(type, serializer); 61 | } 62 | return serializer; 63 | } 64 | 65 | public T Deserialize(string data) 66 | { 67 | if (string.IsNullOrWhiteSpace(data)) 68 | { 69 | return default(T); 70 | } 71 | 72 | foreach (Match match in _dateTime.Matches(data)) 73 | { 74 | var parsed = DateTime.ParseExact(match.Value, _localTimePattern, DateTimeFormatInfo.CurrentInfo); 75 | data = data.Replace(match.Value, parsed.ToString(_dateTimeFormater.FormatString)); 76 | } 77 | 78 | var serializer = GetSerializer(typeof (T)); 79 | 80 | T result; 81 | using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(data))) 82 | { 83 | result = (T) serializer.ReadObject(stream); 84 | } 85 | return result; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /PainlessHttp/Serializers/Typed/DefaultNoActionSerializer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using PainlessHttp.Http; 3 | using PainlessHttp.Serializers.Contracts; 4 | 5 | namespace PainlessHttp.Serializers.Typed 6 | { 7 | public class DefaultNoActionSerializer : IContentSerializer 8 | { 9 | public IEnumerable ContentType 10 | { 11 | get { return new[] {Http.ContentType.TextPlain, Http.ContentType.TextCsv, Http.ContentType.TextHtml}; } 12 | } 13 | 14 | public string Serialize(object data) 15 | { 16 | return data.ToString(); 17 | } 18 | 19 | public T Deserialize(string data) 20 | { 21 | return default(T); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /PainlessHttp/Serializers/Typed/DefaultXmlSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using System.Xml.Serialization; 6 | using PainlessHttp.Http; 7 | using PainlessHttp.Serializers.Contracts; 8 | 9 | namespace PainlessHttp.Serializers.Typed 10 | { 11 | public class DefaultXmlSerializer : IContentSerializer 12 | { 13 | private static IDictionary cachedSerializers; 14 | 15 | private readonly IEnumerable _supportedTypes = new List { Http.ContentType.ApplicationXml }; 16 | 17 | public IEnumerable ContentType 18 | { 19 | get { return _supportedTypes; } 20 | } 21 | 22 | public DefaultXmlSerializer() : this(new Dictionary()) 23 | { /* Don't duplicate code here*/ } 24 | 25 | public DefaultXmlSerializer(IDictionary preCached) 26 | { 27 | cachedSerializers = preCached; 28 | } 29 | 30 | public string Serialize(object data) 31 | { 32 | var type = data.GetType(); 33 | var serializer = GetSerializer(type); 34 | 35 | var stream = new MemoryStream(); 36 | serializer.Serialize(stream, data); 37 | stream.Position = 0; 38 | 39 | string result; 40 | using (var reader = new StreamReader(stream)) 41 | { 42 | result = reader.ReadToEnd(); 43 | } 44 | stream.Dispose(); 45 | return result; 46 | } 47 | 48 | public XmlSerializer GetSerializer(Type type) 49 | { 50 | XmlSerializer serializer; 51 | if (!cachedSerializers.TryGetValue(type, out serializer)) 52 | { 53 | serializer = new XmlSerializer(type); 54 | cachedSerializers.Add(type, serializer); 55 | } 56 | return serializer; 57 | } 58 | 59 | public T Deserialize(string data) 60 | { 61 | var serializer = GetSerializer(typeof (T)); 62 | 63 | T result; 64 | using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(data))) 65 | { 66 | result = (T) serializer.Deserialize(stream); 67 | } 68 | return result; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /PainlessHttp/Utils/AcceptHeaderMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using PainlessHttp.Http; 6 | 7 | namespace PainlessHttp.Utils 8 | { 9 | public class AcceptHeaderMapper 10 | { 11 | public IEnumerable Map(string acceptHeader) 12 | { 13 | if (string.IsNullOrWhiteSpace(acceptHeader)) 14 | { 15 | return new List(); 16 | } 17 | 18 | return acceptHeader 19 | .Split(',') 20 | .Select(MapToHeaderFields) 21 | .ToList(); 22 | } 23 | 24 | private static AcceptHeaderField MapToHeaderFields(string field) 25 | { 26 | var sections = field 27 | .Split(';') 28 | .Select(s => s.Trim()) 29 | .ToList(); 30 | 31 | if (!sections.Any()) 32 | { 33 | return null; 34 | } 35 | 36 | var result = new AcceptHeaderField 37 | { 38 | ContentType = sections[0], 39 | Q = MapHeaderFieldAttribute(sections.FirstOrDefault(s => s.StartsWith("q", StringComparison.InvariantCultureIgnoreCase))), 40 | Mxb = MapHeaderFieldAttribute(sections.FirstOrDefault(s => s.StartsWith("mxb", StringComparison.InvariantCultureIgnoreCase))), 41 | Mxs = MapHeaderFieldAttribute(sections.FirstOrDefault(s => s.StartsWith("mxs", StringComparison.InvariantCultureIgnoreCase))), 42 | }; 43 | 44 | return result; 45 | } 46 | 47 | private static float MapHeaderFieldAttribute(string header) 48 | { 49 | float result = 0; 50 | if (string.IsNullOrWhiteSpace(header)) 51 | { 52 | return result; 53 | } 54 | var valueIndex = header.IndexOf('=') + 1; 55 | var attrValue = string.Format("0{0}", header.Substring(valueIndex).Trim()); 56 | float.TryParse(attrValue, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out result); 57 | return result; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /PainlessHttp/Utils/ClientUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using System.Reflection; 5 | using PainlessHttp.Client; 6 | 7 | namespace PainlessHttp.Utils 8 | { 9 | public class ClientUtils 10 | { 11 | private const string _painlessAgent = "Painless Http Client"; 12 | private static Version _version; 13 | 14 | public static string GetUserAgent() 15 | { 16 | if (_version == null) 17 | { 18 | _version = Assembly.GetAssembly(typeof (HttpClient)).GetName().Version; 19 | } 20 | return string.Format("{0} {1}", _painlessAgent, _version); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /PainlessHttp/Utils/ContentNegotiator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Net; 4 | using PainlessHttp.Http; 5 | using PainlessHttp.Http.Contracts; 6 | using PainlessHttp.Integration; 7 | 8 | namespace PainlessHttp.Utils 9 | { 10 | internal interface IContentNegotiator 11 | { 12 | bool IsApplicable(IHttpWebResponse rawResponse); 13 | ContentType GetSupportedContentType(IHttpWebResponse rawResponse); 14 | } 15 | 16 | public class ContentNegotiator : IContentNegotiator 17 | { 18 | private readonly IEnumerable _supportedContentTypes; 19 | private readonly AcceptHeaderMapper _mapper; 20 | 21 | public ContentNegotiator(IEnumerable supportedContentTypes) 22 | { 23 | _supportedContentTypes = supportedContentTypes; 24 | _mapper = new AcceptHeaderMapper(); 25 | } 26 | 27 | public bool IsApplicable(IHttpWebResponse rawResponse) 28 | { 29 | if (rawResponse == null) 30 | { 31 | return false; 32 | } 33 | 34 | if (rawResponse.StatusCode != HttpStatusCode.UnsupportedMediaType) 35 | { 36 | return false; 37 | } 38 | 39 | var acceptTypes = GetContentTypeFromAcceptHeader(rawResponse).ToList(); 40 | if (!acceptTypes.Any()) 41 | { 42 | return false; 43 | } 44 | 45 | return _supportedContentTypes.Any(supported => acceptTypes.Any(accept => accept == supported)); 46 | } 47 | 48 | public ContentType GetSupportedContentType(IHttpWebResponse rawResponse) 49 | { 50 | var accepts = GetContentTypeFromAcceptHeader(rawResponse); 51 | var found = accepts.First(accept => _supportedContentTypes.Contains(accept)); 52 | return found; 53 | } 54 | 55 | private IEnumerable GetContentTypeFromAcceptHeader(IHttpWebResponse rawResponse) 56 | { 57 | var acceptHeader = rawResponse.Headers["Accept"]; 58 | if (acceptHeader == null) 59 | { 60 | yield break; 61 | } 62 | 63 | var headers = _mapper.Map(acceptHeader); 64 | foreach (var header in headers) 65 | { 66 | yield return HttpConverter.ContentTypeOrDefault(header.ContentType); 67 | } 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /PainlessHttp/Utils/HttpConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using PainlessHttp.Http; 5 | 6 | namespace PainlessHttp.Utils 7 | { 8 | public class HttpConverter 9 | { 10 | 11 | static readonly List> ContentTypes = new List> 12 | { 13 | new Tuple(Http.ContentType.ApplicationJson, Http.ContentTypes.ApplicationJson), 14 | new Tuple(Http.ContentType.ApplicationXml, Http.ContentTypes.ApplicationXml), 15 | new Tuple(Http.ContentType.TextCsv, Http.ContentTypes.TextCsv), 16 | new Tuple(Http.ContentType.TextHtml, Http.ContentTypes.TextHtml), 17 | new Tuple(Http.ContentType.TextPlain, Http.ContentTypes.TextPlain), 18 | }; 19 | 20 | static readonly List> Methods = new List> 21 | { 22 | new Tuple(Http.HttpMethod.Get, HttpMethods.Get), 23 | new Tuple(Http.HttpMethod.Post, HttpMethods.Post), 24 | new Tuple(Http.HttpMethod.Put, HttpMethods.Put), 25 | new Tuple(Http.HttpMethod.Delete, HttpMethods.Delete), 26 | new Tuple(Http.HttpMethod.Options, HttpMethods.Options), 27 | }; 28 | 29 | public static string ContentType(ContentType type) 30 | { 31 | string result; 32 | if (TryParseContentType(type, out result)) 33 | { 34 | return result; 35 | } 36 | 37 | throw new ArgumentException(string.Format("Unable to convert content type {0} to string.", type)); 38 | } 39 | 40 | public static bool TryParseContentType(ContentType type, out string result) 41 | { 42 | result = ContentTypes 43 | .Where(ct => ct.Item1 == type) 44 | .Select(ct => ct.Item2) 45 | .FirstOrDefault(); 46 | 47 | return result != null; 48 | } 49 | 50 | public static bool TryParseContentType(string type, out ContentType result) 51 | { 52 | result = Http.ContentType.Unknown; 53 | if (string.IsNullOrWhiteSpace(type)) 54 | { 55 | return false; 56 | } 57 | 58 | result = ContentTypes 59 | .Where(ct => type.IndexOf(ct.Item2, StringComparison.InvariantCultureIgnoreCase) != -1) 60 | .Select(ct => ct.Item1) 61 | .FirstOrDefault(); 62 | 63 | return result != Http.ContentType.Unknown; 64 | } 65 | 66 | public static ContentType ContentType(string type) 67 | { 68 | ContentType result; 69 | if (TryParseContentType(type, out result)) 70 | { 71 | return result; 72 | } 73 | 74 | throw new ArgumentException(string.Format("Unable to convert content type {0} to enum.", type)); 75 | } 76 | 77 | public static ContentType ContentTypeOrDefault(string type) 78 | { 79 | ContentType result; 80 | if (TryParseContentType(type, out result)) 81 | { 82 | return result; 83 | } 84 | 85 | return default (ContentType); 86 | } 87 | 88 | public static HttpMethod HttpMethod(string method) 89 | { 90 | var result = Methods 91 | .Where(hm => hm.Item2 == method) 92 | .Select(hm => hm.Item1) 93 | .FirstOrDefault(); 94 | 95 | if (result != Http.HttpMethod.Unknown) 96 | { 97 | return result; 98 | } 99 | 100 | throw new ArgumentException(string.Format("Unable to convert method {0} to string.", method)); 101 | } 102 | 103 | public static string HttpMethod(HttpMethod method) 104 | { 105 | var result = Methods 106 | .Where(hm => hm.Item1 == method) 107 | .Select(hm => hm.Item2) 108 | .FirstOrDefault(); 109 | 110 | if (!string.IsNullOrWhiteSpace(result)) 111 | { 112 | return result; 113 | } 114 | 115 | throw new ArgumentException(string.Format("Unable to convert string {0} to method.", method)); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /PainlessHttp/Utils/UrlBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Dynamic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | 8 | namespace PainlessHttp.Utils 9 | { 10 | public class UrlBuilder 11 | { 12 | private readonly string _baseUrl; 13 | private static readonly Type expandoType = typeof (ExpandoObject); 14 | 15 | public UrlBuilder(string baseUrl) 16 | { 17 | _baseUrl = baseUrl; 18 | } 19 | 20 | public string Build(string relativeUrl = null, object query = null) 21 | { 22 | var builder = new StringBuilder(_baseUrl); 23 | if (!string.IsNullOrWhiteSpace(relativeUrl) && !string.IsNullOrWhiteSpace(_baseUrl)) 24 | { 25 | if (_baseUrl.Last() == '/' && relativeUrl.First() == '/') 26 | { 27 | relativeUrl = relativeUrl.Substring(1); 28 | } 29 | if (_baseUrl.Last() != '/' && relativeUrl.First() != '/') 30 | { 31 | builder.Append('/'); 32 | } 33 | builder.Append(relativeUrl); 34 | } 35 | if (query != null) 36 | { 37 | builder.Append(CreateQueryString(query)); 38 | } 39 | return builder.ToString(); 40 | } 41 | 42 | private static string CreateQueryString(object query) 43 | { 44 | var queryType = query.GetType(); 45 | 46 | if (queryType == expandoType) 47 | { 48 | var dictionary = (IDictionary) query; 49 | if (!dictionary.Keys.Any()) 50 | { 51 | return string.Empty; 52 | } 53 | return dictionary.Keys 54 | .Select(k => k + "=" + dictionary[k]) 55 | .Aggregate((accumilated, delta) => string.Format("{0}&{1}", accumilated, delta)) 56 | .Insert(0, "?"); 57 | } 58 | 59 | var properties = new List(queryType.GetProperties()); 60 | 61 | var queryString = properties 62 | .Select(p => p.Name + "=" + p.GetValue(query)) 63 | .Aggregate((accumilated, delta) => string.Format("{0}&{1}", accumilated, delta)) 64 | .Insert(0, "?"); 65 | 66 | return queryString; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /PainlessHttp/Utils/WebRequestBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Threading.Tasks; 6 | using PainlessHttp.Client; 7 | using PainlessHttp.Http; 8 | using PainlessHttp.Serializers.Contracts; 9 | 10 | namespace PainlessHttp.Utils 11 | { 12 | public class WebRequestBuilder 13 | { 14 | private readonly WebRequestTools _tools; 15 | 16 | public WebRequestBuilder(IEnumerable serializers, ContentType defaultContentType, Action webrequestModifier, IEnumerable credentials) 17 | { 18 | var worker = new WebRequestWorker(webrequestModifier, ClientUtils.CreateCredentials(credentials)); 19 | 20 | _tools = new WebRequestTools 21 | { 22 | DefaultContentType = defaultContentType == ContentType.Unknown ? ContentType.ApplicationJson : defaultContentType, 23 | Serializers = serializers.ToList(), 24 | RequestModifier = webrequestModifier, 25 | Resenders = new List 26 | { 27 | new UnsupportedMediaTypeResender(serializers.ToList(), worker) 28 | }, 29 | RequestWorker = worker 30 | }; 31 | } 32 | 33 | public FluentWebRequestBuilder WithUrl(string url) 34 | { 35 | return new FluentWebRequestBuilder(_tools, url); 36 | } 37 | } 38 | 39 | public class FluentWebRequestBuilder 40 | { 41 | private readonly WebRequestTools _tools; 42 | private readonly string _accept = String.Join(",", ContentTypes.ApplicationJson, ContentTypes.ApplicationXml); 43 | private readonly WebRequestSpecifications _requestSpecs; 44 | 45 | internal FluentWebRequestBuilder(WebRequestTools tools, string url) 46 | { 47 | _tools = tools; 48 | _requestSpecs = new WebRequestSpecifications 49 | { 50 | Url = url, 51 | AcceptHeader = _accept, 52 | }; 53 | } 54 | 55 | public FluentWebRequestBuilder WithMethod(HttpMethod method) 56 | { 57 | _requestSpecs.Method = method; 58 | return this; 59 | } 60 | 61 | public FluentWebRequestBuilder WithPayload(object data, ContentType type) 62 | { 63 | if (data == null) 64 | { 65 | return this; 66 | } 67 | 68 | if (type == ContentType.Negotiated) 69 | { 70 | _requestSpecs .ContentNegotiation = true; 71 | type = _tools.DefaultContentType; 72 | _requestSpecs.ContentType = _tools.DefaultContentType; 73 | } 74 | 75 | _requestSpecs.ContentType = type; 76 | _requestSpecs.Data = data; 77 | var serializer = _tools.Serializers.FirstOrDefault(s => s.ContentType.Contains(type)); 78 | if (serializer == null) 79 | { 80 | throw new Exception(string.Format("No Serializer registered for {0}", type)); 81 | } 82 | _requestSpecs.SerializeData = () => serializer.Serialize(data); 83 | 84 | return this; 85 | } 86 | 87 | public RequestWrapper Prepare() 88 | { 89 | return new RequestWrapper(_requestSpecs, _tools); 90 | } 91 | } 92 | 93 | public class RequestWrapper 94 | { 95 | private readonly WebRequestSpecifications _requestSpecs; 96 | private readonly WebRequestTools _tools; 97 | 98 | internal RequestWrapper(WebRequestSpecifications requestSpecs, WebRequestTools tools) 99 | { 100 | _requestSpecs = requestSpecs; 101 | _tools = tools; 102 | 103 | } 104 | 105 | public async Task PerformAsync() 106 | { 107 | var response = await _tools.RequestWorker.GetResponseAsync(_requestSpecs); 108 | 109 | var resender = _tools.Resenders.FirstOrDefault(r => r.IsApplicable(response)); 110 | if (resender == null) 111 | { 112 | return response; 113 | } 114 | 115 | var resendReponse = await resender.ResendRequestAsync(response, _requestSpecs); 116 | return resendReponse; 117 | } 118 | } 119 | 120 | internal class WebRequestTools 121 | { 122 | public ContentType DefaultContentType { get; set; } 123 | public List Serializers { get; set; } 124 | public Action RequestModifier { get; set; } 125 | public WebRequestWorker RequestWorker { get; set; } 126 | public List Resenders { get; set; } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PainlessHttp 2 | 3 | _No external libraries! No over engineered method signatures! No uber verbose setup! Just a HTTP client that is so easy to use that it won’t ever give you any headache!_ 4 | 5 | * async/sync ``GET``, ``POST``, ``PUT`` and ``DELETE`` 6 | * Content negotiation, so that you don't have to think about AcceptHeaders and all that stuff 7 | * Plugable ``If-Modified-Since`` caches for speeding up your application even more. 8 | * Authentication 9 | * Plugable serializers - use defaults, additional ones (like ``PainlessHttp.Serializers.JsonNet``) or build one yourself 10 | * No external references to NuGets (_just Microsoft Core Libs!_) 11 | 12 | ## Quick introduction 13 | 14 | ```csharp 15 | //instantiate client 16 | var client = new HttpClient("http://painless.pardahlman.se"); 17 | 18 | // create new entity 19 | var tomorrow = new Todo { Description = "Sleep in", IsCompleted = false}; 20 | var created = await client.PostAsync("/api/todos", tomorrow); 21 | 22 | // get it 23 | var response = await client.GetAsync("/api/todos/1"); 24 | var existing = response.Body; 25 | 26 | // update it 27 | existing.IsCompleted = true; 28 | var updated = await client.PutAsync("/api/todos/1", existing); 29 | 30 | // delete it 31 | var deleted = await client.DeleteAsync("/api/todos/1"); 32 | if (deleted.StatusCode == HttpStatusCode.OK) 33 | { 34 | Console.Write("Successfully deleted todo"); 35 | } 36 | ``` 37 | ## Configuration 38 | Want to have greater control over how things are done? Just instantiate the client with a ``Configuration`` object, and you'll have the posibility to change just about everything: 39 | ```csharp 40 | //create config 41 | var config = new Configuration 42 | { 43 | BaseUrl = "http://painless.pardahlman.se", 44 | Advanced = 45 | { 46 | Serializers = new List { new PainlessJsonNet() }, 47 | ModifiedSinceCache = new FileCache(cacheDirectory: Environment.CurrentDirectory), 48 | RequestTimeout = new TimeSpan(days:0, hours:0, minutes:0, seconds:2), 49 | ContentNegotiation = true, 50 | Credentials = new NetworkCredential("pardahlman", "strong-password"), 51 | WebrequestModifier = request => request.Headers.Add("X-Additional-Header", "Each request") 52 | } 53 | }; 54 | var client = new HttpClient(config); 55 | ``` 56 | ### Serializers 57 | Painless Http comes with a set of serializers for the standard formats (``application/xml``, ``application/json``). These serializers are registered in the client by default. This means that if you don't really care about how serialization is done, you can jump to the next section. 58 | 59 | If you want to override serializers, just say so in the configuration object 60 | 61 | There are more ways to create customized serializers. 62 | 63 | ```csharp 64 | var typedJson = new DefaultJsonSerializer(); 65 | var defaultJson = new Serializer(ContentType.ApplicationJson); 66 | var customJson = SerializeSettings 67 | .For(ContentType.ApplicationJson) 68 | .Serialize(NewtonSoft.Serialize) // use NewtonSoft's serializer 69 | .Deserialize(DefaultJson.Deserialize); // ...but the normal deserializer 70 | ``` 71 | 72 | Of course, you can create your own class that implements ``IContentSerializer`` and register that one. 73 | 74 | #### Increase speed with pre-complied serializers 75 | Painless Http wants to perform your requests as fast as possible. That's why all the type-specific serializers are cached in all the default implementations. If you request the same _type_ of object twice, you won't need to pay the penalty of creating a new type-specific serializer. However, in some instances you would want to reduce the overhead of creating the serializer in the first request, too. If you want to speed up things right from the start, just register underlying serializers 76 | ```csharp 77 | var ignores = new XmlAttributeOverrides(); 78 | ignores.Add(typeof(Todo), "Description", new XmlAttributes { XmlIgnore = true }); 79 | var preCompiledSerializers = new Dictionary 80 | { 81 | {typeof(Todo), new XmlSerializer(typeof(Todo), ignores)} 82 | }; 83 | var xmlSerializer = new DefaultXmlSerializer(preCompiledSerializers); 84 | 85 | var xmlTomorrow = xmlSerializer.Serialize(tomorrow); 86 | ``` 87 | 88 | _Note that the Jsonsoft `ContentConverter is not part of the core lib. Download the nuget_ ``PainlessHttp.Serializers.JsonNet``. 89 | 90 | ### Content Negotiation 91 | If the server responds with status code ``UnsupportedMediaType`` or the Accept headers that does not contain the supplied content type, the default behaviour is to resend the request with supported content type (based on Accept headers). This behaviour can be overridden by supplying a content type in the request 92 | ```csharp 93 | var config = new Configuration 94 | { 95 | BaseUrl = "http://localhost:1337/", 96 | Advanced = 97 | { 98 | ContentNegotiation = true 99 | } 100 | }; 101 | var client = new HttpClient(config); 102 | var newTodo = new Todo { Description = "Write tests" }; 103 | var created = _client.PostAsync("/api/todo", newTodo); 104 | ``` 105 | 106 | ### Customizing request 107 | The default behavior of PainlessHttp should satisfy most of the developer out there. However, if you for some reason want to control manipulate properties of the request that is sent, there is a way to do so. With the ``WebrequestModifier`` you get access to the raw request and do all sorts of things, like adding you custom request header. 108 | ```csharp 109 | var config = new HttpClientConfiguration 110 | { 111 | BaseUrl = "http://localhost:1337/", 112 | Advanced = 113 | { 114 | WebrequestModifier = req => req.Headers.Add("X-Custom-Header", "Custom-Value") 115 | } 116 | }; 117 | ``` 118 | 119 | ### Authentication 120 | Authentication is handled with ``System.Het.NetworkCredential``, and is registered in the configuration 121 | ```csharp 122 | var config = new HttpClientConfiguration 123 | { 124 | BaseUrl = "http://localhost:1337/", 125 | Advanced = 126 | { 127 | Credentials = Credentials = new NetworkCredential("pardahlman", "password") 128 | } 129 | }; 130 | ``` 131 | 132 | ### If-Modified-Since Cache 133 | If the entities that you are requesting are large, and the server supports [If-Modified-Since Headers](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html), you can turn caching on the through PainlessHttp to make you application even faster. There are three cache types 134 | * ``NoCache`` (does nothing) 135 | * ``InMemoryCache`` (saves entities in working memory) 136 | * ``FileCache`` (saves entities on disk) 137 | 138 | This is done in the configuration view: 139 | ```csharp 140 | var cacheDirectory = Environment.CurrentDirectory; 141 | var config = new Configuration 142 | { 143 | BaseUrl = "http://localhost:1337/", 144 | Advanced = 145 | { 146 | ModifiedSinceCache = new FileCache(cacheDirectory) 147 | } 148 | }; 149 | ``` 150 | 151 | ### Request Timeout 152 | There is no point in waiting for a response forever. By default, The Painless HttpClient defaults to a 10 second Request Timeout. If you want to change this, just do so in the configuration 153 | ```csharp 154 | var config = new Configuration 155 | { 156 | BaseUrl = "http://localhost:1337/", 157 | Advanced = 158 | { 159 | RequestTimeout = new TimeSpan(days:0, hours:0, minutes:0, seconds:2) 160 | } 161 | }; 162 | ``` 163 | ## Credits 164 | 165 | Author: Pär Dahlman 166 | --------------------------------------------------------------------------------