Flurl.Http initial commit

This commit is contained in:
tmenier 2014-03-27 15:55:56 -05:00
parent c8549bd987
commit 22b1fabed0
20 changed files with 767 additions and 0 deletions

View File

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
namespace Flurl.Http
{
public static class ClientConfigExtensions
{
public static FlurlClient WithTimeout(this FlurlClient client, TimeSpan timespan) {
client.HttpClient.Timeout = timespan;
return client;
}
public static FlurlClient WithTimeout(this string url, TimeSpan timespan) {
return new FlurlClient(url).WithTimeout(timespan);
}
public static FlurlClient WithTimeout(this Url url, TimeSpan timespan) {
return new FlurlClient(url).WithTimeout(timespan);
}
public static FlurlClient WithTimeout(this FlurlClient client, int seconds) {
return client.WithTimeout(TimeSpan.FromSeconds(seconds));
}
public static FlurlClient WithTimeout(this string url, int seconds) {
return new FlurlClient(url).WithTimeout(seconds);
}
public static FlurlClient WithTimeout(this Url url, int seconds) {
return new FlurlClient(url).WithTimeout(seconds);
}
}
}

View File

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using PCLStorage;
namespace Flurl.Http
{
public static class DownloadExtensions
{
public static async Task<string> DownloadAsync(this FlurlClient client, string localFolderPath, string localFileName = null, int bufferSize = 4096) {
if (localFileName == null)
localFileName = client.Url.Path.Split('/').Last();
var folder = await EnsureFolderAsync(localFolderPath);
var file = await folder.CreateFileAsync(localFileName, CreationCollisionOption.ReplaceExisting);
// http://developer.greenbutton.com/downloading-large-files-with-the-net-httpclient
var response = await client.HttpClient.GetAsync(client.Url, HttpCompletionOption.ResponseHeadersRead);
// http://codereview.stackexchange.com/a/18679
using (var httpStream = await response.Content.ReadAsStreamAsync())
using (var fileStream = await file.OpenAsync(FileAccess.ReadAndWrite)) {
await httpStream.CopyToAsync(fileStream, bufferSize);
}
return PortablePath.Combine(localFolderPath, localFileName);
}
public static Task<string> DownloadAsync(this string url, string localFolderPath, string localFileName = null, int bufferSize = 4096) {
return new FlurlClient(url).DownloadAsync(localFolderPath, localFileName, bufferSize);
}
public static Task<string> DownloadAsync(this Url url, string localFolderPath, string localFileName = null, int bufferSize = 4096) {
return new FlurlClient(url).DownloadAsync(localFolderPath, localFileName, bufferSize);
}
private static Task<IFolder> EnsureFolderAsync(string path) {
return FileSystem.Current.LocalStorage.CreateFolderAsync(path, CreationCollisionOption.OpenIfExists);
}
}
}

