From 68fbfd7643c90935e4d12eaaa61f7f991d7c6daa Mon Sep 17 00:00:00 2001 From: Connor Laderer Date: Sat, 30 Jun 2018 00:14:43 -0400 Subject: [PATCH] v0.2 --- Build.ps1 | 12 + README.md | 8 +- appveyor.yml | 10 +- .../ClientWriter.cs | 36 +- src/AspNetCore.Client.Generator/Constants.cs | 36 ++ .../Data/ClassDefinition.cs | 50 +- .../Data/MethodDefinition.cs | 129 ++-- src/AspNetCore.Client.Generator/Settings.cs | 1 + .../Program.cs | 55 +- .../ClientGeneratorSettings.json | 1 + test/TestBlazorApp.Clients/Clients.cs | 62 +- .../ClientGeneratorSettings.json | 1 + test/TestWebApp.Clients/Clients.cs | 566 ++++++++++++++---- test/TestWebApp.Tests/ClientTest.cs | 47 +- test/TestWebApp.Tests/FakeHttpOverride.cs | 41 ++ test/TestWebApp.Tests/TestWebApp.Tests.csproj | 1 + .../Controllers/ValuesController.cs | 8 + 17 files changed, 842 insertions(+), 222 deletions(-) create mode 100644 src/AspNetCore.Client.Generator/Constants.cs create mode 100644 test/TestWebApp.Tests/FakeHttpOverride.cs diff --git a/Build.ps1 b/Build.ps1 index b4a466d..27d2d31 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -14,7 +14,9 @@ if([System.String]::IsNullOrEmpty($version)){ } $artifacts = "$scriptBin/artifacts"; +$testGenerator = Resolve-Path "$scriptBin/test/AspNetCore.Client.Test.Generator"; +Remove-Item $artifacts -Recurse -ErrorAction Ignore New-Item -Force -ItemType directory -Path $artifacts $outputDir = Resolve-Path $artifacts; @@ -26,6 +28,16 @@ Write-Host "Building Version $version"; Write-Host ">> dotnet build -c Release -v m;" dotnet build -c Release -v m; +#Run the test project generators +Push-Location -Path $testGenerator -StackName "Run"; +Write-Host ">> dotnet run -c Release -v m;" +dotnet run -c Release -v m; +Pop-Location -StackName "Run"; + +#Build again, making sure that our clients that were just regenerated via the previous command build +Write-Host ">> dotnet build -c Release -v m;" +dotnet build -c Release -v m; + Write-Host ">> dotnet test -c Release -v m;" dotnet test -c Release -v m; diff --git a/README.md b/README.md index d4c1b27..b74bdac 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Package that contains required classes/attributes used by the generator package. On Build generator that will generate a Clients.cs file based on the ClientGeneratorSettings.json file the generator creates. + #### ClientGeneratorSettings.json Reference - Locked @@ -30,8 +31,13 @@ On Build generator that will generate a Clients.cs file based on the ClientGener - Whether or not the generate the clients so they are compatible with Blazor views - Differences include - Newtonsoft.Json.JsonConvert.DeserializeObject => JsonUtil.Deserialize - - Namespace include changes. + - Default namespaces are different. - Requires a reference to [Microsoft.AspNetCore.Blazor](https://www.nuget.org/packages/Microsoft.AspNetCore.Blazor/) inside the client project +- IncludeHttpOverride + - You will be required to provide the service registration for `IHttpOverride`. + - Allows for overrides to a separate service that will check to see if a HttpResponseMessage can be provided by it instead so it doesn't need to make the http call. + - Useful if you want to intercept a request before it goes out depending on internal flags, etc. + - Can be used with mocking - AllowedNamespaces - Namespaces allowed to be pulled from Controllers that the generator is pulling the data from. - ex) You have `using MyApp.Contracts;` inside the Controller, if MyApp.Contracts is inside the allowed namespaces, it would be copied into the Clients.cs diff --git a/appveyor.yml b/appveyor.yml index 40bf609..4f8b873 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,15 +1,7 @@ -version: '0.1.{build}' +version: '0.2.{build}' configuration: Release image: Visual Studio 2017 -max_jobs: 1 -skip_non_tags: true -clone_depth: 1 - -branches: - only: - - master - nuget: account_feed: true project_feed: true diff --git a/src/AspNetCore.Client.Generator/ClientWriter.cs b/src/AspNetCore.Client.Generator/ClientWriter.cs index 15cf5fc..4dbb346 100644 --- a/src/AspNetCore.Client.Generator/ClientWriter.cs +++ b/src/AspNetCore.Client.Generator/ClientWriter.cs @@ -1,9 +1,14 @@ -using AspNetCore.Client.Generator.Data; +using AspNetCore.Client.Core; +using AspNetCore.Client.Generator.Data; +using Flurl.Http; using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net.Http; using System.Text; +using System.Threading; +using System.Threading.Tasks; namespace AspNetCore.Client.Generator { @@ -49,21 +54,38 @@ public static IServiceCollection InstallClients(this IServiceCollection services "; } + public static string GetIncludePreHttpCheck() + { + if (!Settings.Instance.IncludeHttpOverride) + { + return string.Empty; + } + + + return $@" + public interface {Constants.HttpOverride} + {{ + {Helpers.GetTaskType()}<{nameof(HttpResponseMessage)}> {Constants.HttpOverrideGetMethod}({nameof(String)} {Constants.UrlVariable}, {nameof(CancellationToken)} {Constants.CancellationTokenParameter} = default({nameof(CancellationToken)})); + {nameof(Task)} {Constants.HttpOverrideOnNonOverridedResponse}({nameof(String)} {Constants.UrlVariable}, {nameof(HttpResponseMessage)} {Constants.ResponseVariable}, {nameof(CancellationToken)} {Constants.CancellationTokenParameter} = default({nameof(CancellationToken)})); + }} +"; + } + private static string GetServiceClients() { return $@" public class {Settings.Instance.ClientInterfaceName} {{ - public readonly FlurlClient ClientWrapper; + public readonly {nameof(FlurlClient)} {Constants.FlurlClientVariable}; - public {Settings.Instance.ClientInterfaceName}(HttpClient client) + public {Settings.Instance.ClientInterfaceName}({nameof(HttpClient)} client) {{ - ClientWrapper = new FlurlClient(client); + {Constants.FlurlClientVariable} = new {nameof(FlurlClient)}(client); }} }} - public interface I{Settings.Instance.ClientInterfaceName} : IClient {{ }}"; + public interface I{Settings.Instance.ClientInterfaceName} : {nameof(IClient)} {{ }}"; } public static void WriteClientsFile(IList parsedFiles) @@ -83,7 +105,8 @@ public static void WriteClientsFile(IList parsedFiles) "using AspNetCore.Client.Core;", "using AspNetCore.Client.Core.Authorization;", "using AspNetCore.Client.Core.Exceptions;", - "using Microsoft.Extensions.DependencyInjection;" + "using Microsoft.Extensions.DependencyInjection;", + "using System.Threading;" }; if (Settings.Instance.BlazorClients) @@ -133,6 +156,7 @@ namespace {Settings.Instance.Namespace} {{ {GetInstaller(parsedFiles)} +{GetIncludePreHttpCheck()} {GetServiceClients()} {string.Join(Environment.NewLine, blocks)} }} diff --git a/src/AspNetCore.Client.Generator/Constants.cs b/src/AspNetCore.Client.Generator/Constants.cs new file mode 100644 index 0000000..42b9a30 --- /dev/null +++ b/src/AspNetCore.Client.Generator/Constants.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace AspNetCore.Client.Generator +{ + public static class Constants + { + public const string Route = "Route"; + public const string Authorize = "Authorize"; + public const string Obsolete = "Obsolete"; + public const string Http = "Http"; + public const string Attribute = "Attribute"; + public const string AllowAnonymous = "AllowAnonymous"; + public const string ProducesResponseType = "ProducesResponseType"; + + public const string ClientInterfaceName = "Client"; + public const string FlurlClientVariable = "ClientWrapper"; + + public const string HttpOverride = "IHttpOverride"; + public const string HttpOverrideField = "HttpOverride"; + public const string HttpOverrideGetMethod = "GetResponseAsync"; + public const string HttpOverrideOnNonOverridedResponse = "OnNonOverridedResponseAsync"; + + public const string IActionResult = "IActionResult"; + + public const string ControllerRouteReserved = "controller"; + public const string ActionRouteReserved = "action"; + + public const string CancellationTokenParameter = "cancellationToken"; + + public const string ResponseVariable = "response"; + public const string UrlVariable = "url"; + public const string AuthParameter = "auth"; + } +} diff --git a/src/AspNetCore.Client.Generator/Data/ClassDefinition.cs b/src/AspNetCore.Client.Generator/Data/ClassDefinition.cs index 3fdb8f8..dac8566 100644 --- a/src/AspNetCore.Client.Generator/Data/ClassDefinition.cs +++ b/src/AspNetCore.Client.Generator/Data/ClassDefinition.cs @@ -54,7 +54,7 @@ public class ClassDefinition } - var routeAttribute = attributes.SingleOrDefault(x => x.Name.ToFullString().StartsWith("Route")); + var routeAttribute = attributes.SingleOrDefault(x => x.Name.ToFullString().StartsWith(Constants.Route)); if (routeAttribute != null)//Fetch route from RouteAttribute { Route = routeAttribute.ArgumentList.Arguments.ToFullString().Replace("\"", ""); @@ -74,10 +74,10 @@ public class ClassDefinition .ToList(); //Authorize Attribute - Options.Authorize = attributes.SingleOrDefault(x => x.Name.ToFullString().StartsWith("Authorize")) != null; + Options.Authorize = attributes.SingleOrDefault(x => x.Name.ToFullString().StartsWith(Constants.Authorize)) != null; //Obsolete Attribute - var obsoleteAttribute = attributes.SingleOrDefault(x => x.Name.ToFullString().StartsWith("Obsolete")); + var obsoleteAttribute = attributes.SingleOrDefault(x => x.Name.ToFullString().StartsWith(Constants.Obsolete)); if (obsoleteAttribute != null) { Options.Obsolete = obsoleteAttribute.ArgumentList.Arguments.ToFullString().Replace("\"", "").Trim(); @@ -91,6 +91,42 @@ public class ClassDefinition public string GetText() { + var fields = new List(); + + fields.Add($@" public readonly {Settings.Instance.ClientInterfaceName} {Constants.ClientInterfaceName};"); + if (Settings.Instance.IncludeHttpOverride) + { + fields.Add($@" public readonly {Constants.HttpOverride} {Constants.HttpOverrideField};"); + } + + var classFields = string.Join(Environment.NewLine, fields); + + + var parameters = new List(); + + parameters.Add($@"{Settings.Instance.ClientInterfaceName} client"); + + if (Settings.Instance.IncludeHttpOverride) + { + parameters.Add($@"{Constants.HttpOverride} httpOverride"); + } + + + string @params = string.Join(", ", parameters); + + + + + var initializers = new List(); + + initializers.Add($" {Constants.ClientInterfaceName} = client;"); + if (Settings.Instance.IncludeHttpOverride) + { + initializers.Add($" {Constants.HttpOverrideField} = httpOverride;"); + } + + string init = string.Join(Environment.NewLine, initializers); + var str = $@" {GetObsolete()} @@ -102,11 +138,11 @@ public interface I{ClientName} : I{Settings.Instance.ClientInterfaceName} {GetObsolete()} public class {ClientName} : I{ClientName} {{ - public readonly {Settings.Instance.ClientInterfaceName} Client; +{classFields} - public {ClientName}({Settings.Instance.ClientInterfaceName} client) + public {ClientName}({@params}) {{ - Client = client; +{init} }} {string.Join($"{Environment.NewLine}", Methods.Where(x => !x.IsNotEndpoint).Select(x => x.GetImplementationText()))} @@ -122,7 +158,7 @@ private string GetObsolete() { if (Options.Obsolete != null) { - return $@" [Obsolete(""{Options.Obsolete}"")]"; + return $@" [{Constants.Obsolete}(""{Options.Obsolete}"")]"; } else { diff --git a/src/AspNetCore.Client.Generator/Data/MethodDefinition.cs b/src/AspNetCore.Client.Generator/Data/MethodDefinition.cs index 1751a64..3f98e4f 100644 --- a/src/AspNetCore.Client.Generator/Data/MethodDefinition.cs +++ b/src/AspNetCore.Client.Generator/Data/MethodDefinition.cs @@ -1,4 +1,7 @@ -using AspNetCore.Client.Generator.Data.RouteConstraints; +using AspNetCore.Client.Core; +using AspNetCore.Client.Core.Authorization; +using AspNetCore.Client.Generator.Data.RouteConstraints; +using Flurl.Http; using Microsoft.CodeAnalysis.CSharp.Syntax; using System; using System.Collections.Generic; @@ -6,6 +9,7 @@ using System.Net; using System.Text; using System.Text.RegularExpressions; +using System.Threading; namespace AspNetCore.Client.Generator.Data { @@ -57,7 +61,7 @@ public class MethodDefinition //Route Attribute - var routeAttribute = attributes.SingleOrDefault(x => x.Name.ToFullString().StartsWith("Route")); + var routeAttribute = attributes.SingleOrDefault(x => x.Name.ToFullString().StartsWith(Constants.Route)); if (routeAttribute != null)//Fetch route from RouteAttribute { Options.Route = routeAttribute.ArgumentList.Arguments.ToFullString().Replace("\"", "").Trim(); @@ -66,7 +70,7 @@ public class MethodDefinition //HTTP Attribute - var httpAttribute = attributes.SingleOrDefault(x => x.Name.ToFullString().StartsWith("Http")); + var httpAttribute = attributes.SingleOrDefault(x => x.Name.ToFullString().StartsWith(Constants.Http)); if (httpAttribute == null) { IsNotEndpoint = true; @@ -76,8 +80,8 @@ public class MethodDefinition Options.HttpType = (HttpAttributeType)Enum.Parse(typeof(HttpAttributeType), httpAttribute.Name .ToFullString() - .Replace("Http", "") - .Replace("Attribute", "")); + .Replace(Constants.Http, "") + .Replace(Constants.Attribute, "")); if (Options.Route == null && httpAttribute.ArgumentList != null)//If Route was never fetched from RouteAttribute or if they used the Http(template) override { @@ -89,20 +93,20 @@ public class MethodDefinition } //Obsolete Attribute - var obsoleteAttribute = attributes.SingleOrDefault(x => x.Name.ToFullString().StartsWith("Obsolete")); + var obsoleteAttribute = attributes.SingleOrDefault(x => x.Name.ToFullString().StartsWith(Constants.Obsolete)); if (obsoleteAttribute != null) { Options.Obsolete = obsoleteAttribute.ArgumentList.Arguments.ToFullString().Replace("\"", "").Trim(); } //Authorize Attribute - Options.Authorize = attributes.SingleOrDefault(x => x.Name.ToFullString().StartsWith("Authorize")) != null; + Options.Authorize = attributes.SingleOrDefault(x => x.Name.ToFullString().StartsWith(Constants.Authorize)) != null; //AllowAnonymous Attribute - Options.AllowAnonymous = attributes.SingleOrDefault(x => x.Name.ToFullString().StartsWith("AllowAnonymous")) != null; + Options.AllowAnonymous = attributes.SingleOrDefault(x => x.Name.ToFullString().StartsWith(Constants.AllowAnonymous)) != null; //Response types - var responseTypes = attributes.Where(x => x.Name.ToFullString().StartsWith("ProducesResponseType")); + var responseTypes = attributes.Where(x => x.Name.ToFullString().StartsWith(Constants.ProducesResponseType)); Responses = responseTypes.Select(x => new ResponseTypeDefinition(this, x)).ToList(); foreach (var responseType in Settings.Instance.KnownStatusesAndResponseTypes) @@ -121,7 +125,7 @@ public class MethodDefinition .Select(x => new HeaderDefinition(x)) .ToList(); - Options.ActionResultReturn = MethodSyntax.ReturnType.ToFullString().Contains("IActionResult"); + Options.ActionResultReturn = MethodSyntax.ReturnType.ToFullString().Contains(Constants.IActionResult); } @@ -173,10 +177,6 @@ public string FullRoute { routeUnformatted = routeUnformatted.Replace(match.Value, $"{{{Helpers.GetRouteStringTransform(parameter.RouteName, parameter.Type)}}}"); } - if (parameter.RouteName == "code") - { - - } } var queryVariables = Parameters.Where(x => Helpers.IsRoutableType(x.Type) @@ -196,8 +196,8 @@ public string FullRoute - routeUnformatted = routeUnformatted.Replace("[controller]", "{controller}"); - routeUnformatted = routeUnformatted.Replace("[action]", "{action}"); + routeUnformatted = routeUnformatted.Replace($"[{Constants.ControllerRouteReserved}]", $"{{{Constants.ControllerRouteReserved}}}"); + routeUnformatted = routeUnformatted.Replace($"[{Constants.ActionRouteReserved}]", $"{{{Constants.ActionRouteReserved}}}"); return routeUnformatted; } } @@ -236,15 +236,17 @@ public IList GetAllHeaders() return allHeaders; } + static readonly IEnumerable CANCELLATION_TOKEN_PARAM = new List { $"{nameof(CancellationToken)} {Constants.CancellationTokenParameter} = default({nameof(CancellationToken)})" }; + public string GetInterfaceText() { string correctedName = Name.Replace("Async", ""); var allHeaders = GetAllHeaders(); - string syncParameters = string.Join($", {Environment.NewLine} ", new List> { Parameters.Select(x => x.MethodParameterOutput), allHeaders.Select(x => x.ParameterOutput()), Responses.Select(x => x.SyncMethodOutput), OptionParameters() }.SelectMany(x => x)); - string rawParameters = string.Join($", {Environment.NewLine} ", new List> { Parameters.Select(x => x.MethodParameterOutput), allHeaders.Select(x => x.ParameterOutput()), OptionParameters() }.SelectMany(x => x)); - string asyncParameters = string.Join($", {Environment.NewLine} ", new List> { Parameters.Select(x => x.MethodParameterOutput), allHeaders.Select(x => x.ParameterOutput()), Responses.Select(x => x.AsyncMethodOutput), OptionParameters() }.SelectMany(x => x)); + string syncParameters = string.Join($", {Environment.NewLine} ", new List> { Parameters.Select(x => x.MethodParameterOutput), allHeaders.Select(x => x.ParameterOutput()), Responses.Select(x => x.SyncMethodOutput), OptionParameters(), CANCELLATION_TOKEN_PARAM }.SelectMany(x => x)); + string rawParameters = string.Join($", {Environment.NewLine} ", new List> { Parameters.Select(x => x.MethodParameterOutput), allHeaders.Select(x => x.ParameterOutput()), OptionParameters(), CANCELLATION_TOKEN_PARAM }.SelectMany(x => x)); + string asyncParameters = string.Join($", {Environment.NewLine} ", new List> { Parameters.Select(x => x.MethodParameterOutput), allHeaders.Select(x => x.ParameterOutput()), Responses.Select(x => x.AsyncMethodOutput), OptionParameters(), CANCELLATION_TOKEN_PARAM }.SelectMany(x => x)); var returnType = MethodSyntax.ReturnType?.ToFullString(); @@ -259,7 +261,8 @@ public string GetInterfaceText() returnType = returnType.Trim(); - if(returnType == "void") + + if (returnType == "void" || returnType == "Task") { returnType = null; } @@ -291,9 +294,9 @@ public string GetImplementationText() var allHeaders = GetAllHeaders(); - string syncParameters = string.Join($", {Environment.NewLine} ", new List> { Parameters.Select(x => x.MethodParameterOutput), allHeaders.Select(x => x.ParameterOutput()), Responses.Select(x => x.SyncMethodOutput), OptionParameters() }.SelectMany(x => x)); - string rawParameters = string.Join($", {Environment.NewLine} ", new List> { Parameters.Select(x => x.MethodParameterOutput), allHeaders.Select(x => x.ParameterOutput()), OptionParameters() }.SelectMany(x => x)); - string asyncParameters = string.Join($", {Environment.NewLine} ", new List> { Parameters.Select(x => x.MethodParameterOutput), allHeaders.Select(x => x.ParameterOutput()), Responses.Select(x => x.AsyncMethodOutput), OptionParameters() }.SelectMany(x => x)); + string syncParameters = string.Join($", {Environment.NewLine} ", new List> { Parameters.Select(x => x.MethodParameterOutput), allHeaders.Select(x => x.ParameterOutput()), Responses.Select(x => x.SyncMethodOutput), OptionParameters(), CANCELLATION_TOKEN_PARAM }.SelectMany(x => x)); + string rawParameters = string.Join($", {Environment.NewLine} ", new List> { Parameters.Select(x => x.MethodParameterOutput), allHeaders.Select(x => x.ParameterOutput()), OptionParameters(), CANCELLATION_TOKEN_PARAM }.SelectMany(x => x)); + string asyncParameters = string.Join($", {Environment.NewLine} ", new List> { Parameters.Select(x => x.MethodParameterOutput), allHeaders.Select(x => x.ParameterOutput()), Responses.Select(x => x.AsyncMethodOutput), OptionParameters(), CANCELLATION_TOKEN_PARAM }.SelectMany(x => x)); var returnType = MethodSyntax.ReturnType?.ToFullString(); @@ -309,7 +312,7 @@ public string GetImplementationText() returnType = returnType.Trim(); - if (returnType == "void") + if (returnType == "void" || returnType == "Task") { returnType = null; } @@ -335,7 +338,7 @@ public string GetImplementationText() {{ {string.Join(Environment.NewLine, RouteConstraints.Select(x => x.GetText()).Where(x => !string.IsNullOrEmpty(x)))} {ResponseBuilder(false)} - return response; + return {Constants.ResponseVariable}; }} {GetObsolete()} @@ -352,7 +355,7 @@ public string GetImplementationText() {{ {string.Join(Environment.NewLine, RouteConstraints.Select(x => x.GetText()).Where(x => !string.IsNullOrEmpty(x)))} {ResponseBuilder(true)} - return response; + return {Constants.ResponseVariable}; }} "; } @@ -363,15 +366,15 @@ private string ResultTypeReturn(bool async, string returnType) string read = null; if (Helpers.KnownPrimitives.Contains(returnType, StringComparer.CurrentCultureIgnoreCase)) { - read = $@"{(async ? "await " : string.Empty)}response.Content.ReadAsNonJsonAsync<{returnType}>(){(async ? ".ConfigureAwait(false)" : ".ConfigureAwait(false).GetAwaiter().GetResult()")}"; + read = $@"{(async ? "await " : string.Empty)}{Constants.ResponseVariable}.Content.ReadAsNonJsonAsync<{returnType}>(){(async ? ".ConfigureAwait(false)" : ".ConfigureAwait(false).GetAwaiter().GetResult()")}"; } else { - read = $@"{Helpers.GetJsonDeserializer()}<{returnType}>({(async ? "await " : string.Empty)}response.Content.ReadAsStringAsync(){(async ? ".ConfigureAwait(false)" : ".ConfigureAwait(false).GetAwaiter().GetResult()")})"; + read = $@"{Helpers.GetJsonDeserializer()}<{returnType}>({(async ? "await " : string.Empty)}{Constants.ResponseVariable}.Content.ReadAsStringAsync(){(async ? ".ConfigureAwait(false)" : ".ConfigureAwait(false).GetAwaiter().GetResult()")})"; } return $@" - if(response.IsSuccessStatusCode) + if({Constants.ResponseVariable}.IsSuccessStatusCode) {{ return {read}; }} @@ -385,36 +388,66 @@ private string ResultTypeReturn(bool async, string returnType) private string ResponseBuilder(bool async) { - var additionalVars = $@" - var controller = ""{ParentClass.ControllerName}""; - var action = ""{Name}"";"; + var controllerVar = $@" var {Constants.ControllerRouteReserved} = ""{ParentClass.ControllerName}"";"; + var actionVar = $@" var {Constants.ActionRouteReserved} = ""{Name}"";"; + + var route = FullRoute; - bool contains = route.Contains("{controller}") || route.Contains("{action}"); + bool containsController = route.Contains($"{{{Constants.ControllerRouteReserved}}}"); + bool containsAction = route.Contains($"{{{Constants.ActionRouteReserved}}}"); var str = -$@"{(contains ? additionalVars : string.Empty)} - - string url = $@""{FullRoute}""; - HttpResponseMessage response = {(async ? "await " : "")}{GetHttpText()}{(async ? ".ConfigureAwait(false)" : ".ConfigureAwait(false).GetAwaiter().GetResult()")}; +$@" +{(containsController ? controllerVar : string.Empty)} +{(containsAction ? actionVar : string.Empty)} + + string {Constants.UrlVariable} = $@""{FullRoute}""; + HttpResponseMessage {Constants.ResponseVariable} = null; + {GetHttpOverridePre(async)} + if({Constants.ResponseVariable} == null) + {{ + {Constants.ResponseVariable} = {(async ? "await " : "")}{GetHttpText()}{(async ? ".ConfigureAwait(false)" : ".ConfigureAwait(false).GetAwaiter().GetResult()")}; + {GetHttpOverridePost(async)} + }} "; return str; } + private string GetHttpOverridePre(bool async) + { + if (!Settings.Instance.IncludeHttpOverride) + { + return null; + } + + return $@"{Constants.ResponseVariable} = {(async ? "await " : "")}{Constants.HttpOverrideField}.{Constants.HttpOverrideGetMethod}({Constants.UrlVariable}, {Constants.CancellationTokenParameter}){(async ? ".ConfigureAwait(false)" : ".ConfigureAwait(false).GetAwaiter().GetResult()")};"; + } + + private string GetHttpOverridePost(bool async) + { + if (!Settings.Instance.IncludeHttpOverride) + { + return null; + } + + return $@" + {(async ? "await " : "")}{Constants.HttpOverrideField}.{Constants.HttpOverrideOnNonOverridedResponse}({Constants.UrlVariable}, {Constants.ResponseVariable}, {Constants.CancellationTokenParameter}){(async ? ".ConfigureAwait(false)" : ".ConfigureAwait(false).GetAwaiter().GetResult()")};"; + } private string GetHttpText() { - string str = $@"Client.ClientWrapper - .Request(url)"; + string str = $@"{Constants.ClientInterfaceName}.{Constants.FlurlClientVariable} + .{nameof(FlurlClient.Request)}({Constants.UrlVariable})"; const string tabs = "\t\t\t\t"; if (Options.Authorize) { - str = $"{str}{Environment.NewLine}{tabs}.WithAuth(auth)"; + str = $"{str}{Environment.NewLine}{tabs}.{nameof(GeneratorExtensions.WithAuth)}({Constants.AuthParameter})"; } @@ -439,20 +472,20 @@ private string GetHttpText() var bodyParameter = Parameters.SingleOrDefault(x => x.Options?.Body ?? false); - str = $"{str}{Environment.NewLine}{tabs}.AllowAnyHttpStatus()"; + str = $"{str}{Environment.NewLine}{tabs}.{nameof(SettingsExtensions.AllowAnyHttpStatus)}()"; //str = $@"{str}{Environment.NewLine}{tabs}.WithTimeout({Settings.Instance.ServiceName}HttpClient.Timeout)"; switch (Options.HttpType) { case HttpAttributeType.Delete: - return $"{str}{Environment.NewLine}{tabs}.DeleteAsync()"; + return $"{str}{Environment.NewLine}{tabs}.{nameof(GeneratedExtensions.DeleteAsync)}({Constants.CancellationTokenParameter})"; case HttpAttributeType.Get: - return $"{str}{Environment.NewLine}{tabs}.GetAsync()"; + return $"{str}{Environment.NewLine}{tabs}.{nameof(GeneratedExtensions.GetAsync)}({Constants.CancellationTokenParameter})"; case HttpAttributeType.Put: - return $"{str}{Environment.NewLine}{tabs}.PutJsonAsync({bodyParameter?.HttpCallOutput ?? "null"})"; + return $"{str}{Environment.NewLine}{tabs}.{nameof(GeneratedExtensions.PutJsonAsync)}({bodyParameter?.HttpCallOutput ?? "null"},{Constants.CancellationTokenParameter})"; case HttpAttributeType.Patch: - return $"{str}{Environment.NewLine}{tabs}.PatchJsonAsync({bodyParameter?.HttpCallOutput ?? "null"})"; + return $"{str}{Environment.NewLine}{tabs}.{nameof(GeneratedExtensions.PatchJsonAsync)}({bodyParameter?.HttpCallOutput ?? "null"},{Constants.CancellationTokenParameter})"; case HttpAttributeType.Post: - return $"{str}{Environment.NewLine}{tabs}.PostJsonAsync({bodyParameter?.HttpCallOutput ?? "null"})"; + return $"{str}{Environment.NewLine}{tabs}.{nameof(GeneratedExtensions.PutJsonAsync)}({bodyParameter?.HttpCallOutput ?? "null"},{Constants.CancellationTokenParameter})"; default: throw new Exception("Unexpected HTTPType"); } @@ -463,7 +496,7 @@ private string GetObsolete() { if (Options.Obsolete != null) { - return $@" [Obsolete(""{Options.Obsolete}"")]"; + return $@" [{Constants.Obsolete}(""{Options.Obsolete}"")]"; } else { @@ -478,7 +511,7 @@ public IEnumerable OptionParameters() if (Options.Authorize || ParentClass.Options.Authorize) { - optionParameters.Add($"SecurityHeader auth = null"); + optionParameters.Add($"{nameof(SecurityHeader)} {Constants.AuthParameter} = null"); } diff --git a/src/AspNetCore.Client.Generator/Settings.cs b/src/AspNetCore.Client.Generator/Settings.cs index cb34133..011cce9 100644 --- a/src/AspNetCore.Client.Generator/Settings.cs +++ b/src/AspNetCore.Client.Generator/Settings.cs @@ -16,6 +16,7 @@ internal class Settings public bool ValueTask { get; set; } = true; public string Namespace { get; set; } = "MyService.Clients"; public bool BlazorClients { get; set; } = false; + public bool IncludeHttpOverride { get; set; } = false; public string[] AllowedNamespaces { get; set; } = new string[] { "System*", diff --git a/test/AspNetCore.Client.Test.Generator/Program.cs b/test/AspNetCore.Client.Test.Generator/Program.cs index 4fc3b04..8a2ced3 100644 --- a/test/AspNetCore.Client.Test.Generator/Program.cs +++ b/test/AspNetCore.Client.Test.Generator/Program.cs @@ -3,57 +3,56 @@ using Moq; using System; using System.Reflection; +using System.IO; +using System.Linq; namespace AspNetCore.Client.Test.Generator { public static class Program { - const string TESTWEBAPPCLIENT_PATH = "../../../../TestWebApp.Clients"; - const string TESTBLAZOR_PATH = "../../../../TestBlazorApp.Clients"; + const string WEBAPP = "TestWebApp.Clients"; + const string BLAZOR = "TestBlazorApp.Clients"; + const string FAILURE_DIR = "AspNetCore.Client"; static void Main() { - if (!(GenerateAspNetCore() && GenerateBlazor())) + var webApp = GoUpUntilDirectory(WEBAPP, FAILURE_DIR); + var blazor = GoUpUntilDirectory(BLAZOR, FAILURE_DIR); + + if (!(Generate(webApp) && Generate(blazor))) { Console.ReadKey(); } } - - private static bool GenerateAspNetCore() + private static string GoUpUntilDirectory(string targetDirectoryName, string failDirectory) { - var previousWorkDir = Environment.CurrentDirectory; - var task = new GeneratorTask(); + string currentPath = System.Environment.CurrentDirectory; - task.ProjectPath = TESTWEBAPPCLIENT_PATH; - var mockedBuildEngine = new Mock(); - mockedBuildEngine.Setup(x => x.LogErrorEvent(It.IsAny())).Callback((BuildErrorEventArgs args) => - { - Console.Error.WriteLine(args.Message); - throw new Exception(args.Message); - }); - mockedBuildEngine.Setup(x => x.LogMessageEvent(It.IsAny())).Callback((BuildMessageEventArgs args) => - { - Console.WriteLine(args.Message); - }); - mockedBuildEngine.Setup(x => x.LogWarningEvent(It.IsAny())).Callback((BuildWarningEventArgs args) => + while (Path.GetFileName(currentPath) != failDirectory) { - Console.WriteLine(args.Message); - }); - - task.BuildEngine = mockedBuildEngine.Object; + var childDirectories = Directory.GetDirectories(currentPath).ToList(); + var dirs = childDirectories.Select(Path.GetFileName).ToList(); + if (!dirs.Contains(targetDirectoryName)) + { + currentPath = Path.GetFullPath("..", currentPath); + } + else + { + return childDirectories.SingleOrDefault(x => Path.GetFileName(x) == targetDirectoryName); + } + } - var success = task.Execute(); - Environment.CurrentDirectory = previousWorkDir; - return success; + throw new DirectoryNotFoundException($"Directory {targetDirectoryName} was not found."); } - private static bool GenerateBlazor() + + private static bool Generate(string path) { var previousWorkDir = Environment.CurrentDirectory; var task = new GeneratorTask(); - task.ProjectPath = TESTBLAZOR_PATH; + task.ProjectPath = path; var mockedBuildEngine = new Mock(); mockedBuildEngine.Setup(x => x.LogErrorEvent(It.IsAny())).Callback((BuildErrorEventArgs args) => { diff --git a/test/TestBlazorApp.Clients/ClientGeneratorSettings.json b/test/TestBlazorApp.Clients/ClientGeneratorSettings.json index 55610b6..4e5c20f 100644 --- a/test/TestBlazorApp.Clients/ClientGeneratorSettings.json +++ b/test/TestBlazorApp.Clients/ClientGeneratorSettings.json @@ -5,6 +5,7 @@ "ValueTask": true, "Namespace": "TestBlazorApp.Clients", "BlazorClients": true, + "IncludeHttpOverride": false, "AllowedNamespaces": [ "System*", "TestBlazorApp.Shared*" diff --git a/test/TestBlazorApp.Clients/Clients.cs b/test/TestBlazorApp.Clients/Clients.cs index a55a333..e508860 100644 --- a/test/TestBlazorApp.Clients/Clients.cs +++ b/test/TestBlazorApp.Clients/Clients.cs @@ -20,6 +20,7 @@ using AspNetCore.Client.Core.Authorization; using AspNetCore.Client.Core.Exceptions; using Microsoft.Extensions.DependencyInjection; +using System.Threading; using Microsoft.AspNetCore.Blazor; namespace TestBlazorApp.Clients @@ -53,6 +54,7 @@ public static IServiceCollection InstallClients(this IServiceCollection services + public class TestBlazorAppClient { public readonly FlurlClient ClientWrapper; @@ -72,18 +74,20 @@ public interface ISampleDataClient : ITestBlazorAppClient IEnumerable WeatherForecasts(Action BadRequestCallback = null, Action InternalServerErrorCallback = null, - Action ResponseCallback = null); + Action ResponseCallback = null, + CancellationToken cancellationToken = default(CancellationToken)); - HttpResponseMessage WeatherForecastsRaw(); + HttpResponseMessage WeatherForecastsRaw(CancellationToken cancellationToken = default(CancellationToken)); ValueTask> WeatherForecastsAsync(Action BadRequestCallback = null, Action InternalServerErrorCallback = null, - Action ResponseCallback = null); + Action ResponseCallback = null, + CancellationToken cancellationToken = default(CancellationToken)); - ValueTask WeatherForecastsRawAsync(); + ValueTask WeatherForecastsRawAsync(CancellationToken cancellationToken = default(CancellationToken)); } @@ -100,7 +104,8 @@ public SampleDataClient(TestBlazorAppClient client) public IEnumerable WeatherForecasts(Action BadRequestCallback = null, Action InternalServerErrorCallback = null, - Action ResponseCallback = null) + Action ResponseCallback = null, + CancellationToken cancellationToken = default(CancellationToken)) { @@ -108,11 +113,17 @@ public SampleDataClient(TestBlazorAppClient client) var action = "WeatherForecasts"; string url = $@"api/{controller}/{action}"; - HttpResponseMessage response = Client.ClientWrapper + HttpResponseMessage response = null; + + if(response == null) + { + response = Client.ClientWrapper .Request(url) .WithHeader("Accept", "application/json") .AllowAnyHttpStatus() - .GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + .GetAsync(cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + + } if(BadRequestCallback != null && BadRequestCallback.Method.IsDefined(typeof(AsyncStateMachineAttribute), true)) { @@ -148,7 +159,7 @@ public SampleDataClient(TestBlazorAppClient client) } - public HttpResponseMessage WeatherForecastsRaw() + public HttpResponseMessage WeatherForecastsRaw(CancellationToken cancellationToken = default(CancellationToken)) { @@ -156,11 +167,17 @@ public HttpResponseMessage WeatherForecastsRaw() var action = "WeatherForecasts"; string url = $@"api/{controller}/{action}"; - HttpResponseMessage response = Client.ClientWrapper + HttpResponseMessage response = null; + + if(response == null) + { + response = Client.ClientWrapper .Request(url) .WithHeader("Accept", "application/json") .AllowAnyHttpStatus() - .GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + .GetAsync(cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + + } return response; } @@ -168,7 +185,8 @@ public HttpResponseMessage WeatherForecastsRaw() public async ValueTask> WeatherForecastsAsync(Action BadRequestCallback = null, Action InternalServerErrorCallback = null, - Action ResponseCallback = null) + Action ResponseCallback = null, + CancellationToken cancellationToken = default(CancellationToken)) { @@ -176,11 +194,17 @@ public HttpResponseMessage WeatherForecastsRaw() var action = "WeatherForecasts"; string url = $@"api/{controller}/{action}"; - HttpResponseMessage response = await Client.ClientWrapper + HttpResponseMessage response = null; + + if(response == null) + { + response = await Client.ClientWrapper .Request(url) .WithHeader("Accept", "application/json") .AllowAnyHttpStatus() - .GetAsync().ConfigureAwait(false); + .GetAsync(cancellationToken).ConfigureAwait(false); + + } if(BadRequestCallback != null && BadRequestCallback.Method.IsDefined(typeof(AsyncStateMachineAttribute), true)) { @@ -216,7 +240,7 @@ public HttpResponseMessage WeatherForecastsRaw() } - public async ValueTask WeatherForecastsRawAsync() + public async ValueTask WeatherForecastsRawAsync(CancellationToken cancellationToken = default(CancellationToken)) { @@ -224,11 +248,17 @@ public async ValueTask WeatherForecastsRawAsync() var action = "WeatherForecasts"; string url = $@"api/{controller}/{action}"; - HttpResponseMessage response = await Client.ClientWrapper + HttpResponseMessage response = null; + + if(response == null) + { + response = await Client.ClientWrapper .Request(url) .WithHeader("Accept", "application/json") .AllowAnyHttpStatus() - .GetAsync().ConfigureAwait(false); + .GetAsync(cancellationToken).ConfigureAwait(false); + + } return response; } diff --git a/test/TestWebApp.Clients/ClientGeneratorSettings.json b/test/TestWebApp.Clients/ClientGeneratorSettings.json index 125fd07..639849a 100644 --- a/test/TestWebApp.Clients/ClientGeneratorSettings.json +++ b/test/TestWebApp.Clients/ClientGeneratorSettings.json @@ -5,6 +5,7 @@ "ValueTask": true, "Namespace": "TestWebApp.Clients", "BlazorClients": false, + "IncludeHttpOverride": true, "AllowedNamespaces": [ "System*", "MyNamespaceToInclude*" diff --git a/test/TestWebApp.Clients/Clients.cs b/test/TestWebApp.Clients/Clients.cs index c9d1a03..9882505 100644 --- a/test/TestWebApp.Clients/Clients.cs +++ b/test/TestWebApp.Clients/Clients.cs @@ -19,6 +19,7 @@ using AspNetCore.Client.Core.Authorization; using AspNetCore.Client.Core.Exceptions; using Microsoft.Extensions.DependencyInjection; +using System.Threading; using Newtonsoft.Json; namespace TestWebApp.Clients @@ -51,6 +52,13 @@ public static IServiceCollection InstallClients(this IServiceCollection services } + public interface IHttpOverride + { + ValueTask GetResponseAsync(String url, CancellationToken cancellationToken = default(CancellationToken)); + Task OnNonOverridedResponseAsync(String url, HttpResponseMessage response, CancellationToken cancellationToken = default(CancellationToken)); + } + + public class TestWebAppClient { @@ -71,58 +79,68 @@ public interface IValuesClient : ITestWebAppClient IEnumerable Get(Action BadRequestCallback = null, Action InternalServerErrorCallback = null, - Action ResponseCallback = null); + Action ResponseCallback = null, + CancellationToken cancellationToken = default(CancellationToken)); - HttpResponseMessage GetRaw(); + HttpResponseMessage GetRaw(CancellationToken cancellationToken = default(CancellationToken)); ValueTask> GetAsync(Action BadRequestCallback = null, Action InternalServerErrorCallback = null, - Action ResponseCallback = null); + Action ResponseCallback = null, + CancellationToken cancellationToken = default(CancellationToken)); - ValueTask GetRawAsync(); + ValueTask GetRawAsync(CancellationToken cancellationToken = default(CancellationToken)); string Get(int id, Action BadRequestCallback = null, Action InternalServerErrorCallback = null, - Action ResponseCallback = null); + Action ResponseCallback = null, + CancellationToken cancellationToken = default(CancellationToken)); - HttpResponseMessage GetRaw(int id); + HttpResponseMessage GetRaw(int id, + CancellationToken cancellationToken = default(CancellationToken)); ValueTask GetAsync(int id, Action BadRequestCallback = null, Action InternalServerErrorCallback = null, - Action ResponseCallback = null); + Action ResponseCallback = null, + CancellationToken cancellationToken = default(CancellationToken)); - ValueTask GetRawAsync(int id); + ValueTask GetRawAsync(int id, + CancellationToken cancellationToken = default(CancellationToken)); void Post(string value, int UserId, Action BadRequestCallback = null, Action InternalServerErrorCallback = null, - Action ResponseCallback = null); + Action ResponseCallback = null, + CancellationToken cancellationToken = default(CancellationToken)); HttpResponseMessage PostRaw(string value, - int UserId); + int UserId, + CancellationToken cancellationToken = default(CancellationToken)); Task PostAsync(string value, int UserId, Action BadRequestCallback = null, Action InternalServerErrorCallback = null, - Action ResponseCallback = null); + Action ResponseCallback = null, + CancellationToken cancellationToken = default(CancellationToken)); ValueTask PostRawAsync(string value, - int UserId); + int UserId, + CancellationToken cancellationToken = default(CancellationToken)); void Put(int id, @@ -130,12 +148,14 @@ public interface IValuesClient : ITestWebAppClient int UserId, Action BadRequestCallback = null, Action InternalServerErrorCallback = null, - Action ResponseCallback = null); + Action ResponseCallback = null, + CancellationToken cancellationToken = default(CancellationToken)); HttpResponseMessage PutRaw(int id, string value, - int UserId); + int UserId, + CancellationToken cancellationToken = default(CancellationToken)); Task PutAsync(int id, @@ -143,34 +163,62 @@ public interface IValuesClient : ITestWebAppClient int UserId, Action BadRequestCallback = null, Action InternalServerErrorCallback = null, - Action ResponseCallback = null); + Action ResponseCallback = null, + CancellationToken cancellationToken = default(CancellationToken)); ValueTask PutRawAsync(int id, string value, - int UserId); + int UserId, + CancellationToken cancellationToken = default(CancellationToken)); void Delete(int id, int UserId, Action BadRequestCallback = null, Action InternalServerErrorCallback = null, - Action ResponseCallback = null); + Action ResponseCallback = null, + SecurityHeader auth = null, + CancellationToken cancellationToken = default(CancellationToken)); HttpResponseMessage DeleteRaw(int id, - int UserId); + int UserId, + SecurityHeader auth = null, + CancellationToken cancellationToken = default(CancellationToken)); Task DeleteAsync(int id, int UserId, Action BadRequestCallback = null, Action InternalServerErrorCallback = null, - Action ResponseCallback = null); + Action ResponseCallback = null, + SecurityHeader auth = null, + CancellationToken cancellationToken = default(CancellationToken)); ValueTask DeleteRawAsync(int id, - int UserId); + int UserId, + SecurityHeader auth = null, + CancellationToken cancellationToken = default(CancellationToken)); + + + void CancellationTestEndpoint(Action BadRequestCallback = null, + Action InternalServerErrorCallback = null, + Action ResponseCallback = null, + CancellationToken cancellationToken = default(CancellationToken)); + + + HttpResponseMessage CancellationTestEndpointRaw(CancellationToken cancellationToken = default(CancellationToken)); + + + Task CancellationTestEndpointAsync(Action BadRequestCallback = null, + Action InternalServerErrorCallback = null, + Action ResponseCallback = null, + CancellationToken cancellationToken = default(CancellationToken)); + + + ValueTask CancellationTestEndpointRawAsync(CancellationToken cancellationToken = default(CancellationToken)); } @@ -178,28 +226,38 @@ public interface IValuesClient : ITestWebAppClient public class ValuesClient : IValuesClient { public readonly TestWebAppClient Client; + public readonly IHttpOverride HttpOverride; - public ValuesClient(TestWebAppClient client) + public ValuesClient(TestWebAppClient client, IHttpOverride httpOverride) { Client = client; + HttpOverride = httpOverride; } public IEnumerable Get(Action BadRequestCallback = null, Action InternalServerErrorCallback = null, - Action ResponseCallback = null) + Action ResponseCallback = null, + CancellationToken cancellationToken = default(CancellationToken)) { var controller = "Values"; - var action = "Get"; + string url = $@"api/{controller}"; - HttpResponseMessage response = Client.ClientWrapper + HttpResponseMessage response = null; + response = HttpOverride.GetResponseAsync(url, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + if(response == null) + { + response = Client.ClientWrapper .Request(url) .WithHeader("Accept", "application/json") .AllowAnyHttpStatus() - .GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + .GetAsync(cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + + HttpOverride.OnNonOverridedResponseAsync(url, response, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + } if(BadRequestCallback != null && BadRequestCallback.Method.IsDefined(typeof(AsyncStateMachineAttribute), true)) { @@ -235,19 +293,26 @@ public ValuesClient(TestWebAppClient client) } - public HttpResponseMessage GetRaw() + public HttpResponseMessage GetRaw(CancellationToken cancellationToken = default(CancellationToken)) { var controller = "Values"; - var action = "Get"; + string url = $@"api/{controller}"; - HttpResponseMessage response = Client.ClientWrapper + HttpResponseMessage response = null; + response = HttpOverride.GetResponseAsync(url, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + if(response == null) + { + response = Client.ClientWrapper .Request(url) .WithHeader("Accept", "application/json") .AllowAnyHttpStatus() - .GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + .GetAsync(cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + + HttpOverride.OnNonOverridedResponseAsync(url, response, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + } return response; } @@ -255,19 +320,27 @@ public HttpResponseMessage GetRaw() public async ValueTask> GetAsync(Action BadRequestCallback = null, Action InternalServerErrorCallback = null, - Action ResponseCallback = null) + Action ResponseCallback = null, + CancellationToken cancellationToken = default(CancellationToken)) { var controller = "Values"; - var action = "Get"; + string url = $@"api/{controller}"; - HttpResponseMessage response = await Client.ClientWrapper + HttpResponseMessage response = null; + response = await HttpOverride.GetResponseAsync(url, cancellationToken).ConfigureAwait(false); + if(response == null) + { + response = await Client.ClientWrapper .Request(url) .WithHeader("Accept", "application/json") .AllowAnyHttpStatus() - .GetAsync().ConfigureAwait(false); + .GetAsync(cancellationToken).ConfigureAwait(false); + + await HttpOverride.OnNonOverridedResponseAsync(url, response, cancellationToken).ConfigureAwait(false); + } if(BadRequestCallback != null && BadRequestCallback.Method.IsDefined(typeof(AsyncStateMachineAttribute), true)) { @@ -303,19 +376,26 @@ public HttpResponseMessage GetRaw() } - public async ValueTask GetRawAsync() + public async ValueTask GetRawAsync(CancellationToken cancellationToken = default(CancellationToken)) { var controller = "Values"; - var action = "Get"; + string url = $@"api/{controller}"; - HttpResponseMessage response = await Client.ClientWrapper + HttpResponseMessage response = null; + response = await HttpOverride.GetResponseAsync(url, cancellationToken).ConfigureAwait(false); + if(response == null) + { + response = await Client.ClientWrapper .Request(url) .WithHeader("Accept", "application/json") .AllowAnyHttpStatus() - .GetAsync().ConfigureAwait(false); + .GetAsync(cancellationToken).ConfigureAwait(false); + + await HttpOverride.OnNonOverridedResponseAsync(url, response, cancellationToken).ConfigureAwait(false); + } return response; } @@ -324,19 +404,27 @@ public async ValueTask GetRawAsync() public string Get(int id, Action BadRequestCallback = null, Action InternalServerErrorCallback = null, - Action ResponseCallback = null) + Action ResponseCallback = null, + CancellationToken cancellationToken = default(CancellationToken)) { var controller = "Values"; - var action = "Get"; + string url = $@"api/{controller}/{id}"; - HttpResponseMessage response = Client.ClientWrapper + HttpResponseMessage response = null; + response = HttpOverride.GetResponseAsync(url, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + if(response == null) + { + response = Client.ClientWrapper .Request(url) .WithHeader("Accept", "application/json") .AllowAnyHttpStatus() - .GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + .GetAsync(cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + + HttpOverride.OnNonOverridedResponseAsync(url, response, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + } if(BadRequestCallback != null && BadRequestCallback.Method.IsDefined(typeof(AsyncStateMachineAttribute), true)) { @@ -372,19 +460,27 @@ public async ValueTask GetRawAsync() } - public HttpResponseMessage GetRaw(int id) + public HttpResponseMessage GetRaw(int id, + CancellationToken cancellationToken = default(CancellationToken)) { var controller = "Values"; - var action = "Get"; + string url = $@"api/{controller}/{id}"; - HttpResponseMessage response = Client.ClientWrapper + HttpResponseMessage response = null; + response = HttpOverride.GetResponseAsync(url, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + if(response == null) + { + response = Client.ClientWrapper .Request(url) .WithHeader("Accept", "application/json") .AllowAnyHttpStatus() - .GetAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + .GetAsync(cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + + HttpOverride.OnNonOverridedResponseAsync(url, response, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + } return response; } @@ -393,19 +489,27 @@ public HttpResponseMessage GetRaw(int id) public async ValueTask GetAsync(int id, Action BadRequestCallback = null, Action InternalServerErrorCallback = null, - Action ResponseCallback = null) + Action ResponseCallback = null, + CancellationToken cancellationToken = default(CancellationToken)) { var controller = "Values"; - var action = "Get"; + string url = $@"api/{controller}/{id}"; - HttpResponseMessage response = await Client.ClientWrapper + HttpResponseMessage response = null; + response = await HttpOverride.GetResponseAsync(url, cancellationToken).ConfigureAwait(false); + if(response == null) + { + response = await Client.ClientWrapper .Request(url) .WithHeader("Accept", "application/json") .AllowAnyHttpStatus() - .GetAsync().ConfigureAwait(false); + .GetAsync(cancellationToken).ConfigureAwait(false); + + await HttpOverride.OnNonOverridedResponseAsync(url, response, cancellationToken).ConfigureAwait(false); + } if(BadRequestCallback != null && BadRequestCallback.Method.IsDefined(typeof(AsyncStateMachineAttribute), true)) { @@ -441,19 +545,27 @@ public HttpResponseMessage GetRaw(int id) } - public async ValueTask GetRawAsync(int id) + public async ValueTask GetRawAsync(int id, + CancellationToken cancellationToken = default(CancellationToken)) { var controller = "Values"; - var action = "Get"; + string url = $@"api/{controller}/{id}"; - HttpResponseMessage response = await Client.ClientWrapper + HttpResponseMessage response = null; + response = await HttpOverride.GetResponseAsync(url, cancellationToken).ConfigureAwait(false); + if(response == null) + { + response = await Client.ClientWrapper .Request(url) .WithHeader("Accept", "application/json") .AllowAnyHttpStatus() - .GetAsync().ConfigureAwait(false); + .GetAsync(cancellationToken).ConfigureAwait(false); + + await HttpOverride.OnNonOverridedResponseAsync(url, response, cancellationToken).ConfigureAwait(false); + } return response; } @@ -463,20 +575,28 @@ public async ValueTask GetRawAsync(int id) int UserId, Action BadRequestCallback = null, Action InternalServerErrorCallback = null, - Action ResponseCallback = null) + Action ResponseCallback = null, + CancellationToken cancellationToken = default(CancellationToken)) { var controller = "Values"; - var action = "Post"; + string url = $@"api/{controller}?value={value}"; - HttpResponseMessage response = Client.ClientWrapper + HttpResponseMessage response = null; + response = HttpOverride.GetResponseAsync(url, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + if(response == null) + { + response = Client.ClientWrapper .Request(url) .WithHeader("Accept", "application/json") .WithHeader("UserId", UserId) .AllowAnyHttpStatus() - .PostJsonAsync(value).ConfigureAwait(false).GetAwaiter().GetResult(); + .PutJsonAsync(value,cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + + HttpOverride.OnNonOverridedResponseAsync(url, response, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + } if(BadRequestCallback != null && BadRequestCallback.Method.IsDefined(typeof(AsyncStateMachineAttribute), true)) { @@ -504,20 +624,28 @@ public async ValueTask GetRawAsync(int id) public HttpResponseMessage PostRaw(string value, - int UserId) + int UserId, + CancellationToken cancellationToken = default(CancellationToken)) { var controller = "Values"; - var action = "Post"; + string url = $@"api/{controller}?value={value}"; - HttpResponseMessage response = Client.ClientWrapper + HttpResponseMessage response = null; + response = HttpOverride.GetResponseAsync(url, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + if(response == null) + { + response = Client.ClientWrapper .Request(url) .WithHeader("Accept", "application/json") .WithHeader("UserId", UserId) .AllowAnyHttpStatus() - .PostJsonAsync(value).ConfigureAwait(false).GetAwaiter().GetResult(); + .PutJsonAsync(value,cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + + HttpOverride.OnNonOverridedResponseAsync(url, response, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + } return response; } @@ -527,20 +655,28 @@ public async ValueTask GetRawAsync(int id) int UserId, Action BadRequestCallback = null, Action InternalServerErrorCallback = null, - Action ResponseCallback = null) + Action ResponseCallback = null, + CancellationToken cancellationToken = default(CancellationToken)) { var controller = "Values"; - var action = "Post"; + string url = $@"api/{controller}?value={value}"; - HttpResponseMessage response = await Client.ClientWrapper + HttpResponseMessage response = null; + response = await HttpOverride.GetResponseAsync(url, cancellationToken).ConfigureAwait(false); + if(response == null) + { + response = await Client.ClientWrapper .Request(url) .WithHeader("Accept", "application/json") .WithHeader("UserId", UserId) .AllowAnyHttpStatus() - .PostJsonAsync(value).ConfigureAwait(false); + .PutJsonAsync(value,cancellationToken).ConfigureAwait(false); + + await HttpOverride.OnNonOverridedResponseAsync(url, response, cancellationToken).ConfigureAwait(false); + } if(BadRequestCallback != null && BadRequestCallback.Method.IsDefined(typeof(AsyncStateMachineAttribute), true)) { @@ -568,20 +704,28 @@ public async ValueTask GetRawAsync(int id) public async ValueTask PostRawAsync(string value, - int UserId) + int UserId, + CancellationToken cancellationToken = default(CancellationToken)) { var controller = "Values"; - var action = "Post"; + string url = $@"api/{controller}?value={value}"; - HttpResponseMessage response = await Client.ClientWrapper + HttpResponseMessage response = null; + response = await HttpOverride.GetResponseAsync(url, cancellationToken).ConfigureAwait(false); + if(response == null) + { + response = await Client.ClientWrapper .Request(url) .WithHeader("Accept", "application/json") .WithHeader("UserId", UserId) .AllowAnyHttpStatus() - .PostJsonAsync(value).ConfigureAwait(false); + .PutJsonAsync(value,cancellationToken).ConfigureAwait(false); + + await HttpOverride.OnNonOverridedResponseAsync(url, response, cancellationToken).ConfigureAwait(false); + } return response; } @@ -592,20 +736,28 @@ public async ValueTask GetRawAsync(int id) int UserId, Action BadRequestCallback = null, Action InternalServerErrorCallback = null, - Action ResponseCallback = null) + Action ResponseCallback = null, + CancellationToken cancellationToken = default(CancellationToken)) { var controller = "Values"; - var action = "Put"; + string url = $@"api/{controller}/{id}?value={value}"; - HttpResponseMessage response = Client.ClientWrapper + HttpResponseMessage response = null; + response = HttpOverride.GetResponseAsync(url, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + if(response == null) + { + response = Client.ClientWrapper .Request(url) .WithHeader("Accept", "application/json") .WithHeader("UserId", UserId) .AllowAnyHttpStatus() - .PutJsonAsync(value).ConfigureAwait(false).GetAwaiter().GetResult(); + .PutJsonAsync(value,cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + + HttpOverride.OnNonOverridedResponseAsync(url, response, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + } if(BadRequestCallback != null && BadRequestCallback.Method.IsDefined(typeof(AsyncStateMachineAttribute), true)) { @@ -634,20 +786,28 @@ public async ValueTask GetRawAsync(int id) public HttpResponseMessage PutRaw(int id, string value, - int UserId) + int UserId, + CancellationToken cancellationToken = default(CancellationToken)) { var controller = "Values"; - var action = "Put"; + string url = $@"api/{controller}/{id}?value={value}"; - HttpResponseMessage response = Client.ClientWrapper + HttpResponseMessage response = null; + response = HttpOverride.GetResponseAsync(url, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + if(response == null) + { + response = Client.ClientWrapper .Request(url) .WithHeader("Accept", "application/json") .WithHeader("UserId", UserId) .AllowAnyHttpStatus() - .PutJsonAsync(value).ConfigureAwait(false).GetAwaiter().GetResult(); + .PutJsonAsync(value,cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + + HttpOverride.OnNonOverridedResponseAsync(url, response, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + } return response; } @@ -658,20 +818,28 @@ public async ValueTask GetRawAsync(int id) int UserId, Action BadRequestCallback = null, Action InternalServerErrorCallback = null, - Action ResponseCallback = null) + Action ResponseCallback = null, + CancellationToken cancellationToken = default(CancellationToken)) { var controller = "Values"; - var action = "Put"; + string url = $@"api/{controller}/{id}?value={value}"; - HttpResponseMessage response = await Client.ClientWrapper + HttpResponseMessage response = null; + response = await HttpOverride.GetResponseAsync(url, cancellationToken).ConfigureAwait(false); + if(response == null) + { + response = await Client.ClientWrapper .Request(url) .WithHeader("Accept", "application/json") .WithHeader("UserId", UserId) .AllowAnyHttpStatus() - .PutJsonAsync(value).ConfigureAwait(false); + .PutJsonAsync(value,cancellationToken).ConfigureAwait(false); + + await HttpOverride.OnNonOverridedResponseAsync(url, response, cancellationToken).ConfigureAwait(false); + } if(BadRequestCallback != null && BadRequestCallback.Method.IsDefined(typeof(AsyncStateMachineAttribute), true)) { @@ -700,20 +868,28 @@ public async ValueTask GetRawAsync(int id) public async ValueTask PutRawAsync(int id, string value, - int UserId) + int UserId, + CancellationToken cancellationToken = default(CancellationToken)) { var controller = "Values"; - var action = "Put"; + string url = $@"api/{controller}/{id}?value={value}"; - HttpResponseMessage response = await Client.ClientWrapper + HttpResponseMessage response = null; + response = await HttpOverride.GetResponseAsync(url, cancellationToken).ConfigureAwait(false); + if(response == null) + { + response = await Client.ClientWrapper .Request(url) .WithHeader("Accept", "application/json") .WithHeader("UserId", UserId) .AllowAnyHttpStatus() - .PutJsonAsync(value).ConfigureAwait(false); + .PutJsonAsync(value,cancellationToken).ConfigureAwait(false); + + await HttpOverride.OnNonOverridedResponseAsync(url, response, cancellationToken).ConfigureAwait(false); + } return response; } @@ -723,20 +899,30 @@ public async ValueTask GetRawAsync(int id) int UserId, Action BadRequestCallback = null, Action InternalServerErrorCallback = null, - Action ResponseCallback = null) + Action ResponseCallback = null, + SecurityHeader auth = null, + CancellationToken cancellationToken = default(CancellationToken)) { var controller = "Values"; - var action = "Delete"; + string url = $@"api/{controller}/{id}"; - HttpResponseMessage response = Client.ClientWrapper + HttpResponseMessage response = null; + response = HttpOverride.GetResponseAsync(url, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + if(response == null) + { + response = Client.ClientWrapper .Request(url) + .WithAuth(auth) .WithHeader("Accept", "application/json") .WithHeader("UserId", UserId) .AllowAnyHttpStatus() - .DeleteAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + .DeleteAsync(cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + + HttpOverride.OnNonOverridedResponseAsync(url, response, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + } if(BadRequestCallback != null && BadRequestCallback.Method.IsDefined(typeof(AsyncStateMachineAttribute), true)) { @@ -764,20 +950,30 @@ public async ValueTask GetRawAsync(int id) public HttpResponseMessage DeleteRaw(int id, - int UserId) + int UserId, + SecurityHeader auth = null, + CancellationToken cancellationToken = default(CancellationToken)) { var controller = "Values"; - var action = "Delete"; + string url = $@"api/{controller}/{id}"; - HttpResponseMessage response = Client.ClientWrapper + HttpResponseMessage response = null; + response = HttpOverride.GetResponseAsync(url, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + if(response == null) + { + response = Client.ClientWrapper .Request(url) + .WithAuth(auth) .WithHeader("Accept", "application/json") .WithHeader("UserId", UserId) .AllowAnyHttpStatus() - .DeleteAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + .DeleteAsync(cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + + HttpOverride.OnNonOverridedResponseAsync(url, response, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + } return response; } @@ -787,20 +983,30 @@ public async ValueTask GetRawAsync(int id) int UserId, Action BadRequestCallback = null, Action InternalServerErrorCallback = null, - Action ResponseCallback = null) + Action ResponseCallback = null, + SecurityHeader auth = null, + CancellationToken cancellationToken = default(CancellationToken)) { var controller = "Values"; - var action = "Delete"; + string url = $@"api/{controller}/{id}"; - HttpResponseMessage response = await Client.ClientWrapper + HttpResponseMessage response = null; + response = await HttpOverride.GetResponseAsync(url, cancellationToken).ConfigureAwait(false); + if(response == null) + { + response = await Client.ClientWrapper .Request(url) + .WithAuth(auth) .WithHeader("Accept", "application/json") .WithHeader("UserId", UserId) .AllowAnyHttpStatus() - .DeleteAsync().ConfigureAwait(false); + .DeleteAsync(cancellationToken).ConfigureAwait(false); + + await HttpOverride.OnNonOverridedResponseAsync(url, response, cancellationToken).ConfigureAwait(false); + } if(BadRequestCallback != null && BadRequestCallback.Method.IsDefined(typeof(AsyncStateMachineAttribute), true)) { @@ -828,20 +1034,178 @@ public async ValueTask GetRawAsync(int id) public async ValueTask DeleteRawAsync(int id, - int UserId) + int UserId, + SecurityHeader auth = null, + CancellationToken cancellationToken = default(CancellationToken)) { var controller = "Values"; - var action = "Delete"; + string url = $@"api/{controller}/{id}"; - HttpResponseMessage response = await Client.ClientWrapper + HttpResponseMessage response = null; + response = await HttpOverride.GetResponseAsync(url, cancellationToken).ConfigureAwait(false); + if(response == null) + { + response = await Client.ClientWrapper .Request(url) + .WithAuth(auth) .WithHeader("Accept", "application/json") .WithHeader("UserId", UserId) .AllowAnyHttpStatus() - .DeleteAsync().ConfigureAwait(false); + .DeleteAsync(cancellationToken).ConfigureAwait(false); + + await HttpOverride.OnNonOverridedResponseAsync(url, response, cancellationToken).ConfigureAwait(false); + } + + return response; + } + + + public void CancellationTestEndpoint(Action BadRequestCallback = null, + Action InternalServerErrorCallback = null, + Action ResponseCallback = null, + CancellationToken cancellationToken = default(CancellationToken)) + { + + + var controller = "Values"; + var action = "CancellationTestEndpoint"; + + string url = $@"api/{controller}/{action}"; + HttpResponseMessage response = null; + response = HttpOverride.GetResponseAsync(url, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + if(response == null) + { + response = Client.ClientWrapper + .Request(url) + .WithHeader("Accept", "application/json") + .AllowAnyHttpStatus() + .GetAsync(cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + + HttpOverride.OnNonOverridedResponseAsync(url, response, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + } + + if(BadRequestCallback != null && BadRequestCallback.Method.IsDefined(typeof(AsyncStateMachineAttribute), true)) + { + throw new NotSupportedException("Async void action delegates for BadRequestCallback are not supported. As they will run out of the scope of this call."); + } + if(response.StatusCode == System.Net.HttpStatusCode.BadRequest) + { + BadRequestCallback?.Invoke(response.Content.ReadAsNonJsonAsync().ConfigureAwait(false).GetAwaiter().GetResult()); + } + if(InternalServerErrorCallback != null && InternalServerErrorCallback.Method.IsDefined(typeof(AsyncStateMachineAttribute), true)) + { + throw new NotSupportedException("Async void action delegates for InternalServerErrorCallback are not supported. As they will run out of the scope of this call."); + } + if(response.StatusCode == System.Net.HttpStatusCode.InternalServerError) + { + InternalServerErrorCallback?.Invoke(); + } + if(ResponseCallback != null && ResponseCallback.Method.IsDefined(typeof(AsyncStateMachineAttribute), true)) + { + throw new NotSupportedException("Async void action delegates for ResponseCallback are not supported. As they will run out of the scope of this call."); + } + ResponseCallback?.Invoke(response); + return; + } + + + public HttpResponseMessage CancellationTestEndpointRaw(CancellationToken cancellationToken = default(CancellationToken)) + { + + + var controller = "Values"; + var action = "CancellationTestEndpoint"; + + string url = $@"api/{controller}/{action}"; + HttpResponseMessage response = null; + response = HttpOverride.GetResponseAsync(url, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + if(response == null) + { + response = Client.ClientWrapper + .Request(url) + .WithHeader("Accept", "application/json") + .AllowAnyHttpStatus() + .GetAsync(cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + + HttpOverride.OnNonOverridedResponseAsync(url, response, cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + } + + return response; + } + + + public async Task CancellationTestEndpointAsync(Action BadRequestCallback = null, + Action InternalServerErrorCallback = null, + Action ResponseCallback = null, + CancellationToken cancellationToken = default(CancellationToken)) + { + + + var controller = "Values"; + var action = "CancellationTestEndpoint"; + + string url = $@"api/{controller}/{action}"; + HttpResponseMessage response = null; + response = await HttpOverride.GetResponseAsync(url, cancellationToken).ConfigureAwait(false); + if(response == null) + { + response = await Client.ClientWrapper + .Request(url) + .WithHeader("Accept", "application/json") + .AllowAnyHttpStatus() + .GetAsync(cancellationToken).ConfigureAwait(false); + + await HttpOverride.OnNonOverridedResponseAsync(url, response, cancellationToken).ConfigureAwait(false); + } + + if(BadRequestCallback != null && BadRequestCallback.Method.IsDefined(typeof(AsyncStateMachineAttribute), true)) + { + throw new NotSupportedException("Async void action delegates for BadRequestCallback are not supported. As they will run out of the scope of this call."); + } + if(response.StatusCode == System.Net.HttpStatusCode.BadRequest) + { + BadRequestCallback?.Invoke(await response.Content.ReadAsNonJsonAsync().ConfigureAwait(false)); + } + if(InternalServerErrorCallback != null && InternalServerErrorCallback.Method.IsDefined(typeof(AsyncStateMachineAttribute), true)) + { + throw new NotSupportedException("Async void action delegates for InternalServerErrorCallback are not supported. As they will run out of the scope of this call."); + } + if(response.StatusCode == System.Net.HttpStatusCode.InternalServerError) + { + InternalServerErrorCallback?.Invoke(); + } + if(ResponseCallback != null && ResponseCallback.Method.IsDefined(typeof(AsyncStateMachineAttribute), true)) + { + throw new NotSupportedException("Async void action delegates for ResponseCallback are not supported. As they will run out of the scope of this call."); + } + ResponseCallback?.Invoke(response); + return; + } + + + public async ValueTask CancellationTestEndpointRawAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + + + var controller = "Values"; + var action = "CancellationTestEndpoint"; + + string url = $@"api/{controller}/{action}"; + HttpResponseMessage response = null; + response = await HttpOverride.GetResponseAsync(url, cancellationToken).ConfigureAwait(false); + if(response == null) + { + response = await Client.ClientWrapper + .Request(url) + .WithHeader("Accept", "application/json") + .AllowAnyHttpStatus() + .GetAsync(cancellationToken).ConfigureAwait(false); + + await HttpOverride.OnNonOverridedResponseAsync(url, response, cancellationToken).ConfigureAwait(false); + } return response; } diff --git a/test/TestWebApp.Tests/ClientTest.cs b/test/TestWebApp.Tests/ClientTest.cs index fa6797a..18869c1 100644 --- a/test/TestWebApp.Tests/ClientTest.cs +++ b/test/TestWebApp.Tests/ClientTest.cs @@ -6,6 +6,9 @@ using TestWebApp.Clients; using System.Net.Http; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Flurl.Http; namespace TestWebApp.Tests { @@ -16,25 +19,57 @@ public IServiceProvider BuildServer() var server = new Microsoft.AspNetCore.TestHost.TestServer(new WebHostBuilder() .UseStartup()); - var client = server.CreateClient(); + Client = server.CreateClient(); var services = new ServiceCollection(); - services.AddSingleton(client); + services.AddSingleton(Client); services.InstallClients(); + services.AddScoped(); return services.BuildServiceProvider(); } + private HttpClient Client; + private readonly IServiceProvider Provider; - [Fact] - public void Test1() + public ClientTest() { - var provider = BuildServer(); + Provider = BuildServer(); + } - var valuesClient = provider.GetService(); + + [Fact] + public void SimpleTest() + { + var valuesClient = Provider.GetService(); var values = valuesClient.Get(); Assert.Equal(new List { "value1", "value2" }, values); + + + } + + /// + /// Microsoft.AspNetCore.TestHost.ClientHandler does not respect the CancellationToken and will always complete a request. Their unit test around it ClientCancellationAbortsRequest has a "hack" that cancels in TestServer when the token is canceled. + /// When the HttpClient has the default HttpMessageHandler, the SendAsync will cancel approriately, until they match this functionality, this test will be disabled + /// + /// + //[Fact] + public async Task CancelTestAsync() + { + var valuesClient = Provider.GetService(); + + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + var token = cancellationTokenSource.Token; + + var ex = await Assert.ThrowsAsync(async () => + { + var task = valuesClient.CancellationTestEndpointAsync(cancellationToken: token); + cancellationTokenSource.CancelAfter(1500); + await task.ConfigureAwait(false); + }); + + Assert.True(ex.InnerException is TaskCanceledException); } } } diff --git a/test/TestWebApp.Tests/FakeHttpOverride.cs b/test/TestWebApp.Tests/FakeHttpOverride.cs new file mode 100644 index 0000000..2038a1f --- /dev/null +++ b/test/TestWebApp.Tests/FakeHttpOverride.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using TestWebApp.Clients; + +namespace TestWebApp.Tests +{ + /// + /// Could implement redis caching, etc + /// + public class FakeHttpOverride : IHttpOverride + { + private IDictionary memCachedResponses = new ConcurrentDictionary(); + + public async ValueTask GetResponseAsync(string url, CancellationToken cancellationToken = default(CancellationToken)) + { + if (memCachedResponses.ContainsKey(url)) + { + return await Task.FromResult(memCachedResponses[url]); + } + return null; + } + + public async Task OnNonOverridedResponseAsync(string url, HttpResponseMessage response, CancellationToken cancellationToken = default(CancellationToken)) + { + if (memCachedResponses.ContainsKey(url)) + { + memCachedResponses[url] = response; + } + else + { + memCachedResponses.Add(url, response); + } + await Task.CompletedTask; + } + } +} diff --git a/test/TestWebApp.Tests/TestWebApp.Tests.csproj b/test/TestWebApp.Tests/TestWebApp.Tests.csproj index 3671daf..798e3dc 100644 --- a/test/TestWebApp.Tests/TestWebApp.Tests.csproj +++ b/test/TestWebApp.Tests/TestWebApp.Tests.csproj @@ -14,6 +14,7 @@ + diff --git a/test/TestWebApp/Controllers/ValuesController.cs b/test/TestWebApp/Controllers/ValuesController.cs index fa74a85..8834984 100644 --- a/test/TestWebApp/Controllers/ValuesController.cs +++ b/test/TestWebApp/Controllers/ValuesController.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace TestWebApp.Controllers @@ -38,8 +39,15 @@ public void Put(int id, [FromBody] string value) // DELETE api/values/5 [HttpDelete("{id}")] + [Authorize] public void Delete(int id) { } + + [HttpGet("[action]")] + public async Task CancellationTestEndpoint() + { + await Task.Delay(10000); + } } }