├── .nuget ├── NuGet.exe └── NuGet.Config ├── src ├── Common │ ├── CommonAssemblyInfo.cs │ ├── Error.cs │ ├── CommonResources.Designer.cs │ └── DictionaryExtensions.cs ├── WebApiDoodle.Net.Http.Client.Model │ ├── IDto.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── IPaginatedDto.cs │ ├── PaginatedDto.cs │ ├── WebApiDoodle.Net.Http.Client.Model.nuspec │ └── WebApiDoodle.Net.Http.Client.Model.csproj ├── WebApiDoodle.Net.Http │ ├── packages.config │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── EmptyContent.cs │ ├── Common │ │ └── IEnumerableExtensions.cs │ ├── HttpStringResponseMessage.cs │ ├── Extensions │ │ └── HttpRequestHeadersExtensions.cs │ ├── WebApiDoodle.Net.Http.nuspec │ └── WebApiDoodle.Net.Http.csproj ├── WebApiDoodle.Web │ ├── HttpCommonPropertyKeys.cs │ ├── Filters │ │ ├── IApiKeyAuthorizer.cs │ │ ├── RequireHttpsAttribute.cs │ │ └── InvalidModelStateFilterAttribute.cs │ ├── Controllers │ │ ├── IApiControllerBase.cs │ │ ├── UriParametersAttribute.cs │ │ ├── HttpControllerExecutingContext.cs │ │ └── ApiControllerBase.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── packages.config │ ├── UnauthenticatedRequestContext.cs │ ├── Common │ │ ├── HttpErrorExtensions.cs │ │ ├── HttpErrorConstants.cs │ │ ├── CustomAttributeExtensions.cs │ │ ├── HttpConfigurationExtensions.cs │ │ └── TypeHelper.cs │ ├── ModelBinding │ │ ├── BindCatchAllRouteAttribute.cs │ │ ├── PrincipalParameterBinding.cs │ │ └── CatchAllRouteParameterBinding.cs │ ├── WebApiDoodle.Web.nuspec │ ├── MessageHandlers │ │ ├── RequireHttpsMessageHandler.cs │ │ ├── TimeoutHandler.cs │ │ └── ApiKeyAuthenticationHandler.cs │ ├── Formatting │ │ └── RouteDataMapping.cs │ ├── Routing │ │ └── OptionalRegExConstraint.cs │ ├── Internal │ │ └── HttpParameterBindingExtensions.cs │ └── HttpRequestMessageExtensions.cs ├── WebApiDoodle.Web.WebHostEx │ ├── HttpWebHostPropertyKeys.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── packages.config │ ├── Internal │ │ └── HttpRequestMessageExtensions.cs │ ├── WebApiDoodle.Web.WebHostEx.nuspec │ └── MessageHandlers │ │ ├── UserHostAddressSetterHandler.cs │ │ └── RemoveServerHeaderMessageHandler.cs ├── WebApiDoodle.Testing │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── packages.config │ ├── WebApiDoodle.Testing.nuspec │ ├── MessageHandlers │ │ └── DelegatingHandlerExtensions.cs │ ├── IntegrationTest │ │ └── HttpRequestMessageExtensions.cs │ └── WebApiDoodle.Testing.csproj ├── WebApiDoodle.Web.Hosting.WebHost45 │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── IRouteHandlerProvider.cs │ ├── Routing │ │ └── HostedHttpRouteCollection.cs │ ├── HttpControllerRouteHandler.cs │ ├── packages.config │ ├── WebHostHttpControllerTypeResolver.cs │ ├── WebHostBufferPolicySelector.cs │ ├── WebHostAssembliesResolver.cs │ ├── HttpControllerHandler.cs │ ├── GlobalConfiguration.cs │ └── WebApiDoodle.Web.Hosting.WebHost45.csproj ├── WebApiDoodle.Web.SignalR │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── ApiHubController.cs │ ├── WebApiDoodle.Web.SignalR.nuspec │ └── packages.config ├── WebApiDoodle.Net.Http.Formatting │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── packages.config │ ├── WebApiDoodle.Net.Http.Formatting.nuspec │ ├── HttpClientExtensions.cs │ ├── CSVMediaTypeFormatter.cs │ └── WebApiDoodle.Net.Http.Formatting.csproj ├── WebApiDoodle.Net.Http.Client │ ├── packages.config │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Formatting │ │ ├── DefaultWriterMediaTypeFormatter.cs │ │ └── DefaultMediaTypeFormatterCollection.cs │ ├── HttpApiResponseMessage'1.cs │ ├── WebApiDoodle.Net.Http.Client.nuspec │ ├── HttpApiRequestException.cs │ ├── HttpApiResponseMessage.cs │ ├── QueryStringCollection.cs │ ├── InternalResource.Designer.cs │ ├── Internal │ │ └── UriUtil.cs │ └── HttpApiError.cs ├── WebApiDoodle.Web.Meta │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── BindingInfoAttribute.cs │ ├── WebApiDoodle.Web.Meta.nuspec │ └── WebApiDoodle.Web.Meta.csproj └── NuGetPackageManifests │ └── WebApiDoodle.nuspec ├── samples └── WebApiDoodle.Net.Http.Client.Sample45 │ ├── Global.asax │ ├── RequestCommands │ ├── IRequestCommand.cs │ └── PaginatedRequestCommand.cs │ ├── Models │ ├── Car.cs │ ├── EnumerableExtensions.cs │ ├── PaginatedListExtensions.cs │ ├── PaginatedList.cs │ └── CarContext.cs │ ├── Clients │ ├── ICarsClient.cs │ └── Core │ │ ├── ApiClientContextExtensions.cs │ │ ├── ApiClientConfigurationExpression.cs │ │ └── ApiClientContext.cs │ ├── Filters │ ├── InvalidModelStateFilterAttribute.cs │ └── HttpApiRequestExceptionFilterAttribute.cs │ ├── MessageHandlers │ └── AuthMessageHandler.cs │ ├── packages.config │ ├── Validation │ ├── MaximumAttribute.cs │ └── MinimumAttribute.cs │ ├── Web.Debug.config │ ├── Web.Release.config │ ├── Properties │ └── AssemblyInfo.cs │ ├── Web.config │ ├── Controllers │ ├── Server │ │ └── CarsController.cs │ └── Client │ │ └── CarsClientController.cs │ └── Global.asax.cs ├── scripts ├── build.ps1 ├── publish.ps1 └── WebAPIDoodle.msbuild ├── .gitignore ├── tests ├── WebApiDoodle.Test │ ├── xUnit │ │ ├── GCForce.cs │ │ ├── NullUpCurrentPrincipalAttribute.cs │ │ └── PreserveSyncContextAttribute.cs │ ├── packages.config │ ├── Routes │ │ └── OptionalRegExConstraintTest.cs │ ├── Controllers │ │ └── Apis │ │ │ └── UsersController.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── MessageHandlers │ │ └── RequireHttpsMessageHandlerTest.cs │ ├── TestHelpers │ │ └── TestHelper.cs │ ├── Filters │ │ └── ValidateModelStateAttributeTest.cs │ └── Util │ │ └── ContextUtil.cs ├── WebApiDoodle.Net.Http.Client.Test │ ├── packages.config │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Internal │ │ └── UriUtilTest.cs │ └── WebApiDoodle.Net.Http.Client.Test.csproj └── Common │ └── TaskHelpersTest.cs ├── README.md └── LICENSE.md /.nuget/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tugberkugurlu/WebAPIDoodle/HEAD/.nuget/NuGet.exe -------------------------------------------------------------------------------- /src/Common/CommonAssemblyInfo.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tugberkugurlu/WebAPIDoodle/HEAD/src/Common/CommonAssemblyInfo.cs -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http.Client.Model/IDto.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace WebApiDoodle.Net.Http.Client.Model { 3 | 4 | public interface IDto { 5 | 6 | } 7 | } -------------------------------------------------------------------------------- /samples/WebApiDoodle.Net.Http.Client.Sample45/Global.asax: -------------------------------------------------------------------------------- 1 | <%@ Application Codebehind="Global.asax.cs" Inherits="WebApiDoodle.Net.Http.Client.Sample45.Global" Language="C#" %> -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.nuget/NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http.Client.Model/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | [assembly: AssemblyTitle("WebApiDoodle.Net.Http.Client.Model")] 4 | [assembly: AssemblyDescription("WebApiDoodle.Net.Http.Client.Model...")] -------------------------------------------------------------------------------- /src/WebApiDoodle.Web/HttpCommonPropertyKeys.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace WebApiDoodle.Web { 3 | 4 | public static class HttpCommonPropertyKeys { 5 | 6 | public const string UserHostAddressKey = "WD_UserHostAddress"; 7 | } 8 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Web.WebHostEx/HttpWebHostPropertyKeys.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace WebApiDoodle.Web.WebHostEx { 3 | 4 | public static class HttpWebHostPropertyKeys { 5 | 6 | public const string HttpContextBaseKey = "MS_HttpContext"; 7 | } 8 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | [assembly: AssemblyTitle("WebApiDoodle.Net.Http")] 4 | [assembly: AssemblyDescription("WebApiDoodle.Net.Http is a library which provides more features over System.Net.Http")] -------------------------------------------------------------------------------- /src/WebApiDoodle.Web/Filters/IApiKeyAuthorizer.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace WebApiDoodle.Web.Filters { 3 | 4 | public interface IApiKeyAuthorizer { 5 | 6 | bool IsAuthorized(string apiKey); 7 | bool IsAuthorized(string apiKey, string[] roles); 8 | } 9 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Testing/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | [assembly: AssemblyTitle("WebApiDoodle.Testing")] 4 | [assembly: AssemblyDescription("WebApiDoodle.Testing is an ASP.NET Web API testing helper library which makes it easy to test ASP.NET Web API components.")] -------------------------------------------------------------------------------- /src/WebApiDoodle.Web.Hosting.WebHost45/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | [assembly: AssemblyTitle("WebApiDoodle.Web.Hosting.WebHost45")] 4 | [assembly: AssemblyDescription("WebApiDoodle.Web.Hosting.WebHost45 provides an ASP.NET 4.5 host layer for ASP.NET Web API.")] -------------------------------------------------------------------------------- /src/WebApiDoodle.Web.SignalR/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | [assembly: AssemblyTitle("WebApiDoodle.Web.SignalR")] 4 | [assembly: AssemblyDescription("WebApiDoodle.Web.SignalR is an ASP.NET Web API library which contains some SignalR integrated ASP.NET Web API stuff.")] -------------------------------------------------------------------------------- /src/WebApiDoodle.Web.WebHostEx/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | [assembly: AssemblyTitle("WebApiDoodle.Web.WebHostEx")] 4 | [assembly: AssemblyDescription("WebApiDoodle.Web.WebHostEx is an ASP.NET Web API extensibility library which is specific for ASP.NET Web API WebHost")] -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http.Formatting/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | [assembly: AssemblyTitle("WebApiDoodle.Net.Http.Formatting")] 4 | [assembly: AssemblyDescription("WebApiDoodle.Net.Http.Formatting is a library which contains several formatters for System.Net.Http.Formatting.")] -------------------------------------------------------------------------------- /samples/WebApiDoodle.Net.Http.Client.Sample45/RequestCommands/IRequestCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace WebApiDoodle.Net.Http.Client.Sample45.RequestCommands { 7 | 8 | public interface IRequestCommand { 9 | } 10 | } -------------------------------------------------------------------------------- /scripts/build.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | $buildFile = (join-path (Split-Path -parent $MyInvocation.MyCommand.Definition) "WebAPIDoodle.msbuild"), 3 | $buildParams = "/p:Configuration=Release", 4 | $buildTarget = "/t:Default" 5 | ) 6 | 7 | & "$(get-content env:windir)\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe" $buildFile $buildParams $buildTarget -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http/EmptyContent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using System.Text; 6 | 7 | namespace WebApiDoodle.Net.Http { 8 | 9 | public class EmptyContent : StringContent { 10 | 11 | public EmptyContent() : base(string.Empty) { } 12 | } 13 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Web/Controllers/IApiControllerBase.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | 3 | namespace WebApiDoodle.Web.Controllers { 4 | 5 | public interface IApiControllerBase { 6 | 7 | void OnControllerExecuting(HttpControllerExecutingContext controllerExecutingContext); 8 | void OnControllerExecuted(HttpResponseMessage response); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | Bin 3 | obj 4 | sql 5 | *.user 6 | *.suo 7 | _ReSharper* 8 | dist 9 | [Oo]utput] 10 | *.dbmdl 11 | 12 | *~ 13 | 14 | docs 15 | packages 16 | Artifacts 17 | 18 | *.swp 19 | TestResult.xml 20 | *.testsettings 21 | TestResults 22 | 23 | *.dropbox 24 | desktop.ini 25 | *.publish.xml 26 | 27 | *.mdf 28 | *.ldf 29 | 30 | #temp 31 | *.app.config 32 | [Aa]pp.config -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http.Client/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Web.Meta/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | [assembly: AssemblyTitle("WebApiDoodle.Web.Meta")] 4 | [assembly: AssemblyDescription("WebApiDoodle.Web.Meta contains some runtime components such as Attributes, Interfaces. This assembly has no dependecny on ASP.NET Web API so that it would be easy to reference this on Model or Domain Layer projects")] -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http.Formatting/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Web/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | 4 | [assembly: AssemblyTitle("WebApiDoodle.Web")] 5 | [assembly: AssemblyDescription("WebApiDoodle.Web is an ASP.NET Web API library which contains several useful Extensions, Filters, Message Handlers and so on and so forth.")] 6 | [assembly: InternalsVisibleTo("WebAPIDoodle.Test")] -------------------------------------------------------------------------------- /src/WebApiDoodle.Web.Hosting.WebHost45/IRouteHandlerProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Web.Routing; 7 | 8 | namespace WebApiDoodle.Web.Hosting.WebHost45 { 9 | 10 | public interface IRouteHandlerProvider { 11 | 12 | IRouteHandler GetRouteHandler(); 13 | } 14 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http.Client/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | 4 | [assembly: AssemblyTitle("WebApiDoodle.Net.Http.Client")] 5 | [assembly: AssemblyDescription("WebApiDoodle.Net.Http.Client is an ASP.NET Web API library which contains several useful Extensions, Filters, Message Handlers and so on and so forth.")] 6 | [assembly: InternalsVisibleTo("WebApiDoodle.Net.Http.Client.Test")] -------------------------------------------------------------------------------- /src/WebApiDoodle.Web/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Testing/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Web/UnauthenticatedRequestContext.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | 3 | namespace WebApiDoodle.Web { 4 | 5 | public class UnauthenticatedRequestContext { 6 | 7 | public HttpRequestMessage Request { get; private set; } 8 | public HttpResponseMessage Response { get; set; } 9 | 10 | public UnauthenticatedRequestContext(HttpRequestMessage request) { 11 | 12 | Request = request; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /tests/WebApiDoodle.Test/xUnit/GCForce.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using Xunit; 7 | 8 | namespace WebApiDoodle.Web.Test { 9 | 10 | public class GCForce : BeforeAfterTestAttribute { 11 | 12 | public override void After(MethodInfo methodUnderTest) { 13 | 14 | GC.Collect(99); 15 | GC.Collect(99); 16 | GC.Collect(99); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http/Common/IEnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace System.Collections.Generic { 7 | 8 | internal static class IEnumerableExtensions { 9 | 10 | public static void ForEach(this IEnumerable enumerable, Action action) { 11 | 12 | foreach (var item in enumerable) { 13 | action(item); 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/WebApiDoodle.Net.Http.Client.Test/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Web/Common/HttpErrorExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Web.Http; 6 | 7 | namespace System.Web.Http { 8 | 9 | internal static class HttpErrorExtensions { 10 | 11 | internal static HttpError AddAndReturn(this HttpError httpError, string key, object value) { 12 | 13 | httpError.Add(key, value); 14 | return httpError; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Web.Hosting.WebHost45/Routing/HostedHttpRouteCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Web.Http; 7 | using System.Web.Routing; 8 | 9 | namespace WebApiDoodle.Web.Hosting.WebHost45.Routing { 10 | 11 | public class HostedHttpRouteCollection : HttpRouteCollection { 12 | 13 | public HostedHttpRouteCollection(RouteCollection routes) { 14 | 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/WebApiDoodle.Test/xUnit/NullUpCurrentPrincipalAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Threading; 7 | using Xunit; 8 | 9 | namespace WebApiDoodle.Web.Test { 10 | 11 | public class NullUpCurrentPrincipalAttribute : BeforeAfterTestAttribute { 12 | 13 | public override void Before(MethodInfo methodUnderTest) { 14 | 15 | Thread.CurrentPrincipal = null; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http.Client.Model/IPaginatedDto.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace WebApiDoodle.Net.Http.Client.Model { 4 | 5 | public interface IPaginatedDto where TDto : IDto { 6 | 7 | int PageIndex { get; set; } 8 | int PageSize { get; set; } 9 | int TotalCount { get; set; } 10 | int TotalPageCount { get; set; } 11 | 12 | bool HasNextPage { get; set; } 13 | bool HasPreviousPage { get; set; } 14 | 15 | IEnumerable Items { get; } 16 | } 17 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Web.Hosting.WebHost45/HttpControllerRouteHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Web; 7 | using System.Web.Routing; 8 | 9 | namespace WebApiDoodle.Web.Hosting.WebHost45 { 10 | 11 | public class HttpControllerRouteHandler : IRouteHandler { 12 | 13 | public IHttpHandler GetHttpHandler(RequestContext requestContext) { 14 | 15 | throw new NotImplementedException(); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Web.Hosting.WebHost45/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Web.Hosting.WebHost45/WebHostHttpControllerTypeResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Web.Http.Dispatcher; 7 | 8 | namespace WebApiDoodle.Web.Hosting.WebHost45 { 9 | 10 | public class WebHostHttpControllerTypeResolver : IHttpControllerTypeResolver { 11 | 12 | public ICollection GetControllerTypes(IAssembliesResolver assembliesResolver) { 13 | 14 | throw new NotImplementedException(); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http.Client.Model/PaginatedDto.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace WebApiDoodle.Net.Http.Client.Model { 4 | 5 | public class PaginatedDto : IPaginatedDto where TDto : IDto { 6 | 7 | public int PageIndex { get; set; } 8 | public int PageSize { get; set; } 9 | public int TotalCount { get; set; } 10 | public int TotalPageCount { get; set; } 11 | 12 | public bool HasNextPage { get; set; } 13 | public bool HasPreviousPage { get; set; } 14 | 15 | public IEnumerable Items { get; set; } 16 | } 17 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http/HttpStringResponseMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Net.Http; 6 | using System.Net; 7 | 8 | namespace WebApiDoodle.Net.Http { 9 | 10 | public class HttpStringResponseMessage : HttpResponseMessage { 11 | 12 | public HttpStringResponseMessage(string content) { 13 | 14 | base.Content = new StringContent(content); 15 | } 16 | 17 | public HttpStringResponseMessage(string content, HttpStatusCode statusCode) : this(content) { 18 | 19 | base.StatusCode = statusCode; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Web.WebHostEx/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Web.WebHostEx/Internal/HttpRequestMessageExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Web; 3 | 4 | namespace WebApiDoodle.Web.WebHostEx { 5 | 6 | internal static class HttpRequestMessageExtensions { 7 | 8 | internal static HttpContextBase GetHttpContext(this HttpRequestMessage request) { 9 | 10 | return request.GetProperty(HttpWebHostPropertyKeys.HttpContextBaseKey); 11 | } 12 | 13 | internal static string GetUserHostAddress(this HttpRequestMessage request) { 14 | 15 | return request.GetHttpContext().Request.UserHostAddress; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /samples/WebApiDoodle.Net.Http.Client.Sample45/Models/Car.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using WebApiDoodle.Net.Http.Client.Model; 3 | 4 | namespace WebApiDoodle.Net.Http.Client.Sample45.Models { 5 | 6 | public class Car : IDto { 7 | 8 | public int Id { get; set; } 9 | 10 | [Required] 11 | [StringLength(20)] 12 | public string Make { get; set; } 13 | 14 | [Required] 15 | [StringLength(20)] 16 | public string Model { get; set; } 17 | 18 | public int Year { get; set; } 19 | 20 | [Range(0, 500000)] 21 | public float Price { get; set; } 22 | } 23 | } -------------------------------------------------------------------------------- /samples/WebApiDoodle.Net.Http.Client.Sample45/Models/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace WebApiDoodle.Net.Http.Client.Sample45.Models { 5 | 6 | public static class EnumerableExtensions { 7 | 8 | public static PaginatedList ToPaginatedList( 9 | this IEnumerable query, int pageIndex, int pageSize) { 10 | 11 | var totalCount = query.Count(); 12 | var collection = query.Skip((pageIndex - 1) * pageSize).Take(pageSize); 13 | 14 | return new PaginatedList(pageIndex, pageSize, totalCount, collection); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Web.SignalR/ApiHubController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Web.Http; 6 | using SignalR; 7 | using SignalR.Hubs; 8 | 9 | namespace WebApiDoodle.Web.SignalR { 10 | 11 | public abstract class ApiHubController : ApiController where THub : Hub { 12 | 13 | Lazy _hub = new Lazy( 14 | () => GlobalHost.ConnectionManager.GetHubContext() 15 | ); 16 | 17 | protected IHubContext Hub { 18 | get { 19 | return _hub.Value; 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Web/ModelBinding/BindCatchAllRouteAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Http; 2 | using System.Web.Http.Controllers; 3 | 4 | namespace WebApiDoodle.Web.ModelBinding { 5 | 6 | public class BindCatchAllRouteAttribute : ParameterBindingAttribute { 7 | 8 | private readonly char _delimiter; 9 | 10 | public BindCatchAllRouteAttribute(char delimiter) { 11 | 12 | _delimiter = delimiter; 13 | } 14 | 15 | public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter) { 16 | 17 | return new CatchAllRouteParameterBinding(parameter, _delimiter); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http.Client/Formatting/DefaultWriterMediaTypeFormatter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http.Formatting; 3 | 4 | namespace WebApiDoodle.Net.Http.Client.Formatting { 5 | 6 | internal sealed class DefaultWriterMediaTypeFormatter : JsonMediaTypeFormatter { 7 | 8 | private static readonly Lazy lazy = 9 | new Lazy(() => new DefaultWriterMediaTypeFormatter()); 10 | 11 | public static DefaultWriterMediaTypeFormatter Instance { get { return lazy.Value; } } 12 | 13 | private DefaultWriterMediaTypeFormatter() { 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Web.Meta/BindingInfoAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WebApiDoodle.Web.Meta { 4 | 5 | /// 6 | /// Provides the binding information for the properties of the complex type action parameters. 7 | /// 8 | [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] 9 | public class BindingInfoAttribute : Attribute { 10 | 11 | private bool _noBinding = false; 12 | 13 | public bool NoBinding { 14 | get { 15 | return _noBinding; 16 | } 17 | set { 18 | _noBinding = value; 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /tests/WebApiDoodle.Test/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Web.Meta/WebApiDoodle.Web.Meta.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | $author$ 8 | $author$ 9 | https://raw.github.com/WebAPIDoodle/WebAPIDoodle/master/LICENSE.md 10 | https://github.com/WebAPIDoodle/WebAPIDoodle 11 | false 12 | $description$ 13 | Copyright $author$ 2012 14 | http httpclient webapi aspnetwebapi 15 | 16 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http/Extensions/HttpRequestHeadersExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace System.Net.Http.Headers { 8 | 9 | [EditorBrowsable(EditorBrowsableState.Never)] 10 | public static class HttpRequestHeadersExtensions { 11 | 12 | public static IEnumerable> ParseToSingleValueKeyValuePairs(this HttpRequestHeaders headers) { 13 | 14 | return headers.Select(x => 15 | new KeyValuePair( 16 | x.Key, x.Value.FirstOrDefault())); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http/WebApiDoodle.Net.Http.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | $author$ 8 | $author$ 9 | https://raw.github.com/WebAPIDoodle/WebAPIDoodle/master/LICENSE.md 10 | https://github.com/WebAPIDoodle/WebAPIDoodle 11 | false 12 | $description$ 13 | Copyright $author$ 2012 14 | http client httpclient webapi aspnetwebapi 15 | 16 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Web.SignalR/WebApiDoodle.Web.SignalR.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | $author$ 8 | $author$ 9 | https://raw.github.com/WebAPIDoodle/WebAPIDoodle/master/LICENSE.md 10 | https://github.com/WebAPIDoodle/WebAPIDoodle 11 | false 12 | $description$ 13 | Copyright Tugberk Ugurlu 2012 14 | aspnetwebapi aspnet webapi signalr 15 | 16 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Testing/WebApiDoodle.Testing.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | $author$ 8 | $author$ 9 | https://raw.github.com/WebAPIDoodle/WebAPIDoodle/master/LICENSE.md 10 | https://github.com/WebAPIDoodle/WebAPIDoodle 11 | false 12 | $description$ 13 | Copyright $author$ 2012 14 | aspnetwebapi aspnet webapi testing 15 | 16 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http.Formatting/WebApiDoodle.Net.Http.Formatting.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | $author$ 8 | $author$ 9 | https://raw.github.com/WebAPIDoodle/WebAPIDoodle/master/LICENSE.md 10 | https://github.com/WebAPIDoodle/WebAPIDoodle 11 | false 12 | $description$ 13 | Copyright $author$ 2012 14 | aspnetwebapi aspnet webapi formatting 15 | 16 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Web.Hosting.WebHost45/WebHostBufferPolicySelector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Web.Http.Hosting; 8 | 9 | namespace WebApiDoodle.Web.Hosting.WebHost45 { 10 | 11 | public class WebHostBufferPolicySelector : IHostBufferPolicySelector { 12 | 13 | public bool UseBufferedInputStream(object hostContext) { 14 | 15 | throw new NotImplementedException(); 16 | } 17 | 18 | public bool UseBufferedOutputStream(HttpResponseMessage response) { 19 | 20 | throw new NotImplementedException(); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http.Client.Model/WebApiDoodle.Net.Http.Client.Model.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | $author$ 8 | $author$ 9 | https://raw.github.com/WebAPIDoodle/WebAPIDoodle/master/LICENSE.md 10 | https://github.com/WebAPIDoodle/WebAPIDoodle 11 | false 12 | $description$ 13 | Copyright $author$ 2012 14 | http client httpclient webapi aspnetwebapi dotnet 15 | 16 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Web/Filters/RequireHttpsAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.Web.Http.Controllers; 5 | using System.Web.Http.Filters; 6 | 7 | namespace WebApiDoodle.Web.Filters { 8 | 9 | public class RequireHttpsAttribute : AuthorizationFilterAttribute { 10 | 11 | public override void OnAuthorization(HttpActionContext actionContext) { 12 | 13 | if (actionContext.Request.RequestUri.Scheme != Uri.UriSchemeHttps) { 14 | 15 | actionContext.Response = new HttpResponseMessage(HttpStatusCode.Forbidden) { 16 | ReasonPhrase = "Forbidden (SSL Required)" 17 | }; 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Web/Common/HttpErrorConstants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace System.Web.Http { 7 | 8 | internal static class HttpErrorConstants { 9 | 10 | internal const string MessageKey = "Message"; 11 | internal const string MessageDetailKey = "MessageDetail"; 12 | internal const string ModelStateKey = "ModelState"; 13 | internal const string ExceptionMessageKey = "ExceptionMessage"; 14 | internal const string ExceptionTypeKey = "ExceptionType"; 15 | internal const string StackTraceKey = "StackTrace"; 16 | internal const string InnerExceptionKey = "InnerException"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/WebApiDoodle.Net.Http.Client.Sample45/Clients/ICarsClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System.Web; 6 | using WebApiDoodle.Net.Http.Client.Model; 7 | using WebApiDoodle.Net.Http.Client.Sample45.Models; 8 | using WebApiDoodle.Net.Http.Client.Sample45.RequestCommands; 9 | 10 | namespace WebApiDoodle.Net.Http.Client.Sample45.Clients { 11 | 12 | public interface ICarsClient { 13 | 14 | Task> GetCars(PaginatedRequestCommand paginationCmd); 15 | Task GetCar(int carId); 16 | Task AddCar(Car car); 17 | Task UpdateCar(int carId, Car car); 18 | Task RemoveCar(int carId); 19 | } 20 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http.Client/HttpApiResponseMessage'1.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | 3 | namespace WebApiDoodle.Net.Http.Client { 4 | 5 | public class HttpApiResponseMessage : HttpApiResponseMessage { 6 | 7 | public HttpApiResponseMessage(HttpResponseMessage response) 8 | : base(response) { 9 | } 10 | 11 | public HttpApiResponseMessage(HttpResponseMessage response, HttpApiError httpError) 12 | : base(response, httpError) { 13 | } 14 | 15 | public HttpApiResponseMessage(HttpResponseMessage response, TModel model) : 16 | base(response) { 17 | 18 | Model = model; 19 | } 20 | 21 | public TModel Model { get; private set; } 22 | } 23 | } -------------------------------------------------------------------------------- /tests/WebApiDoodle.Test/xUnit/PreserveSyncContextAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Threading; 7 | using Xunit; 8 | 9 | namespace WebApiDoodle.Web.Test { 10 | 11 | public class PreserveSyncContextAttribute : BeforeAfterTestAttribute { 12 | 13 | private SynchronizationContext _syncContext; 14 | 15 | public override void Before(MethodInfo methodUnderTest) { 16 | 17 | _syncContext = SynchronizationContext.Current; 18 | } 19 | 20 | public override void After(MethodInfo methodUnderTest) { 21 | 22 | SynchronizationContext.SetSynchronizationContext(_syncContext); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /samples/WebApiDoodle.Net.Http.Client.Sample45/RequestCommands/PaginatedRequestCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using WebApiDoodle.Net.Http.Client.Sample45.Validation; 6 | 7 | namespace WebApiDoodle.Net.Http.Client.Sample45.RequestCommands { 8 | 9 | public class PaginatedRequestCommand : IRequestCommand { 10 | 11 | public PaginatedRequestCommand() { 12 | } 13 | 14 | public PaginatedRequestCommand(int page, int take) { 15 | Page = page; 16 | Take = take; 17 | } 18 | 19 | [Minimum(1)] 20 | public int Page { get; set; } 21 | 22 | [Maximum(50)] 23 | [Minimum(1)] 24 | public int Take { get; set; } 25 | } 26 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Web.SignalR/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Web/WebApiDoodle.Web.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | $author$ 8 | $author$ 9 | https://raw.github.com/WebAPIDoodle/WebAPIDoodle/master/LICENSE.md 10 | https://github.com/WebAPIDoodle/WebAPIDoodle 11 | false 12 | $description$ 13 | Copyright $author$ 2012 14 | aspnetwebapi aspnet webapi 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http.Client/Formatting/DefaultMediaTypeFormatterCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.ObjectModel; 3 | using System.Net.Http.Formatting; 4 | 5 | namespace WebApiDoodle.Net.Http.Client.Formatting { 6 | 7 | internal sealed class DefaultMediaTypeFormatterCollection : ReadOnlyCollection { 8 | 9 | private static readonly Lazy lazy = 10 | new Lazy(() => new DefaultMediaTypeFormatterCollection()); 11 | 12 | public static DefaultMediaTypeFormatterCollection Instance { get { return lazy.Value; } } 13 | 14 | private DefaultMediaTypeFormatterCollection() 15 | : base(new MediaTypeFormatterCollection()) { 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Web/Controllers/UriParametersAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WebApiDoodle.Web.Controllers { 4 | 5 | [Obsolete("UriParametersAttribute is no more in use. This attribute will be removed at some time in the future.")] 6 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] 7 | public class UriParametersAttribute : Attribute { 8 | 9 | private static readonly string[] _emptyArray = new string[0]; 10 | private readonly string[] _paramsArray; 11 | 12 | public UriParametersAttribute(params string[] parameters) { 13 | 14 | _paramsArray = parameters; 15 | } 16 | 17 | public string[] Parameters { 18 | get { 19 | return _paramsArray; 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Web.WebHostEx/WebApiDoodle.Web.WebHostEx.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | $author$ 8 | $author$ 9 | https://raw.github.com/WebAPIDoodle/WebAPIDoodle/master/LICENSE.md 10 | https://github.com/WebAPIDoodle/WebAPIDoodle 11 | false 12 | $description$ 13 | Copyright $author$ 2012 14 | aspnetwebapi aspnet $author$ webhost 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Web/Filters/InvalidModelStateFilterAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.Web.Http.Controllers; 5 | using System.Web.Http.Filters; 6 | 7 | namespace WebApiDoodle.Web.Filters { 8 | 9 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] 10 | public class InvalidModelStateFilterAttribute : ActionFilterAttribute { 11 | 12 | public override void OnActionExecuting(HttpActionContext actionContext) { 13 | 14 | if (!actionContext.ModelState.IsValid) { 15 | 16 | actionContext.Response = actionContext.Request.CreateErrorResponse( 17 | HttpStatusCode.BadRequest, actionContext.ModelState); 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http.Client/WebApiDoodle.Net.Http.Client.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | $author$ 8 | $author$ 9 | https://raw.github.com/WebAPIDoodle/WebAPIDoodle/master/LICENSE.md 10 | https://github.com/WebAPIDoodle/WebAPIDoodle 11 | false 12 | $description$ 13 | Copyright $author$ 2012 14 | http client httpclient webapi aspnetwebapi dotnet 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /samples/WebApiDoodle.Net.Http.Client.Sample45/Filters/InvalidModelStateFilterAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Web.Http.Controllers; 4 | using System.Web.Http.Filters; 5 | using System.Net.Http; 6 | 7 | namespace WebApiDoodle.Net.Http.Client.Sample45.Filters { 8 | 9 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] 10 | public class InvalidModelStateFilterAttribute : ActionFilterAttribute { 11 | 12 | public override void OnActionExecuting(HttpActionContext actionContext) { 13 | 14 | if (!actionContext.ModelState.IsValid) { 15 | 16 | actionContext.Response = actionContext.Request.CreateErrorResponse( 17 | HttpStatusCode.BadRequest, actionContext.ModelState); 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Web.WebHostEx/MessageHandlers/UserHostAddressSetterHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace WebApiDoodle.Web.WebHostEx.MessageHandlers { 6 | 7 | /// 8 | /// A delegating handler which sets the user's IP Address inside the Properties dictionaty of the 9 | /// current instance. 10 | /// 11 | public class UserHostAddressSetterHandler : DelegatingHandler { 12 | 13 | protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { 14 | 15 | request.Properties[HttpCommonPropertyKeys.UserHostAddressKey] = request.GetUserHostAddress(); 16 | return base.SendAsync(request, cancellationToken); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http.Formatting/HttpClientExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace WebApiDoodle.Net.Http.Formatting { 10 | 11 | public static class HttpClientExtensions { 12 | 13 | public static Task PostAsCsvAsync(this HttpClient client, string requestUri, T value) { 14 | 15 | return client.PostAsCsvAsync(requestUri, value, CancellationToken.None); 16 | } 17 | 18 | public static Task PostAsCsvAsync(this HttpClient client, string requestUri, T value, CancellationToken cancellationToken) { 19 | 20 | return client.PostAsync(requestUri, value, new CSVMediaTypeFormatter(), cancellationToken); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /samples/WebApiDoodle.Net.Http.Client.Sample45/Clients/Core/ApiClientContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Web; 6 | 7 | namespace WebApiDoodle.Net.Http.Client.Sample45.Clients.Core { 8 | 9 | [EditorBrowsable(EditorBrowsableState.Never)] 10 | public static class ApiClientContextExtensions { 11 | 12 | public static ICarsClient GetCarsClient(this ApiClientContext apiClientContext) { 13 | 14 | return apiClientContext.GetClient(() => new CarsClient(apiClientContext.HttpClient)); 15 | } 16 | 17 | internal static TClient GetClient(this ApiClientContext apiClientContext, Func valueFactory) { 18 | 19 | return (TClient)apiClientContext.Clients.GetOrAdd(typeof(TClient), k => valueFactory()); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Web/MessageHandlers/RequireHttpsMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace WebApiDoodle.Web.MessageHandlers { 8 | 9 | public class RequireHttpsMessageHandler : DelegatingHandler { 10 | 11 | protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { 12 | 13 | if (request.RequestUri.Scheme != Uri.UriSchemeHttps) { 14 | 15 | var forbiddenResponse = request.CreateResponse(HttpStatusCode.Forbidden); 16 | forbiddenResponse.ReasonPhrase = "SSL Required"; 17 | return TaskHelpers.FromResult(forbiddenResponse); 18 | } 19 | 20 | return base.SendAsync(request, cancellationToken); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /samples/WebApiDoodle.Net.Http.Client.Sample45/Models/PaginatedListExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using WebApiDoodle.Net.Http.Client.Model; 3 | using System.Linq; 4 | 5 | namespace WebApiDoodle.Net.Http.Client.Sample45.Models { 6 | 7 | internal static class PaginatedListExtensions { 8 | 9 | internal static PaginatedDto ToPaginatedDto( 10 | this PaginatedList source) where TDto : IDto { 11 | 12 | return new PaginatedDto { 13 | PageIndex = source.PageIndex, 14 | PageSize = source.PageSize, 15 | TotalCount = source.TotalCount, 16 | TotalPageCount = source.TotalPageCount, 17 | HasNextPage = source.HasNextPage, 18 | HasPreviousPage = source.HasPreviousPage, 19 | Items = source.AsEnumerable() 20 | }; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #WebAPIDoodle 2 | 3 | > :warning: **THIS REPOSITORY IS NOT MAINTAINED ANYMORE. IF YOU WOULD LIKE TO VOLUNTEER TO BE THE MAINTAINER, PLEASE [CONTACT ME](https://twitter.com/tourismgeek).** 4 | 5 | **WebAPIDoodle** is an ASP.NET Web API library which contains several useful: 6 | 7 | - Extensions 8 | - Filters 9 | - Message Handlers 10 | 11 | and so on and so forth. 12 | 13 | #Development Workflow 14 | `master` branch only holds the latest stable version of the product. Navigate to `dev` branch in order to see latest work. 15 | 16 | ##Pull Requests & Branching 17 | Every feature must be developed under a so-called feature branch and that branch must be brached off from `dev` branch. 18 | 19 | *Pull Requests* should be targeted to `dev` branch, not `master`! Before sending the PR, make sure you have the latest `dev` branch merged into you feature branch. 20 | 21 | #License and Copyright 22 | This project licensed under MIT license. 23 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http.Client/HttpApiRequestException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Http; 4 | 5 | namespace WebApiDoodle.Net.Http.Client { 6 | 7 | [Serializable] 8 | public class HttpApiRequestException : HttpRequestException { 9 | 10 | public HttpApiRequestException( 11 | string message, 12 | HttpStatusCode httpStatusCode) : base(message) { 13 | 14 | StatusCode = httpStatusCode; 15 | } 16 | 17 | public HttpApiRequestException( 18 | string message, 19 | HttpStatusCode httpStatusCode, 20 | HttpApiError httpError) : base(message) { 21 | 22 | StatusCode = httpStatusCode; 23 | HttpError = httpError; 24 | } 25 | 26 | public HttpStatusCode StatusCode { get; private set; } 27 | public HttpApiError HttpError { get; private set; } 28 | } 29 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Web/ModelBinding/PrincipalParameterBinding.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Principal; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using System.Web.Http.Controllers; 5 | using System.Web.Http.Metadata; 6 | 7 | namespace WebApiDoodle.Web.ModelBinding { 8 | 9 | public class PrincipalParameterBinding : HttpParameterBinding { 10 | 11 | public PrincipalParameterBinding(HttpParameterDescriptor descriptor) 12 | : base(descriptor) { 13 | } 14 | 15 | public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken) { 16 | 17 | string name = Descriptor.ParameterName; 18 | IPrincipal principal = Thread.CurrentPrincipal; 19 | actionContext.ActionArguments.Add(name, principal); 20 | 21 | return TaskHelpers.Completed(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/WebApiDoodle.Test/Routes/OptionalRegExConstraintTest.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Web.Http.Routing; 7 | using WebApiDoodle.Web.Routing; 8 | using Xunit; 9 | 10 | namespace WebApiDoodle.Web.Test.Routes { 11 | 12 | public class OptionalRegExConstraintTest { 13 | 14 | [Fact] 15 | public void OptionalRegExConstraint_ThrowsArgumentNullException_If_ExpressionParamIsNull() { 16 | } 17 | 18 | [Fact] 19 | public void Match_ReturnsTrue_If_RouteValueIsRouteParamaterOptional() { 20 | } 21 | 22 | [Fact] 23 | public void Match_ReturnsTrue_If_RouteValueIsNotRouteParamaterOptional_And_RegExMatchesTheValue() { 24 | } 25 | 26 | [Fact] 27 | public void Match_ReturnsFalse_If_RouteValueIsNotRouteParamaterOptional_And_RegExNotMatchesTheValue() { 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /samples/WebApiDoodle.Net.Http.Client.Sample45/MessageHandlers/AuthMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Security.Principal; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using WebApiDoodle.Web.MessageHandlers; 7 | 8 | namespace WebApiDoodle.Net.Http.Client.Sample45.MessageHandlers { 9 | 10 | public class AuthMessageHandler : BasicAuthenticationHandler { 11 | 12 | protected override Task AuthenticateUserAsync( 13 | HttpRequestMessage request, string username, string password, CancellationToken cancellationToken) { 14 | 15 | if (username.Equals(password, StringComparison.InvariantCultureIgnoreCase)) { 16 | 17 | IPrincipal principal = new GenericPrincipal(new GenericIdentity(username), null); 18 | return Task.FromResult(principal); 19 | } 20 | 21 | return Task.FromResult(null); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /samples/WebApiDoodle.Net.Http.Client.Sample45/Filters/HttpApiRequestExceptionFilterAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using System.Net.Http; 6 | using System.Web.Http.Filters; 7 | using System.Net; 8 | 9 | namespace WebApiDoodle.Net.Http.Client.Sample45.Filters { 10 | 11 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)] 12 | public class HttpApiRequestExceptionFilterAttribute : ExceptionFilterAttribute { 13 | 14 | public override void OnException(HttpActionExecutedContext actionExecutedContext) { 15 | 16 | var apiException = actionExecutedContext.Exception as HttpApiRequestException; 17 | if(apiException != null) { 18 | 19 | actionExecutedContext.Response = 20 | actionExecutedContext.Request.CreateResponse(HttpStatusCode.NotFound); 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Web.Hosting.WebHost45/WebHostAssembliesResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Web.Compilation; 8 | using System.Web.Http.Dispatcher; 9 | 10 | namespace WebApiDoodle.Web.Hosting.WebHost45 { 11 | 12 | /// 13 | /// Provides an implementation of using . 14 | /// 15 | public class WebHostAssembliesResolver : IAssembliesResolver { 16 | 17 | /// 18 | /// Returns a list of controllers available for the application. 19 | /// 20 | /// An of controllers. 21 | public ICollection GetAssemblies() { 22 | 23 | return BuildManager.GetReferencedAssemblies().OfType().ToList(); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2012 Tugberk Ugurlu, Turkey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/WebApiDoodle.Web/Common/CustomAttributeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | 7 | namespace WebApiDoodle.Web { 8 | 9 | internal static class CustomAttributeExtensions { 10 | 11 | internal static T GetAttribute(this MemberInfo element) where T : Attribute { 12 | 13 | return (T)((object)element.GetAttribute(typeof(T))); 14 | } 15 | 16 | internal static Attribute GetAttribute(this MemberInfo element, Type attributeType) { 17 | 18 | return Attribute.GetCustomAttribute(element, attributeType); 19 | } 20 | 21 | public static IEnumerable GetCustomAttributes(this MemberInfo element) { 22 | 23 | return Attribute.GetCustomAttributes(element); 24 | } 25 | 26 | public static IEnumerable GetCustomAttributes(this ParameterInfo element) { 27 | 28 | return Attribute.GetCustomAttributes(element); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Web/Controllers/HttpControllerExecutingContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Web.Http.Controllers; 4 | 5 | namespace WebApiDoodle.Web.Controllers { 6 | 7 | public class HttpControllerExecutingContext { 8 | 9 | private HttpControllerContext _controllerContext; 10 | 11 | public HttpControllerExecutingContext(HttpControllerContext controllerContext) { 12 | 13 | if (controllerContext == null) { 14 | 15 | throw new ArgumentNullException("controllerContext"); 16 | } 17 | 18 | _controllerContext = controllerContext; 19 | } 20 | 21 | public HttpControllerContext ControllerContext { 22 | 23 | get { return _controllerContext; } 24 | set { 25 | if (value == null) { 26 | throw new ArgumentNullException("value"); 27 | } 28 | 29 | _controllerContext = value; 30 | } 31 | } 32 | 33 | public HttpResponseMessage Response { get; set; } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/NuGetPackageManifests/WebApiDoodle.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WebApiDoodle 5 | 2.0.1 6 | WebApiDoodle 7 | Tugberk Ugurlu 8 | Tugberk Ugurlu 9 | https://raw.github.com/WebAPIDoodle/WebAPIDoodle/master/LICENSE.md 10 | https://github.com/WebAPIDoodle/WebAPIDoodle 11 | false 12 | WebApiDoodle is an ASP.NET Web API library which contains several useful Extensions, Filters, Message Handlers and so on and so forth. 13 | Copyright Tugberk Ugurlu 2013 14 | aspnetwebapi aspnet webapi 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /samples/WebApiDoodle.Net.Http.Client.Sample45/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /samples/WebApiDoodle.Net.Http.Client.Sample45/Validation/MaximumAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.Globalization; 4 | 5 | namespace WebApiDoodle.Net.Http.Client.Sample45.Validation { 6 | 7 | [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] 8 | public class MaximumAttribute : ValidationAttribute { 9 | 10 | private readonly int _maximumValue; 11 | 12 | public MaximumAttribute(int maximum) : 13 | base(errorMessage: "The {0} field value must be maximum {1}.") { 14 | 15 | _maximumValue = maximum; 16 | } 17 | 18 | public override string FormatErrorMessage(string name) { 19 | 20 | return string.Format(CultureInfo.CurrentCulture, base.ErrorMessageString, name, _maximumValue); 21 | } 22 | 23 | public override bool IsValid(object value) { 24 | 25 | int intValue; 26 | if (value != null && int.TryParse(value.ToString(), out intValue)) { 27 | 28 | return (intValue <= _maximumValue); 29 | } 30 | 31 | return false; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /samples/WebApiDoodle.Net.Http.Client.Sample45/Validation/MinimumAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.Globalization; 4 | 5 | namespace WebApiDoodle.Net.Http.Client.Sample45.Validation { 6 | 7 | [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] 8 | public class MinimumAttribute : ValidationAttribute { 9 | 10 | private readonly int _minimumValue; 11 | 12 | public MinimumAttribute(int minimum) : 13 | base(errorMessage: "The {0} field value must be minimum {1}.") { 14 | 15 | _minimumValue = minimum; 16 | } 17 | 18 | public override string FormatErrorMessage(string name) { 19 | 20 | return string.Format(CultureInfo.CurrentCulture, base.ErrorMessageString, name, _minimumValue); 21 | } 22 | 23 | public override bool IsValid(object value) { 24 | 25 | int intValue; 26 | if (value != null && int.TryParse(value.ToString(), out intValue)) { 27 | 28 | return (intValue >= _minimumValue); 29 | } 30 | 31 | return false; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /samples/WebApiDoodle.Net.Http.Client.Sample45/Models/PaginatedList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace WebApiDoodle.Net.Http.Client.Sample45.Models { 6 | 7 | public class PaginatedList : List { 8 | 9 | public int PageIndex { get; private set; } 10 | public int PageSize { get; private set; } 11 | public int TotalCount { get; private set; } 12 | public int TotalPageCount { get; private set; } 13 | 14 | public PaginatedList( 15 | int pageIndex, int pageSize, 16 | int totalCount, IEnumerable source) { 17 | 18 | AddRange(source); 19 | 20 | PageIndex = pageIndex; 21 | PageSize = pageSize; 22 | TotalCount = totalCount; 23 | TotalPageCount = 24 | (int)Math.Ceiling(totalCount / (double)pageSize); 25 | } 26 | 27 | public bool HasPreviousPage { 28 | 29 | get { 30 | return (PageIndex > 1); 31 | } 32 | } 33 | 34 | public bool HasNextPage { 35 | 36 | get { 37 | return (PageIndex < TotalPageCount); 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /tests/WebApiDoodle.Test/Controllers/Apis/UsersController.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.Text; 7 | using System.Web.Http; 8 | 9 | namespace WebApiDoodle.Web.Test.Controllers.Apis { 10 | 11 | public class UsersController : ApiController { 12 | 13 | public HttpResponseMessage Get() { 14 | 15 | return new HttpResponseMessage(HttpStatusCode.OK) { 16 | Content = new StringContent("Default User") 17 | }; 18 | } 19 | 20 | public HttpResponseMessage Post() { 21 | return new HttpResponseMessage(HttpStatusCode.OK) { 22 | Content = new StringContent("User Posted") 23 | }; 24 | } 25 | 26 | public HttpResponseMessage Put() { 27 | return new HttpResponseMessage(HttpStatusCode.OK) { 28 | Content = new StringContent("User Updated") 29 | }; 30 | } 31 | 32 | public HttpResponseMessage Delete() { 33 | return new HttpResponseMessage(HttpStatusCode.OK) { 34 | Content = new StringContent("User Deleted") 35 | }; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Web/Formatting/RouteDataMapping.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Net.Http.Formatting; 3 | using System.Net.Http.Headers; 4 | 5 | namespace WebApiDoodle.Web.Formatting { 6 | 7 | public class RouteDataMapping : MediaTypeMapping { 8 | 9 | private readonly string _routeDataValueName; 10 | private readonly string _routeDataValueValue; 11 | 12 | public RouteDataMapping(string routeDataValueName, string routeDataValueValue, MediaTypeHeaderValue mediaType) : 13 | base(mediaType) { 14 | 15 | _routeDataValueName = routeDataValueName; 16 | _routeDataValueValue = routeDataValueValue; 17 | 18 | } 19 | 20 | public RouteDataMapping(string routeDataValueName, string routeDataValueValue, string mediaType) : 21 | base(mediaType) { 22 | 23 | _routeDataValueName = routeDataValueName; 24 | _routeDataValueValue = routeDataValueValue; 25 | 26 | } 27 | 28 | public override double TryMatchMediaType(HttpRequestMessage request) { 29 | 30 | return ( 31 | request.GetRouteData().Values[_routeDataValueName].ToString() == _routeDataValueValue 32 | ) ? 1.0 : 0.0; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Testing/MessageHandlers/DelegatingHandlerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Net.Http; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | namespace System.Net.Http { 12 | 13 | [EditorBrowsable(EditorBrowsableState.Never)] 14 | public static class DelegatingHandlerExtensions { 15 | 16 | internal static Task InvokeAsync( 17 | this DelegatingHandler handler, HttpRequestMessage request, CancellationToken cancellationToken = default(CancellationToken)) { 18 | 19 | handler.InnerHandler = new DummyHandler(); 20 | var invoker = new HttpMessageInvoker(handler); 21 | return invoker.SendAsync(request, cancellationToken); 22 | } 23 | 24 | private class DummyHandler : HttpMessageHandler { 25 | 26 | protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { 27 | 28 | var response = new HttpResponseMessage(HttpStatusCode.OK); 29 | return TaskHelpers.FromResult(response); 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /scripts/publish.ps1: -------------------------------------------------------------------------------- 1 | ################################################## 2 | #resources 3 | ################################################## 4 | #http://www.powershellpro.com/powershell-tutorial-introduction/powershell-tutorial-conditional-logic/ 5 | #http://technet.microsoft.com/en-us/library/ee176935.aspx 6 | #http://weblogs.asp.net/soever/archive/2007/02/06/powershell-regerencing-files-relative-to-the-currently-executing-script.aspx 7 | ################################################## 8 | #resources 9 | ################################################## 10 | param( 11 | $nugetApiKey = "$env:NUGET_API_KEY" 12 | ) 13 | 14 | function require-param { 15 | param($value, $paramName) 16 | 17 | if($value -eq $null) { 18 | write-error "The parameter -$paramName is required." 19 | } 20 | } 21 | 22 | require-param $nugetApiKey -paramName "nugetApiKey" 23 | 24 | #safely find the solutionDir 25 | $ps1Dir = (Split-Path -parent $MyInvocation.MyCommand.Definition) 26 | $solutionDir = Split-Path -Path $ps1Dir -Parent 27 | $nugetExePath = resolve-path(join-path $solutionDir ".nuget") 28 | 29 | $packages = dir "$solutionDir\artifacts\packages\WebApiDoodle.*.nupkg" 30 | 31 | foreach($package in $packages) { 32 | #$package is type of System.IO.FileInfo 33 | & "$nugetExePath\Nuget.exe" push $package.FullName $nugetApiKey 34 | } -------------------------------------------------------------------------------- /samples/WebApiDoodle.Net.Http.Client.Sample45/Web.Debug.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 29 | 30 | -------------------------------------------------------------------------------- /samples/WebApiDoodle.Net.Http.Client.Sample45/Web.Release.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 30 | 31 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Web/Routing/OptionalRegExConstraint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Net.Http; 5 | using System.Text.RegularExpressions; 6 | using System.Web.Http; 7 | using System.Web.Http.Routing; 8 | 9 | namespace WebApiDoodle.Web.Routing { 10 | 11 | public class OptionalRegExConstraint : IHttpRouteConstraint { 12 | 13 | private readonly string _regEx; 14 | 15 | public OptionalRegExConstraint(string expression) { 16 | 17 | if (string.IsNullOrEmpty(expression)) { 18 | 19 | throw Error.ArgumentNull("expression"); 20 | } 21 | 22 | _regEx = expression; 23 | } 24 | 25 | public bool Match( 26 | HttpRequestMessage request, 27 | IHttpRoute route, 28 | string parameterName, 29 | IDictionary values, 30 | HttpRouteDirection routeDirection) { 31 | 32 | if (values[parameterName] != RouteParameter.Optional) { 33 | 34 | object value; 35 | values.TryGetValue(parameterName, out value); 36 | string input = Convert.ToString(value, CultureInfo.InvariantCulture); 37 | 38 | string pattern = "^(" + _regEx + ")$"; 39 | return Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); 40 | } 41 | 42 | return true; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Web/ModelBinding/CatchAllRouteParameterBinding.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using System.Web.Http.Controllers; 4 | 5 | namespace WebApiDoodle.Web.ModelBinding { 6 | 7 | public class CatchAllRouteParameterBinding : HttpParameterBinding { 8 | 9 | private readonly string _parameterName; 10 | private readonly char _delimiter; 11 | 12 | public CatchAllRouteParameterBinding( 13 | HttpParameterDescriptor descriptor, char delimiter) 14 | : base(descriptor) { 15 | 16 | _parameterName = descriptor.ParameterName; 17 | _delimiter = delimiter; 18 | } 19 | 20 | public override Task ExecuteBindingAsync( 21 | System.Web.Http.Metadata.ModelMetadataProvider metadataProvider, 22 | HttpActionContext actionContext, 23 | CancellationToken cancellationToken) { 24 | 25 | var routeValues = actionContext.ControllerContext.RouteData.Values; 26 | 27 | if (routeValues[_parameterName] != null) { 28 | 29 | string[] catchAllValues = 30 | routeValues[_parameterName].ToString().Split(_delimiter); 31 | 32 | actionContext.ActionArguments.Add(_parameterName, catchAllValues); 33 | } 34 | else { 35 | 36 | actionContext.ActionArguments.Add(_parameterName, new string[0]); 37 | } 38 | 39 | return TaskHelpers.Completed(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/WebApiDoodle.Test/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("WebAPIDoodle.Test")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Toshiba")] 12 | [assembly: AssemblyProduct("WebAPIDoodle.Test")] 13 | [assembly: AssemblyCopyright("Copyright © Toshiba 2012")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("71a203fe-2834-4232-9dcf-5c5831ea8297")] 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 | -------------------------------------------------------------------------------- /samples/WebApiDoodle.Net.Http.Client.Sample45/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("WebApiDoodle.Net.Http.Client.Sample45")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("WebApiDoodle.Net.Http.Client.Sample45")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 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("1cc6a1df-1d8a-42e2-8716-c9746df681f5")] 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 Revision and Build Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /tests/WebApiDoodle.Net.Http.Client.Test/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("WebApiDoodle.Net.Http.Client.Test")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("WebApiDoodle.Net.Http.Client.Test")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 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("a2b3f708-d05c-4954-b9b3-540bba47aa88")] 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 | -------------------------------------------------------------------------------- /samples/WebApiDoodle.Net.Http.Client.Sample45/Web.config: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Web/Internal/HttpParameterBindingExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Web.Http.Controllers; 6 | using System.Web.Http.ModelBinding; 7 | using System.Web.Http.ValueProviders; 8 | using System.Web.Http.ValueProviders.Providers; 9 | 10 | namespace WebApiDoodle.Web.Internal { 11 | 12 | internal static class HttpParameterBindingExtensions { 13 | 14 | internal static bool WillReadUri(this HttpParameterBinding parameterBinding) { 15 | 16 | if (parameterBinding == null) { 17 | 18 | throw Error.ArgumentNull("parameterBinding"); 19 | } 20 | 21 | IValueProviderParameterBinding valueProviderParameterBinding = parameterBinding as IValueProviderParameterBinding; 22 | if (valueProviderParameterBinding != null) { 23 | 24 | IEnumerable valueProviderFactories = valueProviderParameterBinding.ValueProviderFactories; 25 | 26 | // NOTE: IUriValueProviderFactory is an internal interface. So, instead of checing the factory 27 | // against it, we can check against the actual types as there are only two of them: 28 | // QueryStringValueProviderFactory 29 | // RouteDataValueProviderFactory 30 | if (valueProviderFactories.Any() && valueProviderFactories.All(factory => 31 | factory.GetType() == typeof(QueryStringValueProviderFactory) || 32 | factory.GetType() == typeof(RouteDataValueProviderFactory))) { 33 | 34 | return true; 35 | } 36 | } 37 | 38 | return false; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Web.Hosting.WebHost45/HttpControllerHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using System.Web; 9 | using System.Web.Http; 10 | using System.Web.Http.Hosting; 11 | 12 | namespace WebApiDoodle.Web.Hosting.WebHost45 { 13 | 14 | public class HttpControllerHandler : HttpTaskAsyncHandler { 15 | 16 | internal const string HttpContextBaseKey = "MS_HttpContext"; 17 | 18 | private static readonly Lazy _server = 19 | new Lazy(() => { 20 | HttpServer server = new HttpServer(GlobalConfiguration.Configuration, GlobalConfiguration.DefaultMessageHandler); 21 | return new HttpMessageInvoker(server); 22 | }); 23 | 24 | private static readonly Lazy _bufferPolicySelector = 25 | new Lazy(() => 26 | GlobalConfiguration.Configuration.Services.GetHostBufferPolicySelector()); 27 | 28 | public override Task ProcessRequestAsync(HttpContext context) { 29 | 30 | CancellationTokenSource cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource( 31 | context.Request.TimedOutToken, 32 | context.Response.ClientDisconnectedToken); 33 | 34 | CancellationToken cancellationToken = cancellationTokenSource.Token; 35 | 36 | throw new NotImplementedException(); 37 | } 38 | 39 | public override bool IsReusable { 40 | get { 41 | return false; 42 | } 43 | } 44 | 45 | // private helpers 46 | } 47 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http.Client/HttpApiResponseMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Http; 4 | 5 | namespace WebApiDoodle.Net.Http.Client { 6 | 7 | public class HttpApiResponseMessage : IDisposable { 8 | 9 | internal const string ModelStateKey = "ModelState"; 10 | 11 | public HttpApiResponseMessage(HttpResponseMessage response, HttpApiError httpError) 12 | : this(response) { 13 | 14 | if (httpError == null) { 15 | 16 | throw new ArgumentNullException("httpError"); 17 | } 18 | 19 | HttpError = httpError; 20 | } 21 | 22 | public HttpApiResponseMessage(HttpResponseMessage response) { 23 | 24 | if (response == null) { 25 | 26 | throw new ArgumentNullException("response"); 27 | } 28 | 29 | Response = response; 30 | } 31 | 32 | /// 33 | /// Represents the HttpResponseMessage for the request. 34 | /// 35 | public HttpResponseMessage Response { get; private set; } 36 | 37 | /// 38 | /// Determines if the response is a success or not. 39 | /// 40 | public bool IsSuccess { 41 | get { 42 | return Response.IsSuccessStatusCode; 43 | } 44 | } 45 | 46 | /// 47 | /// Represents the HTTP error message retrieved from the server if the response has "400 Bad Request" status code. 48 | /// 49 | public HttpApiError HttpError { get; private set; } 50 | 51 | public void Dispose() { 52 | 53 | if (Response != null) { 54 | 55 | Response.Dispose(); 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /tests/WebApiDoodle.Test/MessageHandlers/RequireHttpsMessageHandlerTest.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.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Moq; 10 | using Xunit; 11 | using WebApiDoodle.Web.MessageHandlers; 12 | 13 | namespace WebApiDoodle.Web.Test.MessageHandlers { 14 | 15 | public class RequireHttpsMessageHandlerTest { 16 | 17 | [GCForce, Fact] 18 | public Task RequireHttpsMessageHandler_ReturnsForbidden403StatusCodeWhenTheRequestIsNotOverHTTPS() { 19 | 20 | //Arange 21 | var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost:8080"); 22 | 23 | //Act 24 | return TestHelper.InvokeMessageHandler(request, new RequireHttpsMessageHandler()) 25 | 26 | .ContinueWith(task => { 27 | 28 | //Assert 29 | Assert.Equal(TaskStatus.RanToCompletion, task.Status); 30 | Assert.Equal(HttpStatusCode.Forbidden, task.Result.StatusCode); 31 | }); 32 | } 33 | 34 | [GCForce, Fact] 35 | public Task RequireHttpsMessageHandler_ReturnsDelegatedStatusCodeWhenTheRequestIsOverHTTPS() { 36 | 37 | //Arange 38 | var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:8080"); 39 | 40 | //Act 41 | return TestHelper.InvokeMessageHandler(request, new RequireHttpsMessageHandler()) 42 | 43 | .ContinueWith(task => { 44 | 45 | //Assert 46 | Assert.Equal(TaskStatus.RanToCompletion, task.Status); 47 | Assert.Equal(HttpStatusCode.OK, task.Result.StatusCode); 48 | }); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Web/Common/HttpConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Web.Http; 7 | using System.Web.Http.Hosting; 8 | 9 | namespace WebApiDoodle.Web { 10 | 11 | internal static class HttpConfigurationExtensions { 12 | 13 | internal static bool ShouldIncludeErrorDetail(this HttpConfiguration config, HttpRequestMessage request) { 14 | 15 | switch (config.IncludeErrorDetailPolicy) { 16 | 17 | case IncludeErrorDetailPolicy.Default: 18 | 19 | Lazy includeErrorDetail; 20 | 21 | if (request.Properties.TryGetValue>(HttpPropertyKeys.IncludeErrorDetailKey, out includeErrorDetail)) { 22 | 23 | // If we are on webhost and the user hasn't changed the IncludeErrorDetailPolicy 24 | // look up into the ASP.NET CustomErrors property else default to LocalOnly. 25 | return includeErrorDetail.Value; 26 | } 27 | 28 | goto case IncludeErrorDetailPolicy.LocalOnly; 29 | 30 | case IncludeErrorDetailPolicy.LocalOnly: 31 | 32 | if (request == null) { 33 | return false; 34 | } 35 | 36 | Lazy isLocal; 37 | if (request.Properties.TryGetValue>(HttpPropertyKeys.IsLocalKey, out isLocal)) { 38 | return isLocal.Value; 39 | } 40 | return false; 41 | 42 | case IncludeErrorDetailPolicy.Always: 43 | return true; 44 | 45 | case IncludeErrorDetailPolicy.Never: 46 | return false; 47 | 48 | default: 49 | return false; 50 | } 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Web/MessageHandlers/TimeoutHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace WebApiDoodle.Web.MessageHandlers { 8 | 9 | public class TimeoutHandler : DelegatingHandler { 10 | 11 | private readonly int _milliseconds; 12 | private static readonly TimerCallback s_timerCallback = new TimerCallback(TimerCallbackLogic); 13 | 14 | public TimeoutHandler(int milliseconds) { 15 | 16 | if (milliseconds < -1) { 17 | 18 | throw new ArgumentOutOfRangeException("milliseconds"); 19 | } 20 | 21 | _milliseconds = milliseconds; 22 | } 23 | 24 | public int Timeout { 25 | 26 | get { 27 | return _milliseconds; 28 | } 29 | } 30 | 31 | protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { 32 | 33 | var cts = new CancellationTokenSource(); 34 | var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken); 35 | var linkedToken = linkedTokenSource.Token; 36 | var timer = new Timer(s_timerCallback, cts, -1, -1); 37 | 38 | request.RegisterForDispose(timer); 39 | request.RegisterForDispose(cts); 40 | request.RegisterForDispose(linkedTokenSource); 41 | 42 | timer.Change(_milliseconds, -1); 43 | 44 | return base.SendAsync(request, linkedToken).ContinueWith(task => 45 | request.CreateResponse(HttpStatusCode.RequestTimeout), TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnCanceled); 46 | } 47 | 48 | private static void TimerCallbackLogic(object obj) { 49 | 50 | CancellationTokenSource cancellationTokenSource = (CancellationTokenSource)obj; 51 | cancellationTokenSource.Cancel(); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Web.WebHostEx/MessageHandlers/RemoveServerHeaderMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using System.Web; 5 | 6 | namespace WebApiDoodle.Web.WebHostEx.MessageHandlers { 7 | 8 | /// 9 | /// A delegating handler which removes the 'Server' header from the response. 10 | /// 11 | /// 12 | /// Register this as early as possible inside the message handler pipeline because 13 | /// if any unhandled expection occurs during the pipiline and isn't converted into a 14 | /// HttpResponseMessage, this handler won't be able to remove 'Server' header 15 | /// from the response. 16 | /// 17 | public class RemoveServerHeaderMessageHandler : DelegatingHandler { 18 | 19 | protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { 20 | 21 | return base.SendAsync(request, cancellationToken).Then(response => { 22 | 23 | // See if RequestMessage is present or not. 24 | // Another message handler may have set a new HttpResponseMessage and didn't set the 25 | // currenct request object to response.RequestMessage. 26 | if (response.RequestMessage != null) { 27 | 28 | var httpContext = response.RequestMessage.Properties[HttpWebHostPropertyKeys.HttpContextBaseKey] as HttpContextWrapper; 29 | if (httpContext != null) 30 | httpContext.Response.Headers.Remove("Server"); 31 | } 32 | else { 33 | 34 | // Try your lock on removing the header from actual request property. 35 | var httpContext = request.Properties[HttpWebHostPropertyKeys.HttpContextBaseKey] as HttpContextWrapper; 36 | if (httpContext != null) 37 | httpContext.Response.Headers.Remove("Server"); 38 | } 39 | 40 | return response; 41 | }); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/Common/Error.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Web; 6 | 7 | namespace WebApiDoodle { 8 | 9 | /// 10 | /// Utility class for creating and unwrapping instances. 11 | /// 12 | internal static class Error { 13 | 14 | internal static string Format(string format, params object[] args) { 15 | 16 | return String.Format(CultureInfo.CurrentCulture, format, args); 17 | } 18 | 19 | internal static ArgumentException Argument(string messageFormat, params object[] messageArgs) { 20 | 21 | return new ArgumentException(Error.Format(messageFormat, messageArgs)); 22 | } 23 | 24 | internal static ArgumentException ArgumentNullOrEmpty(string parameterName) { 25 | 26 | return Error.Argument(parameterName, "The argument '{0}' is null or empty.", parameterName); 27 | } 28 | 29 | internal static ArgumentNullException PropertyNull() { 30 | 31 | return new ArgumentNullException("value"); 32 | } 33 | 34 | internal static ArgumentNullException ArgumentNull(string parameterName) { 35 | 36 | return new ArgumentNullException(parameterName); 37 | } 38 | 39 | internal static ArgumentNullException ArgumentNull(string parameterName, string messageFormat, params object[] messageArgs) { 40 | 41 | return new ArgumentNullException(parameterName, Error.Format(messageFormat, messageArgs)); 42 | } 43 | 44 | /// 45 | /// Creates an . 46 | /// 47 | /// A composite format string explaining the reason for the exception. 48 | /// An object array that contains zero or more objects to format. 49 | /// The logged . 50 | internal static InvalidOperationException InvalidOperation(string messageFormat, params object[] messageArgs) { 51 | 52 | return new InvalidOperationException(Error.Format(messageFormat, messageArgs)); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Testing/IntegrationTest/HttpRequestMessageExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Web.Http; 8 | 9 | namespace System.Net.Http { 10 | 11 | [EditorBrowsable(EditorBrowsableState.Never)] 12 | public static class HttpRequestMessageExtensions { 13 | 14 | public static Task InvokeServerAsync(this HttpRequestMessage request, HttpConfiguration configuration) { 15 | 16 | var httpServer = new HttpServer(configuration); 17 | var httpClient = HttpClientFactory.Create(innerHandler: httpServer); 18 | 19 | // TODO: Dispose the HttpClient here by using the Finally method. 20 | return httpClient.SendAsync(request); 21 | } 22 | 23 | public static Task InvokeServerAsync(this HttpRequestMessage request, HttpConfiguration configuration, Func successor) { 24 | 25 | TaskCompletionSource tcs = new TaskCompletionSource(); 26 | return request.InvokeServerAsync(configuration).Then(response => { 27 | 28 | // if the response is the one that is expected, get the object out of it 29 | if (successor(response)) { 30 | 31 | response.Content.ReadAsAsync().Then(result => { 32 | 33 | tcs.SetResult(result); 34 | }, runSynchronously: true).Catch(info => { 35 | 36 | tcs.SetException(info.Exception); 37 | return new CatchInfoBase.CatchResult { Task = tcs.Task }; 38 | }); 39 | } 40 | else { 41 | 42 | // TODO: Figure out if this is the best way here. 43 | // Maybe throw a specific exception here... 44 | tcs.SetResult(default(TResult)); 45 | } 46 | 47 | return tcs.Task; 48 | 49 | }, runSynchronously: true).Catch(info => { 50 | 51 | tcs.SetException(info.Exception); 52 | return new CatchInfoBase>.CatchResult { Task = tcs.Task }; 53 | }); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /samples/WebApiDoodle.Net.Http.Client.Sample45/Controllers/Server/CarsController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.Web.Http; 5 | using WebApiDoodle.Net.Http.Client.Model; 6 | using WebApiDoodle.Net.Http.Client.Sample45.Filters; 7 | using WebApiDoodle.Net.Http.Client.Sample45.Models; 8 | using WebApiDoodle.Net.Http.Client.Sample45.RequestCommands; 9 | 10 | namespace WebApiDoodle.Net.Http.Client.Sample45.Controllers.Server { 11 | 12 | [InvalidModelStateFilter] 13 | public class CarsController : ApiController { 14 | 15 | private readonly CarsContext _carsCtx = new CarsContext(); 16 | 17 | // GET /api/cars 18 | public PaginatedDto Get(PaginatedRequestCommand cmd) { 19 | 20 | return _carsCtx.GetAll(cmd.Page, cmd.Take).ToPaginatedDto(); 21 | } 22 | 23 | // GET /api/cars/{id} 24 | public Car GetCar(int id) { 25 | 26 | var carTuple = _carsCtx.GetSingle(id); 27 | 28 | if (!carTuple.Item1) { 29 | 30 | var response = Request.CreateResponse(HttpStatusCode.NotFound); 31 | throw new HttpResponseException(response); 32 | } 33 | 34 | return carTuple.Item2; 35 | } 36 | 37 | // POST /api/cars 38 | public HttpResponseMessage PostCar(Car car) { 39 | 40 | var createdCar = _carsCtx.Add(car); 41 | var response = Request.CreateResponse(HttpStatusCode.Created, createdCar); 42 | response.Headers.Location = new Uri( 43 | Url.Link("ServerHttpRoute", new { id = createdCar.Id })); 44 | 45 | return response; 46 | } 47 | 48 | // PUT /api/cars/{id} 49 | public Car PutCar(int id, Car car) { 50 | 51 | car.Id = id; 52 | 53 | if (!_carsCtx.TryUpdate(car)) { 54 | 55 | var response = Request.CreateResponse(HttpStatusCode.NotFound); 56 | throw new HttpResponseException(response); 57 | } 58 | 59 | return car; 60 | } 61 | 62 | // DELETE /api/cars/{id} 63 | public HttpResponseMessage DeleteCar(int id) { 64 | 65 | if (!_carsCtx.TryRemove(id)) { 66 | 67 | var response = Request.CreateResponse(HttpStatusCode.NotFound); 68 | throw new HttpResponseException(response); 69 | } 70 | 71 | return Request.CreateResponse(HttpStatusCode.NoContent); 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Web/MessageHandlers/ApiKeyAuthenticationHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Net; 4 | using System.Net.Http; 5 | using System.Security.Principal; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace WebApiDoodle.Web.MessageHandlers { 10 | 11 | public abstract class ApiKeyAuthenticationHandler : DelegatingHandler { 12 | 13 | private readonly string _apiKeyQueryParameter; 14 | 15 | public ApiKeyAuthenticationHandler(string apiKeyQueryParameter) { 16 | 17 | if (string.IsNullOrEmpty(apiKeyQueryParameter)) { 18 | 19 | throw new ArgumentNullException("apiKeyQueryParameter"); 20 | } 21 | 22 | _apiKeyQueryParameter = apiKeyQueryParameter; 23 | } 24 | 25 | protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { 26 | 27 | var queryStringCollection = request.RequestUri.ParseQueryString(); 28 | if (queryStringCollection.AllKeys.Any(key => key.Equals(_apiKeyQueryParameter, StringComparison.OrdinalIgnoreCase))) { 29 | 30 | var apiKey = queryStringCollection[_apiKeyQueryParameter]; 31 | IPrincipal principal; 32 | 33 | try { 34 | 35 | //Authenticate the user now 36 | principal = AuthenticateUser(apiKey, request, cancellationToken); 37 | } 38 | catch (Exception e) { 39 | 40 | return TaskHelpers.FromError(e); 41 | } 42 | 43 | //check if the user has been authenticated successfully 44 | if (principal != null) { 45 | 46 | Thread.CurrentPrincipal = principal; 47 | return base.SendAsync(request, cancellationToken); 48 | } 49 | } 50 | 51 | return TaskHelpers.FromResult(request.CreateResponse(HttpStatusCode.Unauthorized)); 52 | } 53 | 54 | /// 55 | /// The method which is responsable for authenticating the user based on the provided API Key and request. 56 | /// 57 | /// 58 | /// 59 | /// 60 | /// 61 | protected abstract IPrincipal AuthenticateUser(string apiKey, HttpRequestMessage request, CancellationToken cancellationToken); 62 | } 63 | } -------------------------------------------------------------------------------- /samples/WebApiDoodle.Net.Http.Client.Sample45/Controllers/Client/CarsClientController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | using System.Web.Http; 6 | using WebApiDoodle.Net.Http.Client.Model; 7 | using WebApiDoodle.Net.Http.Client.Sample45.Clients; 8 | using WebApiDoodle.Net.Http.Client.Sample45.Filters; 9 | using WebApiDoodle.Net.Http.Client.Sample45.Models; 10 | using WebApiDoodle.Net.Http.Client.Sample45.RequestCommands; 11 | 12 | namespace WebApiDoodle.Net.Http.Client.Sample45.Controllers.Client { 13 | 14 | /// 15 | /// This is the client controller. This would be an ASP.NET MVC controller, 16 | /// a WPF application or any other .NET application. It just doesn't matter. 17 | /// We used a Web API controller here to act as a client. 18 | /// 19 | [InvalidModelStateFilter] 20 | [HttpApiRequestExceptionFilter] 21 | public class CarsClientController : ApiController { 22 | 23 | // TODO: Handle expections & unsuccessful responses. 24 | 25 | private readonly ICarsClient _carsClient; 26 | public CarsClientController(ICarsClient carsClient) { 27 | 28 | _carsClient = carsClient; 29 | } 30 | 31 | // GET /carsclient/cars 32 | public async Task> Get(PaginatedRequestCommand cmd) { 33 | 34 | var cars = await _carsClient.GetCars(cmd); 35 | return cars; 36 | } 37 | 38 | // GET /carsclient/cars/{id} 39 | public async Task GetCar(int id) { 40 | 41 | var car = await _carsClient.GetCar(id); 42 | return car; 43 | } 44 | 45 | // POST /carsclient/cars 46 | public async Task PostCar(Car car) { 47 | 48 | var createdCar = await _carsClient.AddCar(car); 49 | var response = Request.CreateResponse(HttpStatusCode.Created, createdCar); 50 | response.Headers.Location = new Uri( 51 | Url.Link("ClientHttpRoute", new { id = createdCar.Id })); 52 | 53 | return response; 54 | } 55 | 56 | // PUT /carsclient/cars/{id} 57 | public async Task PutCar(int id, Car car) { 58 | 59 | var updatedCar = await _carsClient.UpdateCar(id, car); 60 | return updatedCar; 61 | } 62 | 63 | // DELETE /carsclient/cars/{id} 64 | public async Task DeleteCar(int id) { 65 | 66 | await _carsClient.RemoveCar(id); 67 | return Request.CreateResponse(HttpStatusCode.NoContent); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /scripts/WebAPIDoodle.msbuild: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | Debug 8 | $([System.IO.Path]::GetDirectoryName($(MSBuildProjectDirectory))) 9 | $(SolutionDir)\scripts 10 | $(SolutionDir)\.nuget 11 | $(SolutionDir)\WebApiDoodle.sln 12 | $(SolutionDir)\artifacts 13 | $(BuildArtifactsDir)\packages 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /samples/WebApiDoodle.Net.Http.Client.Sample45/Clients/Core/ApiClientConfigurationExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Web; 7 | 8 | namespace WebApiDoodle.Net.Http.Client.Sample45.Clients.Core { 9 | 10 | public class ApiClientConfigurationExpression { 11 | 12 | private readonly ApiClientContext _apiClientContext; 13 | 14 | internal ApiClientConfigurationExpression(ApiClientContext apiClientContext) { 15 | 16 | if (apiClientContext == null) { 17 | 18 | throw new ArgumentNullException("apiClientContext"); 19 | } 20 | 21 | _apiClientContext = apiClientContext; 22 | } 23 | 24 | public ApiClientConfigurationExpression SetCredentialsFromAppSetting(string usernameAppSettingKey, string passwordAppSettingKey) { 25 | 26 | if (string.IsNullOrEmpty(usernameAppSettingKey)) { 27 | 28 | throw new ArgumentNullException("usernameAppSettingKey"); 29 | } 30 | 31 | if (string.IsNullOrEmpty(passwordAppSettingKey)) { 32 | 33 | throw new ArgumentNullException("passwordAppSettingKey"); 34 | } 35 | 36 | string username = ConfigurationManager.AppSettings[usernameAppSettingKey]; 37 | string password = ConfigurationManager.AppSettings[passwordAppSettingKey]; 38 | 39 | if (string.IsNullOrEmpty(username)) { 40 | 41 | throw new ArgumentNullException( 42 | string.Format("The application setting '{0}' does not exist or its value is null.", usernameAppSettingKey)); 43 | } 44 | 45 | if (string.IsNullOrEmpty(password)) { 46 | 47 | throw new ArgumentNullException( 48 | string.Format("The application setting '{0}' does not exist or its value is null.", passwordAppSettingKey)); 49 | } 50 | 51 | _apiClientContext.AuthorizationValue = EncodeToBase64(string.Format("{0}:{1}", username, password)); 52 | 53 | return this; 54 | } 55 | 56 | public ApiClientConfigurationExpression ConnectTo(string baseUri) { 57 | 58 | if (string.IsNullOrEmpty(baseUri)) { 59 | 60 | throw new ArgumentNullException("baseUri"); 61 | } 62 | 63 | _apiClientContext.BaseUri = new Uri(baseUri); 64 | 65 | return this; 66 | } 67 | 68 | private static string EncodeToBase64(string value) { 69 | 70 | byte[] toEncodeAsBytes = Encoding.UTF8.GetBytes(value); 71 | return Convert.ToBase64String(toEncodeAsBytes); 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /samples/WebApiDoodle.Net.Http.Client.Sample45/Global.asax.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using Autofac.Integration.WebApi; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Net.Http; 7 | using System.Reflection; 8 | using System.Web; 9 | using System.Web.Http; 10 | using System.Web.Http.Dispatcher; 11 | using System.Web.Security; 12 | using System.Web.SessionState; 13 | using WebApiDoodle.Net.Http.Client.Sample45.Clients; 14 | using WebApiDoodle.Net.Http.Client.Sample45.Clients.Core; 15 | using WebApiDoodle.Net.Http.Client.Sample45.MessageHandlers; 16 | using WebApiDoodle.Net.Http.Client.Sample45.RequestCommands; 17 | 18 | namespace WebApiDoodle.Net.Http.Client.Sample45 { 19 | 20 | public class Global : HttpApplication { 21 | 22 | protected void Application_Start(object sender, EventArgs e) { 23 | 24 | HttpConfiguration config = GlobalConfiguration.Configuration; 25 | HttpMessageHandler serverPipeline = HttpClientFactory.CreatePipeline(new HttpControllerDispatcher(config), new[] { new AuthMessageHandler() }); 26 | 27 | config.Routes.MapHttpRoute( 28 | "ServerHttpRoute", 29 | "api/cars/{id}", 30 | defaults: new { id = RouteParameter.Optional, controller = "cars" }, 31 | constraints: null, 32 | handler: serverPipeline 33 | ); 34 | 35 | config.Routes.MapHttpRoute( 36 | "ClientHttpRoute", 37 | "client/cars/{id}", 38 | new { id = RouteParameter.Optional, controller = "carsclient" } 39 | ); 40 | 41 | RegisterDependencies(config); 42 | 43 | // Any complex type parameter which is Assignable From 44 | // IRequestCommand will be bound from the URI 45 | config.ParameterBindingRules.Insert(0, descriptor => 46 | typeof(IRequestCommand).IsAssignableFrom(descriptor.ParameterType) 47 | ? new FromUriAttribute().GetBinding(descriptor) : null); 48 | } 49 | 50 | private static void RegisterDependencies(HttpConfiguration config) { 51 | 52 | ContainerBuilder builder = new ContainerBuilder(); 53 | ApiClientContext apiClientContex = ApiClientContext.Create(cfg => 54 | cfg.ConnectTo("http://localhost:18132") 55 | .SetCredentialsFromAppSetting("Api:UserName", "Api:Password")); 56 | 57 | builder.RegisterApiControllers(Assembly.GetExecutingAssembly()); 58 | builder.Register(c => apiClientContex.GetCarsClient()).As().InstancePerApiRequest(); 59 | config.DependencyResolver = new AutofacWebApiDependencyResolver(builder.Build()); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Web.Hosting.WebHost45/GlobalConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Web.Http; 8 | using System.Web.Http.Dispatcher; 9 | using System.Web.Http.Hosting; 10 | using System.Web.Routing; 11 | using WebApiDoodle.Web.Hosting.WebHost45.Routing; 12 | 13 | namespace WebApiDoodle.Web.Hosting.WebHost45 { 14 | 15 | public static class GlobalConfiguration { 16 | 17 | private static Lazy _configuration = new Lazy( 18 | () => { 19 | HttpConfiguration config = new HttpConfiguration(new HostedHttpRouteCollection(RouteTable.Routes)); 20 | config.Services.Replace(typeof(IAssembliesResolver), new WebHostAssembliesResolver()); 21 | config.Services.Replace(typeof(IHttpControllerTypeResolver), new WebHostHttpControllerTypeResolver()); 22 | config.Services.Replace(typeof(IHostBufferPolicySelector), new WebHostBufferPolicySelector()); 23 | return config; 24 | }); 25 | 26 | private static Lazy _defaultHandler = new Lazy( 27 | () => new HttpRoutingDispatcher(_configuration.Value)); 28 | 29 | private static Lazy _routeHandler = new Lazy( 30 | () => { 31 | 32 | if (_configuration.Value.DependencyResolver != null) { 33 | 34 | var routeHandlerProvider = _configuration.Value.DependencyResolver.GetService(typeof(IRouteHandlerProvider)); 35 | if (routeHandlerProvider != null) { 36 | 37 | return ((IRouteHandlerProvider)routeHandlerProvider).GetRouteHandler(); 38 | } 39 | } 40 | 41 | return new HttpControllerRouteHandler(); 42 | 43 | }, isThreadSafe: true); 44 | 45 | /// 46 | /// Gets the global . 47 | /// 48 | public static HttpConfiguration Configuration { 49 | 50 | get { return _configuration.Value; } 51 | } 52 | 53 | /// 54 | /// Gets the default message handler that will be called for all requests. 55 | /// 56 | public static HttpMessageHandler DefaultMessageHandler { 57 | 58 | get { return _defaultHandler.Value; } 59 | } 60 | 61 | /// 62 | /// Gets the provided route handler that will be called for all requests. 63 | /// 64 | public static IRouteHandler DefaultRouteHandler { 65 | 66 | get { return _routeHandler.Value; } 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Web.Meta/WebApiDoodle.Web.Meta.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {6F3C5937-B51D-4117-8276-7C50806BFCCF} 8 | Library 9 | Properties 10 | WebApiDoodle.Web.Meta 11 | WebApiDoodle.Web.Meta 12 | v4.0 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 1591 24 | bin\Debug\WebApiDoodle.Web.Meta.XML 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 1591 34 | bin\Release\WebApiDoodle.Web.Meta.XML 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | Properties\CommonAssemblyInfo.cs 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 60 | -------------------------------------------------------------------------------- /samples/WebApiDoodle.Net.Http.Client.Sample45/Models/CarContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace WebApiDoodle.Net.Http.Client.Sample45.Models { 7 | 8 | public class CarsContext { 9 | 10 | private static int _nextId = 9; 11 | private static object _incLock = new object(); 12 | 13 | //cars store 14 | private readonly static ConcurrentDictionary _carsDictionary = new ConcurrentDictionary(new HashSet> { 15 | new KeyValuePair(1, new Car { Id = 1, Make = "Make1", Model = "Model1", Year = 2010, Price = 10732.2F }), 16 | new KeyValuePair(2, new Car { Id = 2, Make = "Make2", Model = "Model2", Year = 2008, Price = 27233.1F }), 17 | new KeyValuePair(3, new Car { Id = 3, Make = "Make3", Model = "Model1", Year = 2009, Price = 67437.0F }), 18 | new KeyValuePair(4, new Car { Id = 4, Make = "Make4", Model = "Model3", Year = 2007, Price = 78984.2F }), 19 | new KeyValuePair(5, new Car { Id = 5, Make = "Make5", Model = "Model1", Year = 1987, Price = 56200.89F }), 20 | new KeyValuePair(6, new Car { Id = 6, Make = "Make6", Model = "Model4", Year = 1997, Price = 46003.2F }), 21 | new KeyValuePair(7, new Car { Id = 7, Make = "Make7", Model = "Model5", Year = 2001, Price = 78355.92F }), 22 | new KeyValuePair(8, new Car { Id = 8, Make = "Make8", Model = "Model1", Year = 2011, Price = 1823223.23F }) 23 | }); 24 | 25 | public PaginatedList GetAll(int page, int take) { 26 | 27 | return _carsDictionary.Values.ToPaginatedList(page, take); 28 | } 29 | 30 | public IEnumerable Get(Func predicate) { 31 | 32 | return _carsDictionary.Values.Where(predicate); 33 | } 34 | 35 | public Tuple GetSingle(int id) { 36 | 37 | Car car; 38 | var doesExist = _carsDictionary.TryGetValue(id, out car); 39 | return new Tuple(doesExist, car); 40 | } 41 | 42 | public Car GetSingle(Func predicate) { 43 | 44 | return _carsDictionary.Values.FirstOrDefault(predicate); 45 | } 46 | 47 | public Car Add(Car car) { 48 | 49 | lock (_incLock) { 50 | 51 | car.Id = _nextId; 52 | _carsDictionary.TryAdd(car.Id, car); 53 | _nextId++; 54 | } 55 | 56 | return car; 57 | } 58 | 59 | public bool TryRemove(int id) { 60 | 61 | Car removedCar; 62 | return _carsDictionary.TryRemove(id, out removedCar); 63 | } 64 | 65 | public bool TryUpdate(Car car) { 66 | 67 | Car oldCar; 68 | if (_carsDictionary.TryGetValue(car.Id, out oldCar)) { 69 | 70 | return _carsDictionary.TryUpdate(car.Id, car, oldCar); 71 | } 72 | 73 | return false; 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /tests/WebApiDoodle.Test/TestHelpers/TestHelper.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.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace WebApiDoodle.Web.Test { 11 | 12 | internal static class TestHelper { 13 | 14 | internal static Task InvokeMessageHandler(HttpRequestMessage request, DelegatingHandler handler, CancellationToken cancellationToken = default(CancellationToken)) { 15 | 16 | handler.InnerHandler = new DummyHandler(); 17 | var invoker = new HttpMessageInvoker(handler); 18 | return invoker.SendAsync(request, cancellationToken); 19 | } 20 | 21 | private class DummyHandler : DelegatingHandler { 22 | 23 | protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { 24 | 25 | var response = new HttpResponseMessage(HttpStatusCode.OK); 26 | return TaskHelpers.FromResult(response); 27 | } 28 | } 29 | 30 | //private static Task InvokeImpl(HttpRequestMessage request, DelegatingHandler handler, CancellationToken cancellationToken = default(CancellationToken), TaskStatus taskStatus = TaskStatus.RanToCompletion) { 31 | 32 | // var invoker = new InvokerHandler(); 33 | // return invoker.Invoke(request, handler, cancellationToken, taskStatus); 34 | //} 35 | 36 | //private class InvokerHandler : DelegatingHandler { 37 | 38 | // public Task Invoke(HttpRequestMessage request, DelegatingHandler handler, CancellationToken cancellationToken, TaskStatus taskStatus) { 39 | 40 | // handler.InnerHandler = new DummyHandler(taskStatus); 41 | // InnerHandler = handler; 42 | // return SendAsync(request, cancellationToken); 43 | // } 44 | 45 | // private class DummyHandler : DelegatingHandler { 46 | 47 | // TaskStatus _taskStatus; 48 | 49 | // public DummyHandler(TaskStatus taskStatus) { 50 | // _taskStatus = taskStatus; 51 | // } 52 | 53 | // protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { 54 | 55 | // var response = new HttpResponseMessage(HttpStatusCode.OK); 56 | 57 | // switch (_taskStatus) { 58 | 59 | // case TaskStatus.Canceled: 60 | // return TaskHelpers.Canceled(); 61 | 62 | // case TaskStatus.Faulted: 63 | // return TaskHelpers.FromError(new NotImplementedException()); 64 | 65 | // case TaskStatus.RanToCompletion: 66 | // return TaskHelpers.FromResult(response); 67 | 68 | // default: 69 | // return TaskHelpers.FromResult(response); 70 | // } 71 | // } 72 | // } 73 | //} 74 | } 75 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http.Client.Model/WebApiDoodle.Net.Http.Client.Model.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {73F9AF6E-4DDF-4051-B714-C4B41106DAE8} 8 | Library 9 | Properties 10 | WebApiDoodle.Net.Http.Client.Model 11 | WebApiDoodle.Net.Http.Client.Model 12 | v4.0 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 1591 24 | bin\Debug\WebApiDoodle.Net.Http.Client.Model.XML 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | bin\Release\WebApiDoodle.Net.Http.Client.Model.XML 34 | 1591 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | Properties\CommonAssemblyInfo.cs 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 65 | -------------------------------------------------------------------------------- /samples/WebApiDoodle.Net.Http.Client.Sample45/Clients/Core/ApiClientContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Diagnostics; 4 | using System.Net; 5 | using System.Net.Http; 6 | using System.Net.Http.Headers; 7 | using System.Reflection; 8 | 9 | namespace WebApiDoodle.Net.Http.Client.Sample45.Clients.Core { 10 | 11 | public sealed class ApiClientContext { 12 | 13 | internal ApiClientContext() { } 14 | 15 | public Uri BaseUri { get; internal set; } 16 | internal string AuthorizationValue { get; set; } 17 | 18 | private static readonly Lazy> _clients = new Lazy>(() => new ConcurrentDictionary(), isThreadSafe: true); 19 | 20 | private static readonly Lazy _httpClient = 21 | new Lazy( 22 | () => { 23 | 24 | Assembly assembly = Assembly.GetExecutingAssembly(); 25 | HttpClient httpClient = HttpClientFactory.Create(innerHandler: new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.GZip }); 26 | 27 | httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); 28 | httpClient.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip")); 29 | httpClient.DefaultRequestHeaders.Add("X-UserAgent", 30 | string.Concat(assembly.FullName, "( ", FileVersionInfo.GetVersionInfo(assembly.Location).ProductVersion, ")")); 31 | 32 | return httpClient; 33 | 34 | }, isThreadSafe: true); 35 | 36 | internal ConcurrentDictionary Clients { 37 | 38 | get { return _clients.Value; } 39 | } 40 | 41 | internal HttpClient HttpClient { 42 | 43 | get { 44 | 45 | if (!_httpClient.IsValueCreated) { 46 | 47 | if (BaseUri == null) { 48 | throw new ArgumentNullException("BaseUri"); 49 | } 50 | if (string.IsNullOrEmpty(AuthorizationValue)) { 51 | throw new ArgumentNullException("AuthorizationValue"); 52 | } 53 | 54 | InitializeHttpClient(); 55 | } 56 | 57 | return _httpClient.Value; 58 | } 59 | } 60 | 61 | public static ApiClientContext Create(Action action) { 62 | 63 | var apiClientContext = new ApiClientContext(); 64 | var configurationExpression = new ApiClientConfigurationExpression(apiClientContext); 65 | 66 | action(configurationExpression); 67 | 68 | return apiClientContext; 69 | } 70 | 71 | private void InitializeHttpClient() { 72 | 73 | // Set BaseUri 74 | _httpClient.Value.BaseAddress = BaseUri; 75 | 76 | // Set default headers 77 | _httpClient.Value.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", AuthorizationValue); 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /src/Common/CommonResources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.18010 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace WebApiDoodle.Web { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class CommonResources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal CommonResources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WebApiDoodle.Web.Properties.CommonResources", typeof(CommonResources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized string similar to The argument '{0}' is null or empty.. 65 | /// 66 | internal static string ArgumentNullOrEmpty { 67 | get { 68 | return ResourceManager.GetString("ArgumentNullOrEmpty", resourceCulture); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Common/DictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using WebApiDoodle; 7 | 8 | namespace WebApiDoodle { 9 | 10 | internal static class DictionaryExtensions { 11 | 12 | /// 13 | /// Gets the value of associated with the specified key or default value if 14 | /// either the key is not present or the value is not of type . 15 | /// 16 | /// The type of the value associated with the specified key. 17 | /// The instance where TValue is object. 18 | /// The key whose value to get. 19 | /// When this method returns, the value associated with the specified key, if the key is found; otherwise, the default value for the type of the value parameter. 20 | /// true if key was found, value is non-null, and value is of type ; otherwise false. 21 | public static bool TryGetValue(this IDictionary collection, string key, out T value) { 22 | 23 | if (collection == null) { 24 | 25 | throw Error.ArgumentNull("collection"); 26 | } 27 | 28 | object valueObj; 29 | if (collection.TryGetValue(key, out valueObj)) { 30 | 31 | if (valueObj is T) { 32 | 33 | value = (T)valueObj; 34 | return true; 35 | } 36 | } 37 | 38 | value = default(T); 39 | return false; 40 | } 41 | 42 | internal static IEnumerable> FindKeysWithPrefix(this IDictionary dictionary, string prefix) { 43 | 44 | if (dictionary == null) { 45 | 46 | throw Error.ArgumentNull("dictionary"); 47 | } 48 | 49 | if (prefix == null) { 50 | 51 | throw Error.ArgumentNull("prefix"); 52 | } 53 | 54 | TValue exactMatchValue; 55 | if (dictionary.TryGetValue(prefix, out exactMatchValue)) { 56 | 57 | yield return new KeyValuePair(prefix, exactMatchValue); 58 | } 59 | 60 | foreach (var entry in dictionary) { 61 | 62 | string key = entry.Key; 63 | 64 | if (key.Length <= prefix.Length) { 65 | continue; 66 | } 67 | 68 | if (!key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { 69 | continue; 70 | } 71 | 72 | // Everything is prefixed by the empty string 73 | if (prefix.Length == 0) { 74 | yield return entry; 75 | } 76 | else { 77 | char charAfterPrefix = key[prefix.Length]; 78 | switch (charAfterPrefix) { 79 | case '[': 80 | case '.': 81 | yield return entry; 82 | break; 83 | } 84 | } 85 | } 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http.Client/QueryStringCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Collections.ObjectModel; 5 | using System.ComponentModel; 6 | using System.Text; 7 | 8 | namespace WebApiDoodle.Net.Http.Client { 9 | 10 | public class QueryStringCollection : ICollection> { 11 | 12 | private readonly ICollection> _pairs; 13 | 14 | public QueryStringCollection() { 15 | 16 | _pairs = new Collection>(); 17 | } 18 | 19 | public QueryStringCollection(ICollection> pairs) { 20 | 21 | if (pairs == null) { 22 | throw new NullReferenceException("pairs"); 23 | } 24 | 25 | _pairs = pairs; 26 | } 27 | 28 | public QueryStringCollection(object queryParameters) { 29 | 30 | if (queryParameters == null) { 31 | throw new NullReferenceException("queryParameters"); 32 | } 33 | 34 | _pairs = new Collection>(); 35 | 36 | PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(queryParameters); 37 | foreach (PropertyDescriptor propertyDescriptor in properties) { 38 | 39 | object value = propertyDescriptor.GetValue(queryParameters); 40 | Add(new KeyValuePair(propertyDescriptor.Name, (value != null) ? value.ToString() : null)); 41 | } 42 | } 43 | 44 | public override string ToString() { 45 | 46 | StringBuilder queryBuilder = new StringBuilder(); 47 | foreach (var pair in _pairs) { 48 | queryBuilder.AppendFormat("{0}={1}&", pair.Key, pair.Value); 49 | } 50 | 51 | var query = queryBuilder.ToString(); 52 | query = query.Substring(0, query.Length - 1); 53 | 54 | return query; 55 | } 56 | 57 | public IEnumerator> GetEnumerator() { 58 | 59 | return _pairs.GetEnumerator(); 60 | } 61 | 62 | IEnumerator IEnumerable.GetEnumerator() { 63 | 64 | IEnumerable ie = _pairs; 65 | return ie.GetEnumerator(); 66 | } 67 | 68 | public void Add(string key, string value) { 69 | 70 | Add(new KeyValuePair(key, value)); 71 | } 72 | 73 | public void Add(KeyValuePair item) { 74 | 75 | _pairs.Add(new KeyValuePair(item.Key, Uri.EscapeUriString(item.Value))); 76 | } 77 | 78 | public void Clear() { 79 | 80 | _pairs.Clear(); 81 | } 82 | 83 | public bool Contains(KeyValuePair item) { 84 | 85 | return _pairs.Contains(item); 86 | } 87 | 88 | public void CopyTo(KeyValuePair[] array, int arrayIndex) { 89 | 90 | _pairs.CopyTo(array, arrayIndex); 91 | } 92 | 93 | public int Count { 94 | 95 | get { return _pairs.Count; } 96 | } 97 | 98 | public bool IsReadOnly { 99 | 100 | get { return _pairs.IsReadOnly; } 101 | } 102 | 103 | public bool Remove(KeyValuePair item) { 104 | 105 | return _pairs.Remove(item); 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /tests/WebApiDoodle.Test/Filters/ValidateModelStateAttributeTest.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.Net.Http.Headers; 7 | using System.Text; 8 | using System.Web.Http; 9 | using WebApiDoodle.Web.Filters; 10 | using Xunit; 11 | 12 | namespace WebApiDoodle.Web.Test.Filters { 13 | 14 | public class ValidateModelStateAttributeTest { 15 | 16 | [Fact] 17 | public void InvalidModelStateFilterAttribute_ShouldSetThe400ResponseIfTheModelStateIsNotValid() { 18 | 19 | //Arange 20 | var validateModelStateFilter = new InvalidModelStateFilterAttribute(); 21 | var request = new HttpRequestMessage(); 22 | var actionContext = ContextUtil.GetHttpActionContext(request); 23 | actionContext.ModelState.AddModelError("foo", "foo is invalid."); 24 | 25 | //Act 26 | validateModelStateFilter.OnActionExecuting(actionContext); 27 | 28 | //Assert 29 | Assert.NotNull(actionContext.Response); 30 | Assert.Equal(HttpStatusCode.BadRequest, actionContext.Response.StatusCode); 31 | } 32 | 33 | [Fact] 34 | public void InvalidModelStateFilterAttribute_ShouldNotSetTheResponseIfTheModelStateIsValid() { 35 | 36 | //Arange 37 | var validateModelStateFilter = new InvalidModelStateFilterAttribute(); 38 | var request = new HttpRequestMessage(); 39 | var actionContext = ContextUtil.GetHttpActionContext(request); 40 | 41 | //Act 42 | validateModelStateFilter.OnActionExecuting(actionContext); 43 | 44 | //Assert 45 | Assert.Null(actionContext.Response); 46 | } 47 | 48 | [Fact] 49 | public void InvalidModelStateFilterAttribute_ShouldReturnTheResponseWithProperContentTypeIfTheModelStateIsNotValid() { 50 | 51 | //NOTE: This test might seems that we are here testing the framework 52 | // stuff but we are not. We just make sure here that InvalidModelStateFilterAttribute 53 | // really honors the conneg. 54 | 55 | //Arange 56 | var validateModelStateFilter = new InvalidModelStateFilterAttribute(); 57 | var request = new HttpRequestMessage(); 58 | request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); 59 | var actionContext = ContextUtil.GetHttpActionContext(request); 60 | actionContext.ModelState.AddModelError("foo", "foo is invalid."); 61 | 62 | //NOTE: Here, the response is being returned through the CreateErrorResponse extension 63 | // method of the HttpRequestMessage object. What this basically does is 64 | // to pass an HttpError instance to another extension method, CreateResponse. 65 | // The CreateResponse method looks at the configuration instance 66 | // (yes, config shouldn't be null) and gets the IContentNegotiator service 67 | // through Services. If we create a HttpConfiguration object with its 68 | // parameterless ctor, the negotiator will be the type of DefaultContentNegotiator. 69 | // DefaultContentNegotiator should negotiate properly here. 70 | 71 | //Act 72 | validateModelStateFilter.OnActionExecuting(actionContext); 73 | 74 | //Assert 75 | Assert.NotNull(actionContext.Response); 76 | Assert.True(actionContext.Response.Content.Headers.ContentType.MediaType.Equals("application/json", StringComparison.OrdinalIgnoreCase)); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tests/WebApiDoodle.Net.Http.Client.Test/Internal/UriUtilTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using WebApiDoodle.Net.Http.Client.Internal; 6 | using WebApiDoodle.Net.Http.Client.Model; 7 | using Xunit; 8 | 9 | namespace WebApiDoodle.Net.Http.Client.Test.Internal { 10 | 11 | public class UriUtilTest { 12 | 13 | private const string BaseRequestPath = "api/cars"; 14 | private const string BaseRequestUriPath = "http://localhost/api/cars"; 15 | 16 | public class ResolveUriTemplate { 17 | 18 | [Fact] 19 | public void Returns_The_Expected_Uri_When_The_Inputs_Valid() { 20 | 21 | // Arrange 22 | var id = Guid.NewGuid().ToString(); 23 | var requestCommand = new FakeNameRequestCommand { Name = "foo" }; 24 | var parameters = new { id = id, name = requestCommand.Name }; 25 | 26 | // Act 27 | var requestUri = UriUtil.ResolveUriTemplate( 28 | "api/cars/{id}?name={name}", 29 | new QueryStringCollection(parameters)); 30 | 31 | // Assert 32 | Assert.Equal(string.Format("api/cars/{0}?name={1}", id, requestCommand.Name.ToLowerInvariant()), requestUri, StringComparer.InvariantCulture); 33 | } 34 | 35 | [Fact] 36 | public void Returns_The_Expected_Uri_When_The_Inputs_Valid_With_No_Template() { 37 | 38 | // Act 39 | var requestUri = UriUtil.ResolveUriTemplate("api/cars", null); 40 | 41 | // Assert 42 | Assert.Equal("api/cars", requestUri, StringComparer.InvariantCulture); 43 | } 44 | 45 | [Fact] 46 | public void Returns_The_Expected_Uri_When_The_Inputs_Valid_With_No_Template_And_QueryStringCollection() { 47 | 48 | // Arrange 49 | var requestCommand = new FakeNameAgeRequestCommand { Name = "Foo", Age = 36 }; 50 | 51 | // Act 52 | var requestUri = UriUtil.ResolveUriTemplate( 53 | "api/cars", new QueryStringCollection(requestCommand)); 54 | 55 | // Assert 56 | Assert.Equal(string.Format("api/cars?age={1}&name={0}", requestCommand.Name.ToLowerInvariant(), requestCommand.Age.ToString()), requestUri, StringComparer.InvariantCulture); 57 | } 58 | 59 | [Fact] 60 | public void Throws_InvalidOperationException_When_QueryStringCollection_Has_Less_Input_Than_Expected() { 61 | 62 | // Arrange 63 | var requestCommand = new FakeNameRequestCommand { Name = "foo" }; 64 | 65 | // Assert 66 | Assert.Throws(() => // Act 67 | UriUtil.ResolveUriTemplate( 68 | "api/cars?name={name}&surname={surname}", 69 | new QueryStringCollection(requestCommand))); 70 | } 71 | 72 | [Fact] 73 | public void Throws_InvalidOperationException_If_QueryStringCollection_Is_Null_When_Needed() { 74 | 75 | // Assert 76 | Assert.Throws(() => // Act 77 | UriUtil.ResolveUriTemplate( 78 | "api/cars?name={name}&surname={surname}", null)); 79 | } 80 | } 81 | 82 | private class FakeDto : IDto { 83 | 84 | public int Id { get; set; } 85 | } 86 | 87 | private class FakeNameRequestCommand { 88 | 89 | public string Name { get; set; } 90 | } 91 | 92 | private class FakeNameAgeRequestCommand : FakeNameRequestCommand { 93 | 94 | public int Age { get; set; } 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Web/Controllers/ApiControllerBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using System.Web.Http; 6 | using System.Web.Http.Controllers; 7 | 8 | namespace WebApiDoodle.Web.Controllers { 9 | 10 | /// 11 | /// Base APiController class which provides aditional two methods that run before and after the controller execution 12 | /// 13 | public abstract class ApiControllerBase : ApiController, IApiControllerBase { 14 | 15 | public override Task ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken) { 16 | 17 | //We cannot run the Initialize method here to prevent any inconsistency 18 | //because ExecuteAsync method doesn't like Request to be not null. 19 | //For example, Initialize method sets up some public Properties such as 20 | //ControllerContext, Request and Configuration. If the user tries to reach out to them 21 | //inside the OnControllerExecuting method and we don't run the Initialize, there 22 | //will be NullReferenceExceptions which is very bad. But we cannot set Request 23 | //as well. So, set ControllerContext and Configuration at least 24 | ControllerContext = controllerContext; 25 | Configuration = controllerContext.Configuration; 26 | 27 | HttpControllerExecutingContext controllerExecutingContext = new HttpControllerExecutingContext(controllerContext); 28 | 29 | try { 30 | 31 | //If the user try to reach out to the public Request property, 32 | //that property will be null because we cannot set that value before 33 | //the ExecuteAsync method is run. So, it shouldn't be used. 34 | OnControllerExecuting(controllerExecutingContext); 35 | } 36 | catch (Exception e) { 37 | 38 | OnControllerExecuted(null); 39 | return TaskHelpers.FromError(e); 40 | } 41 | 42 | //Set the response message if the response coming from OnControllerExecuting is not null 43 | //and OnControllerExecuting is successfully completed 44 | if (controllerExecutingContext.Response != null) { 45 | 46 | OnControllerExecuted(controllerExecutingContext.Response); 47 | return TaskHelpers.FromResult(controllerExecutingContext.Response); 48 | } 49 | 50 | //TODO: Handle this better. E.g: What happens when an exception is thrown? 51 | return base.ExecuteAsync(controllerContext, cancellationToken).ContinueWith(task => { 52 | 53 | var response = task.Result; 54 | OnControllerExecuted(response); 55 | return response; 56 | }); 57 | } 58 | 59 | /// 60 | /// The method which will run just before the controller execution. 61 | /// If this method sets HttpControllerExecutingContext.Response property to a non-null 62 | /// value, then the controller execution will be terminated and the given response 63 | /// message will be pass through. 64 | /// 65 | /// 66 | public virtual void OnControllerExecuting(HttpControllerExecutingContext controllerExecutingContext) { } 67 | 68 | /// 69 | /// The method which will run just after the controller execution. If the 70 | /// returned Task{HttpResponseMessage} object is not in the RanToCompletion state, 71 | /// the povided HttpResponseMessage parameter will be null. 72 | /// 73 | /// 74 | public virtual void OnControllerExecuted(HttpResponseMessage response) { } 75 | } 76 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Web/HttpRequestMessageExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Net; 4 | using System.Net.Http; 5 | using System.Web.Http; 6 | using System.Web.Http.Dependencies; 7 | 8 | namespace WebApiDoodle.Web { 9 | 10 | [EditorBrowsable(EditorBrowsableState.Never)] 11 | public static class HttpRequestMessageExtensions { 12 | 13 | /// 14 | /// Gets the value of through the registered 15 | /// instance for the instance. 16 | /// 17 | /// The type of dependency 18 | /// The instance 19 | public static TService GetService(this HttpRequestMessage request) { 20 | 21 | if (request == null) { 22 | throw Error.ArgumentNull("request"); 23 | } 24 | 25 | IDependencyScope dependencyScope = request.GetDependencyScope(); 26 | TService service = (TService)dependencyScope.GetService(typeof(TService)); 27 | 28 | return service; 29 | } 30 | 31 | /// 32 | /// Gets the value of associated with the specified key through the Properties dictionary 33 | /// of the instance or default value if either the key is 34 | /// not present or the value is not of type . 35 | /// 36 | /// The type of the value associated with the specified key. 37 | /// The instance 38 | /// The key whose value to get. 39 | /// The object of type if key was found, value is non-null, and value is of type ; otherwise null. 40 | public static T GetProperty(this HttpRequestMessage request, string key) { 41 | 42 | if (request == null) { 43 | throw Error.ArgumentNull("request"); 44 | } 45 | 46 | T value; 47 | request.Properties.TryGetValue(key, out value); 48 | return value; 49 | } 50 | 51 | // privates and internals 52 | internal static HttpResponseMessage CreateErrorResponse(this HttpRequestMessage request, HttpStatusCode statusCode, string message, string messageDetail) { 53 | 54 | HttpError httpError = new HttpError(message); 55 | return request.CreateErrorResponse(statusCode, includeErrorDetail => includeErrorDetail ? httpError.AddAndReturn(HttpErrorConstants.MessageDetailKey, messageDetail) : httpError); 56 | } 57 | 58 | private static HttpResponseMessage CreateErrorResponse(this HttpRequestMessage request, HttpStatusCode statusCode, Func errorCreator) { 59 | 60 | HttpConfiguration configuration = request.GetConfiguration(); 61 | 62 | // CreateErrorResponse should never fail, even if there is no configuration associated with the request 63 | // In that case, use the default HttpConfiguration to con-neg the response media type 64 | if (configuration == null) { 65 | 66 | using (HttpConfiguration defaultConfig = new HttpConfiguration()) { 67 | 68 | HttpError error = errorCreator(defaultConfig.ShouldIncludeErrorDetail(request)); 69 | return request.CreateResponse(statusCode, error, defaultConfig); 70 | } 71 | } 72 | else { 73 | 74 | HttpError error = errorCreator(configuration.ShouldIncludeErrorDetail(request)); 75 | return request.CreateResponse(statusCode, error, configuration); 76 | } 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http.Client/InternalResource.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.18010 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace WebApiDoodle.Net.Http.Client { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class InternalResource { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal InternalResource() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WebApiDoodle.Net.Http.Client.InternalResource", typeof(InternalResource).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized string similar to Value for requested parameter named '{0}' is not available.. 65 | /// 66 | internal static string ResolveUriTemplate_NoValueForRequestedParam { 67 | get { 68 | return ResourceManager.GetString("ResolveUriTemplate_NoValueForRequestedParam", resourceCulture); 69 | } 70 | } 71 | 72 | /// 73 | /// Looks up a localized string similar to Passed paramater value amount is lower than the required amount by the uriTemplate.. 74 | /// 75 | internal static string ResolveUriTemplate_PassedParamaterValueAmountErrorMessage { 76 | get { 77 | return ResourceManager.GetString("ResolveUriTemplate_PassedParamaterValueAmountErrorMessage", resourceCulture); 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http/WebApiDoodle.Net.Http.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {4C07C35B-D14B-4F20-969D-EC6630BC2A99} 8 | Library 9 | Properties 10 | WebApiDoodle.Net.Http 11 | WebApiDoodle.Net.Http 12 | v4.0 13 | 512 14 | ..\..\ 15 | true 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 1591 26 | bin\Debug\WebApiDoodle.Net.Http.XML 27 | 28 | 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 1591 36 | bin\Release\WebApiDoodle.Net.Http.XML 37 | 38 | 39 | 40 | 41 | 42 | ..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll 43 | 44 | 45 | ..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll 46 | 47 | 48 | 49 | 50 | 51 | 52 | Properties\CommonAssemblyInfo.cs 53 | 54 | 55 | Common\TaskHelpers.cs 56 | 57 | 58 | Common\TaskHelpersExtensions.cs 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 79 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http.Client/Internal/UriUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace WebApiDoodle.Net.Http.Client.Internal { 6 | 7 | internal static class UriUtil { 8 | 9 | private static readonly Regex _uriParamRegex = new Regex(@"(?<=\{)(.*?)(?=\})", RegexOptions.Compiled); 10 | 11 | internal static string BuildRequestUri(string baseUri, string uriTemplate, object uriParameters = null) { 12 | 13 | var trimedUriTemplate = uriTemplate.TrimEnd('/').TrimStart('/').ToLowerInvariant(); 14 | QueryStringCollection queryStringCollection = null; 15 | if (uriParameters != null) { 16 | queryStringCollection = new QueryStringCollection(uriParameters); 17 | } 18 | 19 | string resolvedUriTemplate = ResolveUriTemplate(trimedUriTemplate, queryStringCollection); 20 | string appendedUriTemplate = string.Format("{0}/{1}", baseUri, resolvedUriTemplate); 21 | 22 | return appendedUriTemplate; 23 | } 24 | 25 | internal static string ResolveUriTemplate(string uriTemplate, QueryStringCollection queryStringCollection) { 26 | 27 | var createdUriPath = uriTemplate; 28 | MatchCollection matchedParamNames = _uriParamRegex.Matches(uriTemplate); 29 | int matchedParamNameAmount = matchedParamNames.Count; 30 | if (matchedParamNameAmount != 0) { 31 | 32 | // TODO: Regex match operration + this operation's result can be cached. 33 | // Build a key from the base address and the uriTemplate and look that up. 34 | 35 | // Get the match values under an array. 36 | string[] matchedParameterNameArray = new string[matchedParamNameAmount]; 37 | for (int i = 0; i < matchedParamNameAmount; i++) { 38 | 39 | matchedParameterNameArray[i] = matchedParamNames[i].Value; 40 | } 41 | 42 | // Check here that enough number of parameters are available. 43 | // We assume here that we have the queryStringCollection.Count and id parameter if queryStringCollection is not null 44 | if (((queryStringCollection != null) ? queryStringCollection.Count : 0) < matchedParamNameAmount) { 45 | 46 | throw new InvalidOperationException(InternalResource.ResolveUriTemplate_PassedParamaterValueAmountErrorMessage); 47 | } 48 | 49 | foreach (var paramName in matchedParameterNameArray) { 50 | 51 | string paramValueFromQueryStringCollection = null; 52 | if (queryStringCollection == null) { 53 | 54 | throw new InvalidOperationException( 55 | string.Format(InternalResource.ResolveUriTemplate_PassedParamaterValueAmountErrorMessage, paramName)); 56 | } 57 | else { 58 | 59 | var paramFromQueryStringCollection = queryStringCollection.FirstOrDefault(x => x.Key.Equals(paramName, StringComparison.InvariantCultureIgnoreCase)); 60 | paramValueFromQueryStringCollection = paramFromQueryStringCollection.Value; 61 | if (paramValueFromQueryStringCollection == null) { 62 | 63 | throw new InvalidOperationException( 64 | string.Format(InternalResource.ResolveUriTemplate_PassedParamaterValueAmountErrorMessage, paramName)); 65 | } 66 | 67 | // Remove the selected one because it will be 68 | // used for querystring composition. 69 | queryStringCollection.Remove(paramFromQueryStringCollection); 70 | } 71 | 72 | string paramValue = paramValueFromQueryStringCollection; 73 | createdUriPath = createdUriPath.Replace("{" + paramName + "}", paramValue); 74 | } 75 | } 76 | 77 | return ((queryStringCollection != null) && queryStringCollection.Count > 0) 78 | ? string.Format("{0}?{1}", createdUriPath, queryStringCollection.ToString().ToLowerInvariant()) : createdUriPath; 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http.Formatting/CSVMediaTypeFormatter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Net.Http; 8 | using System.Net.Http.Formatting; 9 | using System.Net.Http.Headers; 10 | using System.Threading.Tasks; 11 | 12 | namespace WebApiDoodle.Net.Http.Formatting { 13 | 14 | public class CSVMediaTypeFormatter : MediaTypeFormatter { 15 | 16 | public CSVMediaTypeFormatter() { 17 | 18 | SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/csv")); 19 | } 20 | 21 | public CSVMediaTypeFormatter(MediaTypeMapping mediaTypeMapping) : this() { 22 | 23 | MediaTypeMappings.Add(mediaTypeMapping); 24 | } 25 | 26 | public CSVMediaTypeFormatter(IEnumerable mediaTypeMappings) : this() { 27 | 28 | foreach (var mediaTypeMapping in mediaTypeMappings) { 29 | MediaTypeMappings.Add(mediaTypeMapping); 30 | } 31 | } 32 | 33 | public override bool CanReadType(Type type) { 34 | 35 | return false; 36 | } 37 | 38 | public override bool CanWriteType(Type type) { 39 | 40 | if (type == null) 41 | throw new ArgumentNullException("type"); 42 | 43 | return isTypeOfIEnumerable(type); 44 | } 45 | 46 | public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext) { 47 | 48 | return TaskHelpers.RunSynchronously(() => { 49 | writeToStream(type, value, writeStream, content.Headers); 50 | }); 51 | } 52 | 53 | private void writeToStream(Type type, object value, Stream stream, HttpContentHeaders contentHeaders) { 54 | 55 | //NOTE: We have check the type inside CanWriteType method 56 | //If request comes this far, the type is IEnumerable. We are safe. 57 | 58 | Type itemType = type.GetGenericArguments()[0]; 59 | 60 | StringWriter _stringWriter = new StringWriter(); 61 | 62 | _stringWriter.WriteLine( 63 | string.Join( 64 | ",", itemType.GetProperties().Select(x => x.Name) 65 | ) 66 | ); 67 | 68 | foreach (var obj in (IEnumerable)value) { 69 | 70 | var vals = obj.GetType().GetProperties().Select( 71 | pi => new { 72 | Value = pi.GetValue(obj, null) 73 | } 74 | ); 75 | 76 | string _valueLine = string.Empty; 77 | 78 | foreach (var val in vals) { 79 | 80 | if (val.Value != null) { 81 | 82 | var _val = val.Value.ToString(); 83 | 84 | //Check if the value contans a comma and place it in quotes if so 85 | if (_val.Contains(",")) 86 | _val = string.Concat("\"", _val, "\""); 87 | 88 | //Replace any \r or \n special characters from a new line with a space 89 | if (_val.Contains("\r")) 90 | _val = _val.Replace("\r", " "); 91 | if (_val.Contains("\n")) 92 | _val = _val.Replace("\n", " "); 93 | 94 | _valueLine = string.Concat(_valueLine, _val, ","); 95 | 96 | } 97 | else { 98 | 99 | _valueLine = string.Concat(string.Empty, ","); 100 | } 101 | } 102 | 103 | _stringWriter.WriteLine(_valueLine.TrimEnd(',')); 104 | } 105 | 106 | var streamWriter = new StreamWriter(stream); 107 | streamWriter.Write(_stringWriter.ToString()); 108 | } 109 | 110 | private bool isTypeOfIEnumerable(Type type) { 111 | 112 | foreach (Type interfaceType in type.GetInterfaces()) { 113 | 114 | if (interfaceType == typeof(IEnumerable)) 115 | return true; 116 | } 117 | 118 | return false; 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http.Client/HttpApiError.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Xml; 5 | using System.Xml.Schema; 6 | using System.Xml.Serialization; 7 | 8 | namespace WebApiDoodle.Net.Http.Client { 9 | 10 | [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "This type is only a dictionary to get the right serialization format")] 11 | [SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable", Justification = "DCS does not support IXmlSerializable types that are also marked as [Serializable]")] 12 | [XmlRoot("Error")] 13 | public class HttpApiError : Dictionary, IXmlSerializable { 14 | 15 | private const string MessageKey = "Message"; 16 | private const string MessageDetailKey = "MessageDetail"; 17 | private const string ModelStateKey = "ModelState"; 18 | private const string ExceptionMessageKey = "ExceptionMessage"; 19 | private const string ExceptionTypeKey = "ExceptionType"; 20 | private const string StackTraceKey = "StackTrace"; 21 | private const string InnerExceptionKey = "InnerException"; 22 | 23 | public string Message { 24 | get { return GetPropertyValue(MessageKey); } 25 | set { this[MessageKey] = value; } 26 | } 27 | 28 | // NOTE: Couldn't make it work in a formatter agnostic way. 29 | //public HttpApiError ModelState { 30 | // get { return GetPropertyValue(ModelStateKey); } 31 | //} 32 | 33 | public string MessageDetail { 34 | get { return GetPropertyValue(MessageDetailKey); } 35 | set { this[MessageDetailKey] = value; } 36 | } 37 | 38 | public string ExceptionMessage { 39 | get { return GetPropertyValue(ExceptionMessageKey); } 40 | set { this[ExceptionMessageKey] = value; } 41 | } 42 | 43 | public string ExceptionType { 44 | get { return GetPropertyValue(ExceptionTypeKey); } 45 | set { this[ExceptionTypeKey] = value; } 46 | } 47 | 48 | public string StackTrace { 49 | get { return GetPropertyValue(StackTraceKey); } 50 | set { this[StackTraceKey] = value; } 51 | } 52 | 53 | public HttpApiError InnerException { 54 | get { return GetPropertyValue(InnerExceptionKey); } 55 | } 56 | 57 | public TValue GetPropertyValue(string key) { 58 | 59 | TValue value; 60 | if (this.TryGetValue(key, out value)) { 61 | return value; 62 | } 63 | return default(TValue); 64 | } 65 | 66 | XmlSchema IXmlSerializable.GetSchema() { 67 | return null; 68 | } 69 | 70 | void IXmlSerializable.ReadXml(XmlReader reader) { 71 | 72 | if (reader.IsEmptyElement) { 73 | reader.Read(); 74 | return; 75 | } 76 | 77 | reader.ReadStartElement(); 78 | 79 | while (reader.NodeType != System.Xml.XmlNodeType.EndElement) { 80 | string key = XmlConvert.DecodeName(reader.LocalName); 81 | string value = reader.ReadInnerXml(); 82 | 83 | this.Add(key, value); 84 | reader.MoveToContent(); 85 | } 86 | 87 | reader.ReadEndElement(); 88 | } 89 | 90 | void IXmlSerializable.WriteXml(XmlWriter writer) { 91 | 92 | foreach (KeyValuePair keyValuePair in this) { 93 | 94 | string key = keyValuePair.Key; 95 | object value = keyValuePair.Value; 96 | writer.WriteStartElement(XmlConvert.EncodeLocalName(key)); 97 | if (value != null) { 98 | 99 | HttpApiError innerError = value as HttpApiError; 100 | if (innerError == null) { 101 | writer.WriteValue(value); 102 | } 103 | else { 104 | ((IXmlSerializable)innerError).WriteXml(writer); 105 | } 106 | } 107 | writer.WriteEndElement(); 108 | } 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /tests/WebApiDoodle.Test/Util/ContextUtil.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Net.Http; 6 | using System.Text; 7 | using System.Web.Http; 8 | using System.Web.Http.Controllers; 9 | using System.Web.Http.Filters; 10 | using System.Web.Http.Hosting; 11 | using System.Web.Http.Routing; 12 | 13 | namespace WebApiDoodle.Web.Test { 14 | 15 | internal static class ContextUtil { 16 | 17 | public static HttpControllerContext CreateControllerContext(IHttpController instance, string controllerName, Type controllerType, HttpConfiguration configuration = null, IHttpRouteData routeData = null, HttpRequestMessage request = null) { 18 | 19 | HttpConfiguration config = configuration ?? new HttpConfiguration(); 20 | IHttpRouteData route = routeData ?? new HttpRouteData(new HttpRoute()); 21 | HttpRequestMessage req = request ?? new HttpRequestMessage(); 22 | req.Properties[HttpPropertyKeys.HttpConfigurationKey] = config; 23 | req.Properties[HttpPropertyKeys.HttpRouteDataKey] = route; 24 | 25 | 26 | HttpControllerContext context = new HttpControllerContext(config, route, req); 27 | context.Controller = instance; 28 | context.ControllerDescriptor = CreateControllerDescriptor(controllerName, controllerType, config); 29 | 30 | return context; 31 | } 32 | 33 | public static HttpControllerDescriptor CreateControllerDescriptor(string controllerName, Type controllerType, HttpConfiguration config = null) { 34 | 35 | if (config == null) { 36 | 37 | config = new HttpConfiguration(); 38 | } 39 | return new HttpControllerDescriptor(config, controllerName, controllerType); 40 | } 41 | 42 | public static HttpControllerDescriptor CreateControllerDescriptor(HttpConfiguration configuration = null) { 43 | 44 | HttpConfiguration config = configuration ?? new HttpConfiguration(); 45 | HttpControllerDescriptor controllerDescriptor = new HttpControllerDescriptor(); 46 | controllerDescriptor.Configuration = configuration; 47 | 48 | return controllerDescriptor; 49 | } 50 | 51 | public static HttpControllerContext CreateControllerContext( 52 | HttpConfiguration configuration = null, IHttpController controller = null, IHttpRouteData routeData = null, HttpRequestMessage request = null) { 53 | 54 | HttpConfiguration config = configuration ?? new HttpConfiguration(); 55 | IHttpRouteData route = routeData ?? new HttpRouteData(new HttpRoute()); 56 | HttpRequestMessage req = request ?? new HttpRequestMessage(); 57 | req.Properties[HttpPropertyKeys.HttpConfigurationKey] = config; 58 | req.Properties[HttpPropertyKeys.HttpRouteDataKey] = route; 59 | 60 | HttpControllerContext context = new HttpControllerContext(config, route, req); 61 | if (controller != null) { 62 | context.Controller = controller; 63 | } 64 | context.ControllerDescriptor = CreateControllerDescriptor(config); 65 | 66 | return context; 67 | } 68 | 69 | public static HttpActionContext CreateActionContext( 70 | HttpControllerContext controllerContext = null, HttpActionDescriptor actionDescriptor = null, HttpRequestMessage request = null) { 71 | 72 | HttpControllerContext controllerCtx = controllerContext ?? CreateControllerContext(request: request); 73 | HttpActionDescriptor descriptor = actionDescriptor ?? new Mock() { CallBase = true }.Object; 74 | 75 | return new HttpActionContext(controllerCtx, descriptor); 76 | } 77 | 78 | public static HttpActionContext GetHttpActionContext(HttpRequestMessage request) { 79 | 80 | HttpActionContext actionContext = CreateActionContext(request: request); 81 | return actionContext; 82 | } 83 | 84 | public static HttpActionExecutedContext GetActionExecutedContext(HttpRequestMessage request, HttpResponseMessage response) { 85 | 86 | HttpActionContext actionContext = CreateActionContext(); 87 | actionContext.ControllerContext.Request = request; 88 | HttpActionExecutedContext actionExecutedContext = new HttpActionExecutedContext(actionContext, null) { Response = response }; 89 | 90 | return actionExecutedContext; 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /src/WebApiDoodle.Net.Http.Formatting/WebApiDoodle.Net.Http.Formatting.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {4E9C705E-8964-401E-B2B1-A766B5FF9B32} 8 | Library 9 | Properties 10 | WebApiDoodle.Net.Http.Formatting 11 | WebApiDoodle.Net.Http.Formatting 12 | v4.0 13 | 512 14 | ..\..\ 15 | true 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 1591 26 | bin\Debug\WebApiDoodle.Net.Http.Formatting.XML 27 | 28 | 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 1591 36 | bin\Release\WebApiDoodle.Net.Http.Formatting.XML 37 | 38 | 39 | 40 | False 41 | ..\..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll 42 | 43 | 44 | 45 | 46 | ..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll 47 | 48 | 49 | ..\..\packages\Microsoft.AspNet.WebApi.Client.4.0.20710.0\lib\net40\System.Net.Http.Formatting.dll 50 | 51 | 52 | ..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll 53 | 54 | 55 | 56 | 57 | 58 | 59 | Properties\CommonAssemblyInfo.cs 60 | 61 | 62 | Common\TaskHelpers.cs 63 | 64 | 65 | Common\TaskHelpersExtensions.cs 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 85 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Web/Common/TypeHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.ComponentModel; 5 | using System.Diagnostics.Contracts; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Web.Http; 10 | using System.Web.Http.Controllers; 11 | 12 | namespace WebApiDoodle.Web { 13 | 14 | /// 15 | /// A static class that provides various related helpers. 16 | /// 17 | /// Taken from ASP.NET Web API source code. 18 | internal static class TypeHelper { 19 | 20 | private static readonly Type TaskGenericType = typeof(Task<>); 21 | 22 | internal static readonly Type HttpControllerType = typeof(IHttpController); 23 | internal static readonly Type ApiControllerType = typeof(ApiController); 24 | 25 | internal static Type GetTaskInnerTypeOrNull(Type type) { 26 | 27 | Contract.Assert(type != null); 28 | if (type.IsGenericType && !type.IsGenericTypeDefinition) { 29 | Type genericTypeDefinition = type.GetGenericTypeDefinition(); 30 | // REVIEW: should we consider subclasses of Task<> ?? 31 | if (TaskGenericType == genericTypeDefinition) { 32 | return type.GetGenericArguments()[0]; 33 | } 34 | } 35 | 36 | return null; 37 | } 38 | 39 | internal static Type ExtractGenericInterface(Type queryType, Type interfaceType) { 40 | 41 | Func matchesInterface = t => t.IsGenericType && t.GetGenericTypeDefinition() == interfaceType; 42 | return matchesInterface(queryType) ? queryType : queryType.GetInterfaces().FirstOrDefault(matchesInterface); 43 | } 44 | 45 | internal static Type[] GetTypeArgumentsIfMatch(Type closedType, Type matchingOpenType) { 46 | 47 | if (!closedType.IsGenericType) { 48 | return null; 49 | } 50 | 51 | Type openType = closedType.GetGenericTypeDefinition(); 52 | return (matchingOpenType == openType) ? closedType.GetGenericArguments() : null; 53 | } 54 | 55 | internal static bool IsCompatibleObject(Type type, object value) { 56 | 57 | return (value == null && TypeAllowsNullValue(type)) || type.IsInstanceOfType(value); 58 | } 59 | 60 | internal static bool IsNullableValueType(Type type) { 61 | 62 | return Nullable.GetUnderlyingType(type) != null; 63 | } 64 | 65 | internal static bool TypeAllowsNullValue(Type type) { 66 | 67 | return !type.IsValueType || IsNullableValueType(type); 68 | } 69 | 70 | internal static bool IsSimpleType(Type type) { 71 | 72 | return type.IsPrimitive || 73 | type.Equals(typeof(string)) || 74 | type.Equals(typeof(DateTime)) || 75 | type.Equals(typeof(Decimal)) || 76 | type.Equals(typeof(Guid)) || 77 | type.Equals(typeof(DateTimeOffset)) || 78 | type.Equals(typeof(TimeSpan)); 79 | } 80 | 81 | internal static bool IsSimpleUnderlyingType(Type type) { 82 | 83 | Type underlyingType = Nullable.GetUnderlyingType(type); 84 | if (underlyingType != null) { 85 | type = underlyingType; 86 | } 87 | 88 | return TypeHelper.IsSimpleType(type); 89 | } 90 | 91 | internal static bool HasStringConverter(Type type) { 92 | 93 | return TypeDescriptor.GetConverter(type).CanConvertFrom(typeof(string)); 94 | } 95 | 96 | /// 97 | /// Fast implementation to get the subset of a given type. 98 | /// 99 | /// type to search for 100 | /// subset of objects that can be assigned to T 101 | internal static ReadOnlyCollection OfType(object[] objects) where T : class { 102 | 103 | int max = objects.Length; 104 | List list = new List(max); 105 | int idx = 0; 106 | for (int i = 0; i < max; i++) { 107 | 108 | T attr = objects[i] as T; 109 | if (attr != null) { 110 | list.Add(attr); 111 | idx++; 112 | } 113 | } 114 | list.Capacity = idx; 115 | 116 | return new ReadOnlyCollection(list); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Testing/WebApiDoodle.Testing.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {72A10F73-8FB8-40F4-AB18-EAF828C1B32D} 8 | Library 9 | Properties 10 | WebApiDoodle.Testing 11 | WebApiDoodle.Testing 12 | v4.0 13 | 512 14 | ..\..\ 15 | true 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 1591 26 | bin\Debug\WebApiDoodle.Testing.XML 27 | 28 | 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 1591 36 | bin\Release\WebApiDoodle.Testing.XML 37 | 38 | 39 | 40 | ..\..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll 41 | 42 | 43 | 44 | 45 | ..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll 46 | 47 | 48 | ..\..\packages\Microsoft.AspNet.WebApi.Client.4.0.20710.0\lib\net40\System.Net.Http.Formatting.dll 49 | 50 | 51 | ..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll 52 | 53 | 54 | ..\..\packages\Microsoft.AspNet.WebApi.Core.4.0.20710.0\lib\net40\System.Web.Http.dll 55 | 56 | 57 | 58 | 59 | 60 | 61 | Properties\CommonAssemblyInfo.cs 62 | 63 | 64 | Common\TaskHelpers.cs 65 | 66 | 67 | Common\TaskHelpersExtensions.cs 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 86 | -------------------------------------------------------------------------------- /src/WebApiDoodle.Web.Hosting.WebHost45/WebApiDoodle.Web.Hosting.WebHost45.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {BAEF1A2F-84D5-437D-A55F-3FD88F184B0C} 8 | Library 9 | Properties 10 | WebApiDoodle.Web.Hosting.WebHost45 11 | WebApiDoodle.Web.Hosting.WebHost45 12 | v4.5 13 | 512 14 | ..\..\ 15 | true 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 1591 26 | bin\Debug\WebApiDoodle.Web.Hosting.WebHost45.XML 27 | 28 | 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 1591 36 | bin\Release\WebApiDoodle.Web.Hosting.WebHost45.XML 37 | 38 | 39 | 40 | True 41 | ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll 42 | 43 | 44 | ..\..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll 45 | 46 | 47 | 48 | 49 | 50 | ..\..\packages\Microsoft.AspNet.WebApi.Client.4.0.20710.0\lib\net40\System.Net.Http.Formatting.dll 51 | 52 | 53 | 54 | 55 | ..\..\packages\Microsoft.AspNet.WebApi.Core.4.0.20710.0\lib\net40\System.Web.Http.dll 56 | 57 | 58 | 59 | 60 | 61 | 62 | Properties\CommonAssemblyInfo.cs 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 86 | -------------------------------------------------------------------------------- /tests/WebApiDoodle.Net.Http.Client.Test/WebApiDoodle.Net.Http.Client.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {9E54CB6F-F483-4130-815A-84745D2462A8} 8 | Library 9 | Properties 10 | WebApiDoodle.Net.Http.Client.Test 11 | WebApiDoodle.Net.Http.Client.Test 12 | v4.0 13 | 512 14 | ..\..\ 15 | true 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | ..\..\packages\Moq.4.0.10827\lib\NET40\Moq.dll 37 | 38 | 39 | False 40 | ..\..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll 41 | 42 | 43 | 44 | 45 | ..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll 46 | 47 | 48 | ..\..\packages\Microsoft.AspNet.WebApi.Client.4.0.20710.0\lib\net40\System.Net.Http.Formatting.dll 49 | 50 | 51 | ..\..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll 52 | 53 | 54 | 55 | 56 | 57 | 58 | ..\..\packages\xunit.1.9.1\lib\net20\xunit.dll 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | {73f9af6e-4ddf-4051-b714-c4b41106dae8} 70 | WebApiDoodle.Net.Http.Client.Model 71 | 72 | 73 | {21da89f7-84b9-4122-8a5e-3686e8a35bcc} 74 | WebApiDoodle.Net.Http.Client 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 89 | -------------------------------------------------------------------------------- /tests/Common/TaskHelpersTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace WebApiDoodle.Web.Test { 9 | 10 | //mostly taken from ASP.NET Web Stack source: http://aspnetwebstack.codeplex.com 11 | public class TaskHelpersTest { 12 | 13 | //TaskHelpers.Canceled 14 | [Fact] 15 | public void Canceled_ReturnsCanceledTask() { 16 | 17 | Task task = TaskHelpers.Canceled(); 18 | 19 | Assert.NotNull(task); 20 | Assert.True(task.IsCanceled); 21 | } 22 | 23 | //TaskHelpers.Canceled 24 | [Fact] 25 | public void Canceled_Generic_ReturnsCanceledTask() { 26 | 27 | Task task = TaskHelpers.Canceled(); 28 | 29 | Assert.NotNull(task); 30 | Assert.True(task.IsCanceled); 31 | } 32 | 33 | //TaskHelpers.Completed 34 | [Fact] 35 | public void Completed_RuturnsCompletedTask() { 36 | 37 | Task task = TaskHelpers.Completed(); 38 | 39 | Assert.NotNull(task); 40 | Assert.Equal(TaskStatus.RanToCompletion, task.Status); 41 | } 42 | 43 | //TaskHelpers.FromError 44 | [Fact] 45 | public void FromError_RuturnsFaultedTaskWithTheGivenException() { 46 | 47 | var exception = new Exception(); 48 | Task task = TaskHelpers.FromError(exception); 49 | 50 | Assert.NotNull(task); 51 | Assert.True(task.IsFaulted); 52 | Assert.Same(exception, task.Exception.InnerException); 53 | } 54 | 55 | //TaskHelpers.FromError 56 | [Fact] 57 | public void FromError_Generic_RuturnsFaultedTaskWithTheGivenException() { 58 | 59 | var exception = new Exception(); 60 | Task task = TaskHelpers.FromError(exception); 61 | 62 | Assert.NotNull(task); 63 | Assert.True(task.IsFaulted); 64 | Assert.Same(exception, task.Exception.InnerException); 65 | } 66 | 67 | //TaskHelpers.FromErrors 68 | [Fact] 69 | public void FromErrors_RuturnsFaultedTaskWithTheGivenExceptions() { 70 | 71 | var exceptions = new[] { new Exception(), new InvalidOperationException() }; 72 | Task task = TaskHelpers.FromErrors(exceptions); 73 | 74 | Assert.NotNull(task); 75 | Assert.True(task.IsFaulted); 76 | Assert.Equal(exceptions, task.Exception.InnerExceptions.ToArray()); 77 | } 78 | 79 | // ----------------------------------------------------------------- 80 | // TaskHelpers.TrySetFromTask 81 | 82 | [Fact] 83 | public void TrySetFromTask_IfSourceTaskIsCanceled_CancelsTaskCompletionSource() { 84 | TaskCompletionSource tcs = new TaskCompletionSource(); 85 | Task canceledTask = TaskHelpers.Canceled(); 86 | 87 | tcs.TrySetFromTask(canceledTask); 88 | 89 | Assert.Equal(TaskStatus.Canceled, tcs.Task.Status); 90 | } 91 | 92 | [Fact] 93 | public void TrySetFromTask_IfSourceTaskIsFaulted_FaultsTaskCompletionSource() { 94 | TaskCompletionSource tcs = new TaskCompletionSource(); 95 | Exception exception = new Exception(); 96 | Task faultedTask = TaskHelpers.FromError(exception); 97 | 98 | tcs.TrySetFromTask(faultedTask); 99 | 100 | Assert.Equal(TaskStatus.Faulted, tcs.Task.Status); 101 | Assert.Same(exception, tcs.Task.Exception.InnerException); 102 | } 103 | 104 | [Fact] 105 | public void TrySetFromTask_IfSourceTaskIsSuccessfulAndOfSameResultType_SucceedsTaskCompletionSourceAndSetsResult() { 106 | TaskCompletionSource tcs = new TaskCompletionSource(); 107 | Task successfulTask = TaskHelpers.FromResult("abc"); 108 | 109 | tcs.TrySetFromTask(successfulTask); 110 | 111 | Assert.Equal(TaskStatus.RanToCompletion, tcs.Task.Status); 112 | Assert.Equal("abc", tcs.Task.Result); 113 | } 114 | 115 | [Fact] 116 | public void TrySetFromTask_IfSourceTaskIsSuccessfulAndOfDifferentResultType_SucceedsTaskCompletionSourceAndSetsDefaultValueAsResult() { 117 | TaskCompletionSource tcs = new TaskCompletionSource(); 118 | Task successfulTask = TaskHelpers.FromResult(new object()); 119 | 120 | tcs.TrySetFromTask(successfulTask); 121 | 122 | Assert.Equal(TaskStatus.RanToCompletion, tcs.Task.Status); 123 | Assert.Equal(null, tcs.Task.Result); 124 | } 125 | 126 | //TODO: 127 | //TaskHelpers.FromErrors 128 | 129 | //TODO: 130 | //TaskHelpers.FromResult 131 | 132 | //TODO: 133 | //TaskHelpers.RunSynchronously 134 | } 135 | } --------------------------------------------------------------------------------