View File

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<MinimumVisualStudioVersion>10.0</MinimumVisualStudioVersion>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{4C163DD1-318A-42C0-ACF2-9E0D766D8900}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Flurl.Http</RootNamespace>
<AssemblyName>Flurl.Http</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<TargetFrameworkProfile>Profile158</TargetFrameworkProfile>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\Flurl.Http.XML</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<!-- A reference to the entire .NET Framework is automatically included -->
<ProjectReference Include="..\Flurl\Flurl.csproj">
<Project>{70a34167-759e-4902-82e0-e6a84c2ce46f}</Project>
<Name>Flurl</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Compile Include="ClientConfigExtensions.cs" />
<Compile Include="DownloadExtensions.cs" />
<Compile Include="FlurlHttpException.cs" />
<Compile Include="GetExtensions.cs" />
<Compile Include="JsonHelper.cs" />
<Compile Include="PostExtensions.cs" />
<Compile Include="FlurlClient.cs" />
<Compile Include="FlurlHttp.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="FlurlMessageHandler.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.Threading.Tasks">
<HintPath>..\packages\Microsoft.Bcl.Async.1.0.165\lib\portable-net40+sl4+win8+wp71\Microsoft.Threading.Tasks.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Threading.Tasks.Extensions">
<HintPath>..\packages\Microsoft.Bcl.Async.1.0.165\lib\portable-net40+sl4+win8+wp71\Microsoft.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>..\packages\Newtonsoft.Json.6.0.1\lib\portable-net40+sl5+wp80+win8+monotouch+monoandroid\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="PCLStorage">
<HintPath>..\packages\PCLStorage.0.9.4\lib\portable-net45+sl5+wp8+win8+monoandroid+monotouch\PCLStorage.dll</HintPath>
</Reference>
<Reference Include="PCLStorage.Abstractions">
<HintPath>..\packages\PCLStorage.0.9.4\lib\portable-net45+sl5+wp8+win8+monoandroid+monotouch\PCLStorage.Abstractions.dll</HintPath>
</Reference>
<Reference Include="System.IO">
<HintPath>..\packages\Microsoft.Bcl.1.1.6\lib\portable-net40+sl5+win8+wp8\System.IO.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http">
<HintPath>..\packages\Microsoft.Net.Http.2.2.18\lib\portable-net40+sl4+win8+wp71\System.Net.Http.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http.Extensions">
<HintPath>..\packages\Microsoft.Net.Http.2.2.18\lib\portable-net40+sl4+win8+wp71\System.Net.Http.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http.Primitives">
<HintPath>..\packages\Microsoft.Net.Http.2.2.18\lib\portable-net40+sl4+win8+wp71\System.Net.Http.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.Runtime">
<HintPath>..\packages\Microsoft.Bcl.1.1.6\lib\portable-net40+sl5+win8+wp8\System.Runtime.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks">
<HintPath>..\packages\Microsoft.Bcl.1.1.6\lib\portable-net40+sl5+win8+wp8\System.Threading.Tasks.dll</HintPath>
</Reference>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
</Target>
<Import Project="..\packages\Microsoft.Bcl.Build.1.0.10\tools\Microsoft.Bcl.Build.targets" Condition="Exists('..\packages\Microsoft.Bcl.Build.1.0.10\tools\Microsoft.Bcl.Build.targets')" />
<Target Name="EnsureBclBuildImported" BeforeTargets="BeforeBuild" Condition="'$(BclBuildImported)' == ''">
<Error Condition="!Exists('..\packages\Microsoft.Bcl.Build.1.0.10\tools\Microsoft.Bcl.Build.targets')" Text="This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=317567." HelpKeyword="BCLBUILD2001" />
<Error Condition="Exists('..\packages\Microsoft.Bcl.Build.1.0.10\tools\Microsoft.Bcl.Build.targets')" Text="The build restored NuGet packages. Build the project again to include these packages in the build. For more information, see http://go.microsoft.com/fwlink/?LinkID=317568." HelpKeyword="BCLBUILD2002" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

42
Flurl.Http/FlurlClient.cs Normal file
View File

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace Flurl.Http
{
/// <summary>
/// A simple container for a Url and an HttpClient, used for fluent chaining.
/// </summary>
public class FlurlClient
{
public FlurlClient(string url) : this(new Url(url)) { }
public FlurlClient(Url url) {
this.Url = url;
}
private HttpClient _httpClient;
public Url Url { get; private set; }
public HttpClient HttpClient {
get {
if (_httpClient == null) {
var handler = new FlurlMesageHandler();
_httpClient = new HttpClient(handler) {
Timeout = FlurlHttp.DefaultTimeout
};
}
return _httpClient;
}
set {
if (!FlurlHttp.TestMode)
_httpClient = value;
}
}
}
}

55
Flurl.Http/FlurlHttp.cs Normal file
View File

@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace Flurl.Http
{
/// <summary>
/// A static configuration object affecting global behavior Flurl HTTP methods
/// </summary>
public static class FlurlHttp
{
/// <summary>
/// Get or set whether to actually execute HTTP methods
/// </summary>
public static bool TestMode { get; set; }
/// <summary>
/// Gets or sets the default timeout for every HTTP request.
/// </summary>
public static TimeSpan DefaultTimeout { get; set; }
/// <summary>
/// Register a callback to occur immediately before every HTTP request is sent.
/// </summary>
public static Action<HttpRequestMessage> BeforeCall { get; set; }
/// <summary>
/// Register a callback to occur immediately after every HTTP response is received.
/// </summary>
public static Action<HttpRequestMessage, HttpResponseMessage> AfterCall { get; set; }
static FlurlHttp() {
DefaultTimeout = new HttpClient().Timeout;
BeforeCall = req => { };
AfterCall = (req, resp) => { };
}
public static class Testing
{
/// <summary>
/// Gets the last-sent HttpRequestMessage ONLY when TestMode = true
/// </summary>
public static HttpRequestMessage LastRequest { get; internal set; }
/// <summary>
/// Gets the string content of the last-sent HTTP request ONLY when TestMode = true
/// </summary>
public static string LastRequestBody { get; internal set; }
}
}
}

View File

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
namespace Flurl.Http
{
public class FlurlHttpException : HttpRequestException
{
public HttpRequestMessage Request { get; private set; }
public HttpResponseMessage Response { get; private set; }
public FlurlHttpException(HttpRequestMessage request, HttpResponseMessage response, Exception inner)
: base(BuildMessage(request, response, inner), inner)
{
this.Request = request;
this.Response = response;
}
public FlurlHttpException(HttpRequestMessage request, HttpResponseMessage response)
: this(request, response, null) { }
private static string BuildMessage(HttpRequestMessage request, HttpResponseMessage response, Exception inner) {
if (!response.IsSuccessStatusCode)
return string.Format("Request to {0} failed with status {1} ({2}).", request.RequestUri.AbsoluteUri, (int)response.StatusCode, response.ReasonPhrase);
else if (inner != null)
return string.Format("Request to {0} failed. {1}", request.RequestUri.AbsoluteUri, inner.Message);
// in theory we should never get here.
return string.Format("Request to {0} failed.", request.RequestUri.AbsoluteUri);
}
}
}

View File

@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Flurl.Http
{
internal class FlurlMesageHandler : HttpClientHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
FlurlHttp.BeforeCall(request);
if (FlurlHttp.TestMode) {
FlurlHttp.Testing.LastRequest = request;
FlurlHttp.Testing.LastRequestBody = await request.Content.ReadAsStringAsync();
}
HttpResponseMessage response = null;
try {
response = FlurlHttp.TestMode ? GetTestResponse() : await base.SendAsync(request, cancellationToken);
}
catch (TaskCanceledException) {
throw new TimeoutException(string.Format("Request to {0} timed out.", request.RequestUri.AbsoluteUri));
}
catch (Exception ex) {
throw new FlurlHttpException(request, response, ex);
}
finally {
FlurlHttp.AfterCall(request, response);
}
if (!response.IsSuccessStatusCode)
throw new FlurlHttpException(request, response);
return response;
}
private static HttpResponseMessage GetTestResponse() {
return new HttpResponseMessage {
StatusCode = HttpStatusCode.OK,
Content = new StringContent("{ message: 'Hello!' }")
};
}
}
}

View File

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Threading.Tasks;
namespace Flurl.Http
{
public static class GetExtensions
{
public static async Task<T> GetJsonAsync<T>(this FlurlClient client) {
using (var stream = await client.HttpClient.GetStreamAsync(client.Url))
return JsonHelper.ReadJsonFromStream<T>(stream);
}
public static Task<T> GetJsonAsync<T>(this string url) {
return new FlurlClient(url).GetJsonAsync<T>();
}
public static Task<T> GetJsonAsync<T>(this Url url) {
return new FlurlClient(url).GetJsonAsync<T>();
}
public static async Task<dynamic> GetJsonAsync(this FlurlClient client) {
dynamic d = await client.GetJsonAsync<ExpandoObject>();
return d;
}
public static Task<dynamic> GetJsonAsync(this string url) {
return new FlurlClient(url).GetJsonAsync();
}
public static Task<dynamic> GetJsonAsync(this Url url) {
return new FlurlClient(url).GetJsonAsync();
}
}
}

20
Flurl.Http/JsonHelper.cs Normal file
View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
namespace Flurl.Http
{
public static class JsonHelper
{
public static T ReadJsonFromStream<T>(Stream stream) {
// http://james.newtonking.com/json/help/index.html?topic=html/Performance.htm
using (var sr = new StreamReader(stream))
using (var jr = new JsonTextReader(sr)) {
return new JsonSerializer().Deserialize<T>(jr);
}
}
}
}

View File

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace Flurl.Http
{
public static class PostExtensions
{
public static async Task<T> PostJsonAsync<T>(this FlurlClient client, object data) {
var content = (HttpContent)new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8, "application/json");
var resp = await client.HttpClient.PostAsync(client.Url, content);
using (var stream = await resp.Content.ReadAsStreamAsync())
return JsonHelper.ReadJsonFromStream<T>(stream);
}
public static Task<T> PostJsonAsync<T>(this string url, object data) {
return new FlurlClient(url).PostJsonAsync<T>(data);
}
public static Task<T> PostJsonAsync<T>(this Url url, object data) {
return new FlurlClient(url).PostJsonAsync<T>(data);
}
}
}

View File

@ -0,0 +1,30 @@
using System.Resources;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Flurl.Http")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft")]
[assembly: AssemblyProduct("Flurl.Http")]
[assembly: AssemblyCopyright("Copyright © Microsoft 2014")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

15
Flurl.Http/app.config Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Threading.Tasks" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.6.6.0" newVersion="2.6.6.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.6.6.0" newVersion="2.6.6.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Bcl" version="1.1.6" targetFramework="portable-win+net45+sl50+wp80" />
<package id="Microsoft.Bcl.Async" version="1.0.165" targetFramework="portable-win+net45+sl50+wp80" />
<package id="Microsoft.Bcl.Build" version="1.0.10" targetFramework="portable-win+net45+sl50+wp80" />
<package id="Microsoft.Net.Http" version="2.2.18" targetFramework="portable-win+net45+sl50+wp80" />
<package id="Newtonsoft.Json" version="6.0.1" targetFramework="portable-win+net45+sl50+wp80" />
<package id="PCLStorage" version="0.9.4" targetFramework="portable-win+net45+sl50+wp80" />
</packages>

View File

@ -32,11 +32,39 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Newtonsoft.Json.6.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="nunit.framework">
<HintPath>..\packages\NUnit.2.6.3\lib\nunit.framework.dll</HintPath>
</Reference>
<Reference Include="PCLStorage, Version=0.9.4.0, Culture=neutral, PublicKeyToken=286fe515a2c35b64, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\PCLStorage.0.9.4\lib\net45\PCLStorage.dll</HintPath>
</Reference>
<Reference Include="PCLStorage.Abstractions, Version=0.9.4.0, Culture=neutral, PublicKeyToken=286fe515a2c35b64, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\PCLStorage.0.9.4\lib\net45\PCLStorage.Abstractions.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Net.Http.Extensions">
<HintPath>..\packages\Microsoft.Net.Http.2.2.18\lib\net45\System.Net.Http.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http.Formatting, Version=5.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.AspNet.WebApi.Client.5.1.1\lib\net45\System.Net.Http.Formatting.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http.Primitives">
<HintPath>..\packages\Microsoft.Net.Http.2.2.18\lib\net45\System.Net.Http.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http.WebRequest" />
<Reference Include="System.Web.Http, Version=5.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.AspNet.WebApi.Core.5.1.1\lib\net45\System.Web.Http.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
@ -44,20 +72,32 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="HttpTests.cs" />
<Compile Include="ReflectionHelper.cs" />
<Compile Include="UrlBuilderTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Flurl.Http\Flurl.Http.csproj">
<Project>{4c163dd1-318a-42c0-acf2-9e0d766d8900}</Project>
<Name>Flurl.Http</Name>
</ProjectReference>
<ProjectReference Include="..\Flurl\Flurl.csproj">
<Project>{70a34167-759e-4902-82e0-e6a84c2ce46f}</Project>
<Name>Flurl</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Import Project="..\packages\Microsoft.Bcl.Build.1.0.10\tools\Microsoft.Bcl.Build.targets" Condition="Exists('..\packages\Microsoft.Bcl.Build.1.0.10\tools\Microsoft.Bcl.Build.targets')" />
<Target Name="EnsureBclBuildImported" BeforeTargets="BeforeBuild" Condition="'$(BclBuildImported)' == ''">
<Error Condition="!Exists('..\packages\Microsoft.Bcl.Build.1.0.10\tools\Microsoft.Bcl.Build.targets')" Text="This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=317567." HelpKeyword="BCLBUILD2001" />
<Error Condition="Exists('..\packages\Microsoft.Bcl.Build.1.0.10\tools\Microsoft.Bcl.Build.targets')" Text="The build restored NuGet packages. Build the project again to include these packages in the build. For more information, see http://go.microsoft.com/fwlink/?LinkID=317568." HelpKeyword="BCLBUILD2002" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

77
Flurl.Test/HttpTests.cs Normal file
View File

@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Web.Http;
using NUnit.Framework;
using Flurl.Http;
namespace Flurl.Test
{
[TestFixture]
public class HttpTests
{
[Test]
// check that for every FlurlClient extension method, we have an equivalent Url and string extension
public void extension_methods_consistently_supported() {
var allExtMethods = ReflectionHelper.GetAllExtensionMethods(typeof(FlurlClient).Assembly);
var fcExtensions = allExtMethods.Where(m => m.GetParameters()[0].ParameterType == typeof(FlurlClient)).ToArray();
foreach (var method in fcExtensions) {
foreach (var type in new[] { typeof(string), typeof(Url) }) {
if (!allExtMethods.Except(fcExtensions).Any(m => ReflectionHelper.IsEquivalentExtensionMethod(method, m, type))) {
Assert.Fail("No equivalent {0} extension method found for FlurlClient.{1}", type.Name, method.Name);
}
}
}
}
[Test]
public void can_set_timeout() {
var client = "http://www.google.com".WithTimeout(15);
Assert.AreEqual(client.HttpClient.Timeout, TimeSpan.FromSeconds(15));
}
[Test]
public async Task can_download_file() {
var path = await "http://www.google.com".DownloadAsync(@"c:\a\b", "google.txt");
Assert.That(File.Exists(path));
File.Delete(path);
Directory.Delete(@"c:\a", true);
}
[Test]
public async Task can_post_json() {
FlurlHttp.TestMode = true;
await "http://some-api.com".PostJsonAsync<object>(new { a = 1, b = 2 });
Assert.AreEqual(HttpMethod.Post, FlurlHttp.Testing.LastRequest.Method);
Assert.AreEqual("{\"a\":1,\"b\":2}", FlurlHttp.Testing.LastRequestBody);
}
[Test]
public async Task can_get_json_dynamic() {
FlurlHttp.TestMode = false;
var result = await "http://echo.jsontest.com/key/value/one/two".GetJsonAsync();
Assert.AreEqual("value", result.key);
Assert.AreEqual("two", result.one);
}
[Test]
public async Task can_get_json_strongly_typed() {
FlurlHttp.TestMode = false;
var result = await "http://echo.jsontest.com/key/value/one/two".GetJsonAsync<JsonTestData>();
Assert.AreEqual("value", result.key);
Assert.AreEqual("two", result.one);
}
class JsonTestData
{
public string key { get; set; }
public string one { get; set; }
}
}
}

View File

@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace Flurl.Test
{
public static class ReflectionHelper
{
public static MethodInfo[] GetAllExtensionMethods(Assembly asm) {
// http://stackoverflow.com/a/299526/62600
return (
from type in asm.GetTypes()
where type.IsSealed && !type.IsGenericType && !type.IsNested
from method in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
where method.IsDefined(typeof(ExtensionAttribute), false)
select method).ToArray();
}
public static bool IsEquivalentExtensionMethod(MethodInfo method1, MethodInfo method2, Type method2ExtType) {
if (method1.Name != method2.Name)
return false;
if (!AreSameType(method1.ReturnType, method2.ReturnType))
return false;
var genArgs1 = method1.GetGenericArguments();
var genArgs2 = method2.GetGenericArguments();
if (genArgs1.Length != genArgs2.Length)
return false;
for (int i = 0; i < genArgs1.Length; i++) {
if (!AreSameType(genArgs1[i], genArgs2[i]))
return false;
}
var args1 = method1.GetParameters();
var args2 = method2.GetParameters();
if (args1.Length != args2.Length)
return false;
if (!AreSameType(args2[0].ParameterType, method2ExtType))
return false;
for (int i = 1; i < args1.Length; i++) {
if (args1[i].Name != args2[i].Name) return false;
if (!AreSameType(args1[i].ParameterType, args2[i].ParameterType)) return false;
if (args1[i].IsOptional != args2[i].IsOptional) return false;
if (!AreSameValue(args1[i].DefaultValue, args2[i].DefaultValue)) return false;
if (args1[i].IsIn != args2[i].IsIn) return false;
}
return true;
}
public static bool AreSameValue(object a, object b) {
if (a == null && b == null)
return true;
if (a == null ^ b == null)
return false;
// ok, neither is null
return a.Equals(b);
}
public static bool AreSameType(Type a, Type b) {
if (a.IsGenericParameter && b.IsGenericParameter) {
var constraintsA = a.GetGenericParameterConstraints();
var constraintsB = b.GetGenericParameterConstraints();
if (constraintsA.Length != constraintsB.Length)
return false;
for (int i = 0; i < constraintsA.Length; i++) {
if (!AreSameType(constraintsA[i], constraintsB[i]))
return false;
}
return true;
}
if (a.IsGenericType && b.IsGenericType) {
if (a.GetGenericTypeDefinition() != b.GetGenericTypeDefinition())
return false;
var genArgsA = a.GetGenericArguments();
var genArgsB = b.GetGenericArguments();
if (genArgsA.Length != genArgsB.Length)
return false;
for (int i = 0; i < genArgsA.Length; i++) {
if (!AreSameType(genArgsA[i], genArgsB[i]))
return false;
}
return true;
}
return a == b;
}
}
}

15
Flurl.Test/app.config Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Net.Http.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.18.0" newVersion="4.2.18.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@ -1,4 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.AspNet.WebApi.Client" version="5.1.1" targetFramework="net45" />
<package id="Microsoft.AspNet.WebApi.Core" version="5.1.1" targetFramework="net45" />
<package id="Microsoft.Bcl" version="1.1.3" targetFramework="net45" />
<package id="Microsoft.Bcl.Build" version="1.0.10" targetFramework="net45" />
<package id="Microsoft.Net.Http" version="2.2.18" targetFramework="net45" />
<package id="Newtonsoft.Json" version="6.0.1" targetFramework="net45" />
<package id="NUnit" version="2.6.3" targetFramework="net45" />
<package id="PCLStorage" version="0.9.4" targetFramework="net45" />
</packages>

View File

@ -17,6 +17,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
README.md = README.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flurl.Http", "Flurl.Http\Flurl.Http.csproj", "{4C163DD1-318A-42C0-ACF2-9E0D766D8900}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -31,6 +33,10 @@ Global
{81F583F8-1D15-4E0B-8064-EB892042C09A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81F583F8-1D15-4E0B-8064-EB892042C09A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81F583F8-1D15-4E0B-8064-EB892042C09A}.Release|Any CPU.Build.0 = Release|Any CPU
{4C163DD1-318A-42C0-ACF2-9E0D766D8900}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4C163DD1-318A-42C0-ACF2-9E0D766D8900}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4C163DD1-318A-42C0-ACF2-9E0D766D8900}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4C163DD1-318A-42C0-ACF2-9E0D766D8900}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

File diff suppressed because one or more lines are too long