diff --git a/.editorconfig b/.editorconfig
index a23aedf..6cb7ab4 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,32 +1,41 @@
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+indent_style = tab
+indent_size = tab
+tab_width = 4
+
+[{*.yaml,*.yml}]
+indent_style = space
+indent_size = 2
+
[*.cs]
# symbols
-dotnet_naming_symbols.private_methods.applicable_kinds = method
+dotnet_naming_symbols.private_methods.applicable_kinds = method
dotnet_naming_symbols.private_methods.applicable_accessibilities = private
-dotnet_naming_symbols.private_fields.applicable_kinds = field
+dotnet_naming_symbols.private_fields.applicable_kinds = field
dotnet_naming_symbols.private_fields.applicable_accessibilities = private
# naming styles
dotnet_naming_style.camel_case_style.capitalization = camel_case
-dotnet_naming_style.underscore_camel_case.capitalization = camel_case
-dotnet_naming_style.underscore_camel_case.required_prefix = _
+dotnet_naming_style.underscore_camel_case.capitalization = camel_case
+dotnet_naming_style.underscore_camel_case.required_prefix = _
# naming rules
dotnet_naming_rule.private_methods_camel_case.symbols = private_methods
-dotnet_naming_rule.private_methods_camel_case.style = camel_case_style
+dotnet_naming_rule.private_methods_camel_case.style = camel_case_style
dotnet_naming_rule.private_methods_camel_case.severity = suggestion
dotnet_naming_rule.private_fields_underscore_camel_case.symbols = private_fields
-dotnet_naming_rule.private_fields_underscore_camel_case.style = underscore_camel_case
+dotnet_naming_rule.private_fields_underscore_camel_case.style = underscore_camel_case
dotnet_naming_rule.private_fields_underscore_camel_case.severity = suggestion
+# Sonar
-
-dotnet_diagnostic.IDE0003.severity = suggestion
-dotnet_diagnostic.CA1825.severity = none
-
-dotnet_diagnostic.S1186.severity = none # does not allow empty methods
-dotnet_diagnostic.S3011.severity = none # does not allow reflection of private members
-dotnet_diagnostic.S3267.severity = none # Loops should be simplified by LINQ
+# Bug, S3903:Types should be defined in named namespaces
+# Doesn't take into consideration file scoped namespaces
+dotnet_diagnostic.S3903.severity = none
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..f7c7529
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,8 @@
+# Auto detect text files and perform LF normalization
+* text=auto
+
+*.cs text diff=csharp
+*.cshtml text diff=html
+*.csx text diff=csharp
+*.sln text eol=crlf
+*.csproj text eol=crlf
diff --git a/.github/workflows/push-action.yml b/.github/workflows/push-action.yml
index d810733..d869268 100644
--- a/.github/workflows/push-action.yml
+++ b/.github/workflows/push-action.yml
@@ -15,7 +15,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
- dotnet-version: 5.0.x
+ dotnet-version: 6.0.x
- name: Checkout
uses: actions/checkout@v2
diff --git a/.github/workflows/release-action.yml b/.github/workflows/release-action.yml
index bf9b4b3..79b6e50 100644
--- a/.github/workflows/release-action.yml
+++ b/.github/workflows/release-action.yml
@@ -12,7 +12,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
- dotnet-version: 5.0.x
+ dotnet-version: 6.0.x
- name: Checkout
uses: actions/checkout@v2
@@ -33,10 +33,10 @@ jobs:
run: |
dotnet nuget push ./out/*.nupkg \
-s https://nuget.pkg.github.com/Byteology/index.json -k ${{ secrets.GITHUB_TOKEN }} \
- --skip-duplicate --no-symbols true
+ --skip-duplicate --no-symbols
- name: Push package - NuGet.org
run: |
dotnet nuget push ./out/*.nupkg \
-s https://api.nuget.org/v3/index.json -k ${{ secrets.NUGET_API_KEY }} \
- --skip-duplicate --no-symbols true
+ --skip-duplicate --no-symbols
diff --git a/.gitignore b/.gitignore
index f677870..3aafe5c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
bin
obj
-.vs
\ No newline at end of file
+.vs
+.idea
+**.DotSettings
+**.DotSettings.user
diff --git a/Byteology.TypedHttpClients.Tests/Byteology.TypedHttpClients.Tests.csproj b/Byteology.TypedHttpClients.Tests/Byteology.TypedHttpClients.Tests.csproj
index 0c28ee5..afa3a5e 100644
--- a/Byteology.TypedHttpClients.Tests/Byteology.TypedHttpClients.Tests.csproj
+++ b/Byteology.TypedHttpClients.Tests/Byteology.TypedHttpClients.Tests.csproj
@@ -1,30 +1,32 @@
-
-
-
- net5.0
-
- false
-
-
-
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
-
-
+
+
+
+ net6.0
+
+ false
+
+ enable
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
diff --git a/Byteology.TypedHttpClients.Tests/JsonHttpClientTests.cs b/Byteology.TypedHttpClients.Tests/JsonHttpClientTests.cs
index d7946bf..e3353f1 100644
--- a/Byteology.TypedHttpClients.Tests/JsonHttpClientTests.cs
+++ b/Byteology.TypedHttpClients.Tests/JsonHttpClientTests.cs
@@ -1,240 +1,242 @@
-using Byteology.TypedHttpClients.Tests.Mocks;
-using Byteology.TypedHttpClients.Tests.TestServices;
-using System;
-using System.Net;
-using System.Net.Http;
-using System.Threading.Tasks;
-using Xunit;
-
-namespace Byteology.TypedHttpClients.Tests
-{
- public class JsonHttpClientTests
- {
- [Fact]
- public void Success()
- {
- // Arrange
- using HttpClient httpClient = getHttpClient(HttpStatusCode.OK, null);
- ITestService service = new JsonHttpClient(httpClient).Endpoints;
-
- // Act
- Task result = service.NoResultActionAsync();
-
- // Assert
- Assert.True(result.IsCompletedSuccessfully);
- }
-
- [Fact]
- public void Success_Generic()
- {
- // Arrange
- TestServiceResult response = new ();
- using HttpClient httpClient = getHttpClient(HttpStatusCode.OK, new StringContent(response.ToString()));
- ITestService service = new JsonHttpClient(httpClient).Endpoints;
-
- // Act
- TestServiceResult result = service.ActionAsync().Result;
-
- // Assert
- Assert.Equal(response, result);
- }
-
- [Fact]
- public void Error()
- {
- // Arrange
- using HttpClient httpClient = getHttpClient(HttpStatusCode.Forbidden, null);
- ITestService service = new JsonHttpClient(httpClient).Endpoints;
-
- // Act
- Task result = service.NoResultActionAsync();
-
- // Assert
- Assert.ThrowsAny(() => result.Wait());
- }
-
- [Fact]
- public void Error_Generic()
- {
- // Arrange
- using HttpClient httpClient = getHttpClient(HttpStatusCode.Forbidden, null);
- ITestService service = new JsonHttpClient(httpClient).Endpoints;
-
- // Act
- Task result = service.ActionAsync();
-
- // Assert
- Assert.ThrowsAny(() => result.Wait());
- }
-
- [Fact]
- public void Uri_Simple()
- {
- // Arrange
- string expectedUrl = "https://example.com/simpleuri";
- void assertUri(HttpRequestMessage m) => Assert.Equal(expectedUrl, m.RequestUri?.ToString());
-
- using HttpClient httpClient = getHttpClient(HttpStatusCode.OK, null, assertUri);
- ITestService service = new JsonHttpClient(httpClient).Endpoints;
-
- // Act
- Task result = service.SimpleUriAsync();
- result.Wait();
-
- // Assert
- Assert.True(result.IsCompletedSuccessfully);
- }
-
- [Fact]
- public void Uri_Simple_NoDash()
- {
- // Arrange
- static void assertUri(HttpRequestMessage m) => Assert.Equal("https://example.com/simpleuri", m.RequestUri?.ToString());
-
- using HttpClient httpClient = getHttpClient(HttpStatusCode.OK, null, assertUri);
- ITestService service = new JsonHttpClient(httpClient).Endpoints;
-
- // Act
- Task result = service.SimpleUriNoDashAsync();
- result.Wait();
-
- // Assert
- Assert.True(result.IsCompletedSuccessfully);
- }
-
- [Theory]
- [InlineData("test")]
- [InlineData(5)]
- [InlineData(5.8f)]
- [InlineData(true)]
- [InlineData(null)]
- public void Uri_Param(object param)
- {
- // Arrange
- string expectedUrl = $"https://example.com/paramuri/{param}";
- void assertUri(HttpRequestMessage m) => Assert.Equal(expectedUrl, m.RequestUri?.ToString());
-
- using HttpClient httpClient = getHttpClient(HttpStatusCode.OK, null, assertUri);
- ITestService service = new JsonHttpClient(httpClient).Endpoints;
-
- // Act
- Task result = service.ParamUriAsync(param);
- result.Wait();
-
- // Assert
- Assert.True(result.IsCompletedSuccessfully);
- }
-
- [Fact]
- public void Uri_Query()
- {
- // Arrange
- string expectedUrl = "https://example.com/query?i=5&s=asdf&b=True&f=5.4&n=&a=1&a=2&a=3&a=";
- void assertUri(HttpRequestMessage m) => Assert.Equal(expectedUrl, m.RequestUri?.ToString());
-
- using HttpClient httpClient = getHttpClient(HttpStatusCode.OK, null, assertUri);
- ITestService service = new JsonHttpClient(httpClient).Endpoints;
-
- // Act
- Task result = service.QueryAsync(5, "asdf", true, 5.4f, null, new int?[] { 1, 2, 3, null });
- result.Wait();
-
- // Assert
- Assert.True(result.IsCompletedSuccessfully);
- }
-
- [Fact]
- public void Body()
- {
- // Arrange
- string expectedUrl = "https://example.com/actionbody";
- TestServiceResult body = new();
- void assertUri(HttpRequestMessage m)
- {
- Assert.Equal(expectedUrl, m.RequestUri?.ToString());
- Assert.Equal(body.ToString(), m.Content.ReadAsStringAsync().Result);
- }
-
- using HttpClient httpClient = getHttpClient(HttpStatusCode.OK, null, assertUri);
- ITestService service = new JsonHttpClient(httpClient).Endpoints;
-
- // Act
- Task result = service.BodyActionAsync(body);
- result.Wait();
-
- // Assert
- Assert.True(result.IsCompletedSuccessfully);
- }
-
- [Fact]
- public void Verb()
- {
- // Arrange
- static void assertUri(HttpRequestMessage m) => Assert.Equal("VERB", m.Method.Method);
-
- using HttpClient httpClient = getHttpClient(HttpStatusCode.OK, null, assertUri);
- ITestService service = new JsonHttpClient(httpClient).Endpoints;
-
- // Act
- Task result = service.VerbAsync();
- result.Wait();
-
- // Assert
- Assert.True(result.IsCompletedSuccessfully);
- }
-
- [Fact]
- public void Invalid_OutParam()
- {
- // Arrange
- using HttpClient httpClient = getHttpClient(HttpStatusCode.OK, null);
- ITestService service = new JsonHttpClient(httpClient).Endpoints;
-
- // Act & Assert
- Assert.ThrowsAny(() => service.OutParamAsync(out int p).Wait());
- }
-
- [Fact]
- public void Invalid_MultipleBody()
- {
- // Arrange
- using HttpClient httpClient = getHttpClient(HttpStatusCode.OK, null);
- ITestService service = new JsonHttpClient(httpClient).Endpoints;
-
- // Act & Assert
- Assert.ThrowsAny(() => service.MultipleBodyAsync(1, 2).Wait());
- }
-
- [Fact]
- public void Invalid_NotDecorated()
- {
- // Arrange
- using HttpClient httpClient = getHttpClient(HttpStatusCode.OK, null);
- ITestService service = new JsonHttpClient(httpClient).Endpoints;
-
- // Act & Assert
- Assert.ThrowsAny(() => service.NotDecoratedAsync().Wait());
- }
-
- [Fact]
- public void Invalid_NotAsync()
- {
- // Arrange
- using HttpClient httpClient = getHttpClient(HttpStatusCode.OK, null);
- ITestService service = new JsonHttpClient(httpClient).Endpoints;
-
- // Act & Assert
- Assert.ThrowsAny(() => service.NotAsync());
- }
-
- private static HttpClient getHttpClient(
- HttpStatusCode statusCode,
- HttpContent content,
- Action onBeforeSend = null)
- {
- HttpClient httpClient = MockHttpClientFactory.Create(statusCode, content, onBeforeSend);
- httpClient.BaseAddress = new Uri("https://example.com");
- return httpClient;
- }
- }
-}
+using Byteology.TypedHttpClients.Tests.Mocks;
+using Byteology.TypedHttpClients.Tests.TestServices;
+using System;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Byteology.TypedHttpClients.Tests
+{
+ public class JsonHttpClientTests
+ {
+ [Fact]
+ public void Success()
+ {
+ // Arrange
+ using HttpClient httpClient = getHttpClient(HttpStatusCode.OK, null);
+ ITestService service = new JsonHttpClient(httpClient).Endpoints;
+
+ // Act
+ Task result = service.NoResultActionAsync();
+
+ // Assert
+ Assert.True(result.IsCompletedSuccessfully);
+ }
+
+ [Fact]
+ public void Success_Generic()
+ {
+ // Arrange
+ TestServiceResult response = new();
+ using HttpClient httpClient = getHttpClient(HttpStatusCode.OK, new StringContent(response.ToString()));
+ ITestService service = new JsonHttpClient(httpClient).Endpoints;
+
+ // Act
+ TestServiceResult result = service.ActionAsync().Result;
+
+ // Assert
+ Assert.Equal(response, result);
+ }
+
+ [Fact]
+ public void Error()
+ {
+ // Arrange
+ using HttpClient httpClient = getHttpClient(HttpStatusCode.Forbidden, null);
+ ITestService service = new JsonHttpClient(httpClient).Endpoints;
+
+ // Act
+ Task result = service.NoResultActionAsync();
+
+ // Assert
+ Assert.ThrowsAny(() => result.Wait());
+ }
+
+ [Fact]
+ public void Error_Generic()
+ {
+ // Arrange
+ using HttpClient httpClient = getHttpClient(HttpStatusCode.Forbidden, null);
+ ITestService service = new JsonHttpClient(httpClient).Endpoints;
+
+ // Act
+ Task result = service.ActionAsync();
+
+ // Assert
+ Assert.ThrowsAny(() => result.Wait());
+ }
+
+ [Fact]
+ public void Uri_Simple()
+ {
+ // Arrange
+ string expectedUrl = "https://example.com/simpleUri";
+ void assertUri(HttpRequestMessage m) => Assert.Equal(expectedUrl, m.RequestUri?.ToString());
+
+ using HttpClient httpClient = getHttpClient(HttpStatusCode.OK, null, assertUri);
+ ITestService service = new JsonHttpClient(httpClient).Endpoints;
+
+ // Act
+ Task result = service.SimpleUriAsync();
+ result.Wait();
+
+ // Assert
+ Assert.True(result.IsCompletedSuccessfully);
+ }
+
+ [Fact]
+ public void Uri_Simple_NoDash()
+ {
+ // Arrange
+ static void assertUri(HttpRequestMessage m) =>
+ Assert.Equal("https://example.com/simpleUri", m.RequestUri?.ToString());
+
+ using HttpClient httpClient = getHttpClient(HttpStatusCode.OK, null, assertUri);
+ ITestService service = new JsonHttpClient(httpClient).Endpoints;
+
+ // Act
+ Task result = service.SimpleUriNoDashAsync();
+ result.Wait();
+
+ // Assert
+ Assert.True(result.IsCompletedSuccessfully);
+ }
+
+ [Theory]
+ [InlineData("test")]
+ [InlineData(5)]
+ [InlineData(5.8f)]
+ [InlineData(true)]
+ [InlineData(null)]
+ public void Uri_Param(object param)
+ {
+ // Arrange
+ string expectedUrl = $"https://example.com/paramUri/{param}";
+ void assertUri(HttpRequestMessage m) => Assert.Equal(expectedUrl, m.RequestUri?.ToString());
+
+ using HttpClient httpClient = getHttpClient(HttpStatusCode.OK, null, assertUri);
+ ITestService service = new JsonHttpClient(httpClient).Endpoints;
+
+ // Act
+ Task result = service.ParamUriAsync(param);
+ result.Wait();
+
+ // Assert
+ Assert.True(result.IsCompletedSuccessfully);
+ }
+
+ [Fact]
+ public void Uri_Query()
+ {
+ // Arrange
+ string expectedUrl = "https://example.com/query?i=5&s=a&b=True&f=5.4&n=&a=1&a=2&a=3&a=";
+ void assertUri(HttpRequestMessage m) => Assert.Equal(expectedUrl, m.RequestUri?.ToString());
+
+ using HttpClient httpClient = getHttpClient(HttpStatusCode.OK, null, assertUri);
+ ITestService service = new JsonHttpClient(httpClient).Endpoints;
+
+ // Act
+ Task result = service.QueryAsync(5, "a", true, 5.4f, null, new int?[] { 1, 2, 3, null });
+ result.Wait();
+
+ // Assert
+ Assert.True(result.IsCompletedSuccessfully);
+ }
+
+ [Fact]
+ public void Body()
+ {
+ // Arrange
+ string expectedUrl = "https://example.com/actionBody";
+ TestServiceResult body = new();
+
+ void assertUri(HttpRequestMessage m)
+ {
+ Assert.Equal(expectedUrl, m.RequestUri?.ToString());
+ Assert.Equal(body.ToString(), m.Content?.ReadAsStringAsync().Result);
+ }
+
+ using HttpClient httpClient = getHttpClient(HttpStatusCode.OK, null, assertUri);
+ ITestService service = new JsonHttpClient(httpClient).Endpoints;
+
+ // Act
+ Task result = service.BodyActionAsync(body);
+ result.Wait();
+
+ // Assert
+ Assert.True(result.IsCompletedSuccessfully);
+ }
+
+ [Fact]
+ public void Verb()
+ {
+ // Arrange
+ static void assertUri(HttpRequestMessage m) => Assert.Equal("VERB", m.Method.Method);
+
+ using HttpClient httpClient = getHttpClient(HttpStatusCode.OK, null, assertUri);
+ ITestService service = new JsonHttpClient(httpClient).Endpoints;
+
+ // Act
+ Task result = service.VerbAsync();
+ result.Wait();
+
+ // Assert
+ Assert.True(result.IsCompletedSuccessfully);
+ }
+
+ [Fact]
+ public void Invalid_OutParam()
+ {
+ // Arrange
+ using HttpClient httpClient = getHttpClient(HttpStatusCode.OK, null);
+ ITestService service = new JsonHttpClient(httpClient).Endpoints;
+
+ // Act & Assert
+ Assert.ThrowsAny(() => service.OutParamAsync(out int p).Wait());
+ }
+
+ [Fact]
+ public void Invalid_MultipleBody()
+ {
+ // Arrange
+ using HttpClient httpClient = getHttpClient(HttpStatusCode.OK, null);
+ ITestService service = new JsonHttpClient(httpClient).Endpoints;
+
+ // Act & Assert
+ Assert.ThrowsAny(() => service.MultipleBodyAsync(1, 2).Wait());
+ }
+
+ [Fact]
+ public void Invalid_NotDecorated()
+ {
+ // Arrange
+ using HttpClient httpClient = getHttpClient(HttpStatusCode.OK, null);
+ ITestService service = new JsonHttpClient(httpClient).Endpoints;
+
+ // Act & Assert
+ Assert.ThrowsAny(() => service.NotDecoratedAsync().Wait());
+ }
+
+ [Fact]
+ public void Invalid_NotAsync()
+ {
+ // Arrange
+ using HttpClient httpClient = getHttpClient(HttpStatusCode.OK, null);
+ ITestService service = new JsonHttpClient(httpClient).Endpoints;
+
+ // Act & Assert
+ Assert.ThrowsAny(() => service.NotAsync());
+ }
+
+ private static HttpClient getHttpClient(
+ HttpStatusCode statusCode,
+ HttpContent? content,
+ Action? onBeforeSend = null)
+ {
+ HttpClient httpClient = MockHttpClientFactory.Create(statusCode, content, onBeforeSend);
+ httpClient.BaseAddress = new Uri("https://example.com");
+ return httpClient;
+ }
+ }
+}
diff --git a/Byteology.TypedHttpClients.Tests/Mocks/MockHttpClientFactory.cs b/Byteology.TypedHttpClients.Tests/Mocks/MockHttpClientFactory.cs
index b53eea1..993a1e5 100644
--- a/Byteology.TypedHttpClients.Tests/Mocks/MockHttpClientFactory.cs
+++ b/Byteology.TypedHttpClients.Tests/Mocks/MockHttpClientFactory.cs
@@ -1,43 +1,45 @@
-using System;
-using System.Net;
-using System.Net.Http;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace Byteology.TypedHttpClients.Tests.Mocks
-{
- internal static class MockHttpClientFactory
- {
- public static HttpClient Create(HttpStatusCode statusCode, HttpContent content, Action onBeforeSend = null)
- {
- MessageHandler handler = new (statusCode, content, onBeforeSend);
- return new HttpClient(handler, true);
- }
-
- private class MessageHandler : HttpMessageHandler
- {
- private readonly HttpStatusCode _statusCode;
- private readonly HttpContent _content;
- private readonly Action _onBeforeSend;
-
- public MessageHandler(HttpStatusCode statusCode, HttpContent content, Action onBeforeSend = null)
- {
- _statusCode = statusCode;
- _content = content;
- _onBeforeSend = onBeforeSend;
- }
-
- protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
- {
- _onBeforeSend?.Invoke(request);
-
- return Task.FromResult(
- new HttpResponseMessage()
- {
- StatusCode = _statusCode,
- Content = _content
- });
- }
- }
- }
-}
+using System;
+using System.Net;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Byteology.TypedHttpClients.Tests.Mocks
+{
+ internal static class MockHttpClientFactory
+ {
+ public static HttpClient Create(HttpStatusCode statusCode, HttpContent? content,
+ Action? onBeforeSend = null)
+ {
+ MessageHandler handler = new(statusCode, content, onBeforeSend);
+ return new HttpClient(handler, true);
+ }
+
+ private class MessageHandler : HttpMessageHandler
+ {
+ private readonly HttpContent? _content;
+ private readonly Action? _onBeforeSend;
+ private readonly HttpStatusCode _statusCode;
+
+ public MessageHandler(HttpStatusCode statusCode, HttpContent? content,
+ Action? onBeforeSend = null)
+ {
+ _statusCode = statusCode;
+ _content = content;
+ _onBeforeSend = onBeforeSend;
+ }
+
+ protected override Task SendAsync(HttpRequestMessage request,
+ CancellationToken cancellationToken)
+ {
+ _onBeforeSend?.Invoke(request);
+
+ return Task.FromResult(new HttpResponseMessage
+ {
+ StatusCode = _statusCode,
+ Content = _content
+ });
+ }
+ }
+ }
+}
diff --git a/Byteology.TypedHttpClients.Tests/TestServices/ITestService.cs b/Byteology.TypedHttpClients.Tests/TestServices/ITestService.cs
index 243fd74..b093275 100644
--- a/Byteology.TypedHttpClients.Tests/TestServices/ITestService.cs
+++ b/Byteology.TypedHttpClients.Tests/TestServices/ITestService.cs
@@ -1,43 +1,42 @@
-using System.Threading.Tasks;
-
-namespace Byteology.TypedHttpClients.Tests.TestServices
-{
- internal interface ITestService
- {
- [HttpEndpoint("POST", "/noresultaction")]
- Task NoResultActionAsync();
-
- [HttpEndpoint("POST", "/action")]
- Task ActionAsync();
-
- [HttpEndpoint("POST", "/actionbody")]
- Task BodyActionAsync([HttpBody]TestServiceResult body);
-
- [HttpEndpoint("POST", "/simpleuri")]
- Task SimpleUriAsync();
-
- [HttpEndpoint("POST", "simpleuri")]
- Task SimpleUriNoDashAsync();
-
- [HttpEndpoint("POST", "/paramuri/{param}")]
- Task ParamUriAsync(object param);
-
- [HttpEndpoint("POST", "/query")]
- Task QueryAsync(int i, string s, bool b, float f, object n, int?[] a);
-
- [HttpEndpoint("VERB", "/verb")]
- Task VerbAsync();
-
- [HttpEndpoint("POST", "/uri")]
- Task OutParamAsync(out int param);
-
- [HttpEndpoint("POST", "/uri")]
- Task MultipleBodyAsync([HttpBody] int param, [HttpBody] int param2);
-
- Task NotDecoratedAsync();
-
- [HttpEndpoint("POST", "/uri")]
- int NotAsync();
- }
-
-}
+using System.Threading.Tasks;
+
+namespace Byteology.TypedHttpClients.Tests.TestServices
+{
+ internal interface ITestService
+ {
+ [HttpEndpoint("POST", "/noResultAction")]
+ Task NoResultActionAsync();
+
+ [HttpEndpoint("POST", "/action", Tags = new[] { "tag" })]
+ Task ActionAsync();
+
+ [HttpEndpoint("POST", "/actionBody")]
+ Task BodyActionAsync([HttpBody] TestServiceResult body);
+
+ [HttpEndpoint("POST", "/simpleUri")]
+ Task SimpleUriAsync();
+
+ [HttpEndpoint("POST", "simpleUri")]
+ Task SimpleUriNoDashAsync();
+
+ [HttpEndpoint("POST", "/paramUri/{param}")]
+ Task ParamUriAsync(object param);
+
+ [HttpEndpoint("POST", "/query")]
+ Task QueryAsync(int i, string s, bool b, float f, object? n, int?[] a);
+
+ [HttpEndpoint("VERB", "/verb")]
+ Task VerbAsync();
+
+ [HttpEndpoint("POST", "/uri")]
+ Task OutParamAsync(out int param);
+
+ [HttpEndpoint("POST", "/uri")]
+ Task MultipleBodyAsync([HttpBody] int param, [HttpBody] int param2);
+
+ Task NotDecoratedAsync();
+
+ [HttpEndpoint("POST", "/uri")]
+ int NotAsync();
+ }
+}
diff --git a/Byteology.TypedHttpClients.sln b/Byteology.TypedHttpClients.sln
index 7b04846..d057ccc 100644
--- a/Byteology.TypedHttpClients.sln
+++ b/Byteology.TypedHttpClients.sln
@@ -1,61 +1,62 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.0.31808.319
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Byteology.TypedHttpClients", "Byteology.TypedHttpClients\Byteology.TypedHttpClients.csproj", "{C212C350-4301-4773-9029-162826EF7CD0}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Byteology.TypedHttpClients.Tests", "Byteology.TypedHttpClients.Tests\Byteology.TypedHttpClients.Tests.csproj", "{20BB41AD-2786-4550-8D94-182B7D723BA5}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{28CC8107-D48A-4EBB-84A4-37D113F3614D}"
- ProjectSection(SolutionItems) = preProject
- .editorconfig = .editorconfig
- .gitignore = .gitignore
- Icon.png = Icon.png
- EndProjectSection
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{83E38053-A14F-4FC7-A099-46A611744FCF}"
- ProjectSection(SolutionItems) = preProject
- .github\workflows\push-action.yml = .github\workflows\push-action.yml
- .github\workflows\release-action.yml = .github\workflows\release-action.yml
- EndProjectSection
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{E93839AF-ED4E-4074-B642-DCBB4FE18CC9}"
- ProjectSection(SolutionItems) = preProject
- .github\CODEOWNERS = .github\CODEOWNERS
- .github\README.md = .github\README.md
- EndProjectSection
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".sonar", ".sonar", "{78567D61-B59E-4379-A572-4FF8C4F117AA}"
- ProjectSection(SolutionItems) = preProject
- .sonar\coverlet.runsettings = .sonar\coverlet.runsettings
- .sonar\SonarQube.Analysis.xml = .sonar\SonarQube.Analysis.xml
- EndProjectSection
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {C212C350-4301-4773-9029-162826EF7CD0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {C212C350-4301-4773-9029-162826EF7CD0}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C212C350-4301-4773-9029-162826EF7CD0}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C212C350-4301-4773-9029-162826EF7CD0}.Release|Any CPU.Build.0 = Release|Any CPU
- {20BB41AD-2786-4550-8D94-182B7D723BA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {20BB41AD-2786-4550-8D94-182B7D723BA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {20BB41AD-2786-4550-8D94-182B7D723BA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {20BB41AD-2786-4550-8D94-182B7D723BA5}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(NestedProjects) = preSolution
- {83E38053-A14F-4FC7-A099-46A611744FCF} = {E93839AF-ED4E-4074-B642-DCBB4FE18CC9}
- {E93839AF-ED4E-4074-B642-DCBB4FE18CC9} = {28CC8107-D48A-4EBB-84A4-37D113F3614D}
- {78567D61-B59E-4379-A572-4FF8C4F117AA} = {28CC8107-D48A-4EBB-84A4-37D113F3614D}
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {3C5450B1-6E34-4347-9447-4F8814CEECF4}
- EndGlobalSection
-EndGlobal
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31808.319
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Byteology.TypedHttpClients", "Byteology.TypedHttpClients\Byteology.TypedHttpClients.csproj", "{C212C350-4301-4773-9029-162826EF7CD0}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Byteology.TypedHttpClients.Tests", "Byteology.TypedHttpClients.Tests\Byteology.TypedHttpClients.Tests.csproj", "{20BB41AD-2786-4550-8D94-182B7D723BA5}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{28CC8107-D48A-4EBB-84A4-37D113F3614D}"
+ ProjectSection(SolutionItems) = preProject
+ .editorconfig = .editorconfig
+ .gitignore = .gitignore
+ Icon.png = Icon.png
+ .gitattributes = .gitattributes
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{83E38053-A14F-4FC7-A099-46A611744FCF}"
+ ProjectSection(SolutionItems) = preProject
+ .github\workflows\push-action.yml = .github\workflows\push-action.yml
+ .github\workflows\release-action.yml = .github\workflows\release-action.yml
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{E93839AF-ED4E-4074-B642-DCBB4FE18CC9}"
+ ProjectSection(SolutionItems) = preProject
+ .github\CODEOWNERS = .github\CODEOWNERS
+ .github\README.md = .github\README.md
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".sonar", ".sonar", "{78567D61-B59E-4379-A572-4FF8C4F117AA}"
+ ProjectSection(SolutionItems) = preProject
+ .sonar\coverlet.runsettings = .sonar\coverlet.runsettings
+ .sonar\SonarQube.Analysis.xml = .sonar\SonarQube.Analysis.xml
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {C212C350-4301-4773-9029-162826EF7CD0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C212C350-4301-4773-9029-162826EF7CD0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C212C350-4301-4773-9029-162826EF7CD0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C212C350-4301-4773-9029-162826EF7CD0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {20BB41AD-2786-4550-8D94-182B7D723BA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {20BB41AD-2786-4550-8D94-182B7D723BA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {20BB41AD-2786-4550-8D94-182B7D723BA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {20BB41AD-2786-4550-8D94-182B7D723BA5}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {83E38053-A14F-4FC7-A099-46A611744FCF} = {E93839AF-ED4E-4074-B642-DCBB4FE18CC9}
+ {E93839AF-ED4E-4074-B642-DCBB4FE18CC9} = {28CC8107-D48A-4EBB-84A4-37D113F3614D}
+ {78567D61-B59E-4379-A572-4FF8C4F117AA} = {28CC8107-D48A-4EBB-84A4-37D113F3614D}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {3C5450B1-6E34-4347-9447-4F8814CEECF4}
+ EndGlobalSection
+EndGlobal
diff --git a/Byteology.TypedHttpClients/Byteology.TypedHttpClients.csproj b/Byteology.TypedHttpClients/Byteology.TypedHttpClients.csproj
index c9f7bde..166aa20 100644
--- a/Byteology.TypedHttpClients/Byteology.TypedHttpClients.csproj
+++ b/Byteology.TypedHttpClients/Byteology.TypedHttpClients.csproj
@@ -1,45 +1,46 @@
-
-
-
- net5.0
- True
- true
- Byteology
- Byteology Ltd
- https://github.com/Byteology/typed-http-clients.git
- Icon.png
- Provides classes for creating typed http clients from an interface describing a service.
- 1.0.0
- http typed HttpClient HttpClientFactory
- MIT
- https://github.com/Byteology/typed-http-clients
- git
-
-
-
-
- True
-
- False
-
-
-
-
-
- True
-
- False
-
-
-
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
+
+
+
+ net6.0
+ True
+ true
+ Byteology
+ Byteology Ltd
+ https://github.com/Byteology/typed-http-clients.git
+ Icon.png
+ Provides classes for creating typed http clients from an interface describing a service.
+ 2.0.0
+ http typed HttpClient HttpClientFactory
+ MIT
+ https://github.com/Byteology/typed-http-clients
+ git
+ enable
+
+
+
+
+ True
+
+ False
+
+
+
+
+
+ True
+
+ False
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
diff --git a/Byteology.TypedHttpClients/Clients/JsonHttpClient.cs b/Byteology.TypedHttpClients/Clients/JsonHttpClient.cs
index b891f5f..87ce304 100644
--- a/Byteology.TypedHttpClients/Clients/JsonHttpClient.cs
+++ b/Byteology.TypedHttpClients/Clients/JsonHttpClient.cs
@@ -1,79 +1,81 @@
-using System.Net.Http;
-using System.Net.Http.Headers;
-using System.Text;
-using System.Text.Json;
-using System.Threading.Tasks;
-
-namespace Byteology.TypedHttpClients
-{
- ///
- /// An HTTP clients that implement a contract of a service with a JSON content type.
- ///
- ///
- public class JsonHttpClient : TypedHttpClient
- where TServiceContract : class
- {
- ///
- /// Gets the JSON serialization options used to create and parse the HTTP requests and responses.
- ///
- public JsonSerializerOptions JsonSerializerOptions { get; } = new JsonSerializerOptions(JsonSerializerDefaults.Web);
-
- ///
- /// Initializes a new instance of the class using an
- /// that will send the HTTP requests.
- ///
- /// The HTTP client.
- public JsonHttpClient(HttpClient httpClient) : base(httpClient) { }
-
- ///
- /// Builds an HTTP request with a JSON content type.
- ///
- ///
- ///
- ///
- ///
- protected override Task BuildRequestAsync(string verb, string uri, object body, string[] tags)
- {
- HttpRequestMessage httpRequest = new(new HttpMethod(verb), uri);
- httpRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
-
- if (body != null)
- httpRequest.Content = new StringContent(JsonSerializer.Serialize(body, JsonSerializerOptions),
- Encoding.UTF8,
- "application/json");
-
- return Task.FromResult(httpRequest);
- }
-
- ///
- /// Throws an exception if the
- /// property for the HTTP response is false.
- ///
- ///
- ///
- ///
- protected override Task ProcessResponse(HttpResponseMessage response, string[] tags)
- {
- response.EnsureSuccessStatusCode();
- return Task.CompletedTask;
- }
-
- ///
- /// Throws an exception if the
- /// property for the HTTP response is false. Otherwise converts the response content
- /// to a object and returns it.
- ///
- ///
- ///
- ///
- protected override async Task ProcessResponse(HttpResponseMessage response, string[] tags)
- {
- response.EnsureSuccessStatusCode();
-
- string bodyString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
- TResult result = JsonSerializer.Deserialize(bodyString, JsonSerializerOptions);
-
- return result;
- }
- }
-}
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+
+namespace Byteology.TypedHttpClients
+{
+ ///
+ /// An HTTP clients that implement a contract of a service with a JSON content type.
+ ///
+ ///
+ public class JsonHttpClient : TypedHttpClient
+ where TServiceContract : class
+ {
+ ///
+ /// Initializes a new instance of the class using an
+ /// that will send the HTTP requests.
+ ///
+ /// The HTTP client.
+ public JsonHttpClient(HttpClient httpClient) : base(httpClient) { }
+
+ ///
+ /// Gets the JSON serialization options used to create and parse the HTTP requests and responses.
+ ///
+ public JsonSerializerOptions JsonSerializerOptions { get; } =
+ new JsonSerializerOptions(JsonSerializerDefaults.Web);
+
+ ///
+ /// Builds an HTTP request with a JSON content type.
+ ///
+ ///
+ ///
+ ///
+ ///
+ protected override Task BuildRequestAsync(string verb, string uri, object? body,
+ string[]? tags)
+ {
+ HttpRequestMessage httpRequest = new(new HttpMethod(verb), uri);
+ httpRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
+
+ if (body != null)
+ httpRequest.Content = new StringContent(JsonSerializer.Serialize(body, JsonSerializerOptions),
+ Encoding.UTF8,
+ "application/json");
+
+ return Task.FromResult(httpRequest);
+ }
+
+ ///
+ /// Throws an exception if the
+ /// property for the HTTP response is false.
+ ///
+ ///
+ ///
+ ///
+ protected override Task ProcessResponse(HttpResponseMessage response, string[]? tags)
+ {
+ response.EnsureSuccessStatusCode();
+ return Task.CompletedTask;
+ }
+
+ ///
+ /// Throws an exception if the
+ /// property for the HTTP response is false. Otherwise converts the response content
+ /// to a object and returns it.
+ ///
+ ///
+ ///
+ ///
+ protected override async Task ProcessResponse(HttpResponseMessage response, string[]? tags)
+ {
+ response.EnsureSuccessStatusCode();
+
+ string bodyString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+ TResult result = JsonSerializer.Deserialize(bodyString, JsonSerializerOptions)!;
+
+ return result;
+ }
+ }
+}
diff --git a/Byteology.TypedHttpClients/Clients/TypedHttpClient.cs b/Byteology.TypedHttpClients/Clients/TypedHttpClient.cs
index 8979bd5..26a6912 100644
--- a/Byteology.TypedHttpClients/Clients/TypedHttpClient.cs
+++ b/Byteology.TypedHttpClients/Clients/TypedHttpClient.cs
@@ -1,257 +1,270 @@
-using Byteology.GuardClauses;
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net.Http;
-using System.Reflection;
-using System.Text;
-using System.Threading.Tasks;
-using System.Web;
-
-namespace Byteology.TypedHttpClients
-{
- ///
- /// Provides a base class for HTTP clients that implement a service contract.
- ///
- /// The service contract. It should be an interface containing only
- /// async methods decorated with and having no output parameters.
- /// A single method parameter is allowed to be decorated with the
- /// in order to be used as the request's content. All other parameters will be used either as route or query parameters
- /// depending on whether their name has a match in the or not.
- #pragma warning disable CS0618 // Type or member is obsolete
- public abstract class TypedHttpClient : IDispatchHandler
- #pragma warning restore CS0618 // Type or member is obsolete
- where TServiceContract : class
- {
- private readonly HttpClient _httpClient;
-
- ///
- /// Gets the endpoints of the service.
- ///
- public TServiceContract Endpoints { get; }
-
- ///
- /// Initializes a new instance of the
- /// class using an
- /// that will send the HTTP requests.
- ///
- /// The HTTP client.
- protected TypedHttpClient(HttpClient httpClient)
- {
- Guard.Argument(httpClient, nameof(httpClient)).NotNull();
-
- _httpClient = httpClient;
- #pragma warning disable CS0618 // Type or member is obsolete
- Endpoints = DispatchProxyDelegator.Create(this);
- #pragma warning restore CS0618 // Type or member is obsolete
- }
-
- ///
- /// Builds a query string.
- ///
- /// The query parameters.
- /// The tags of the request. These are provided by the
- /// in order for this method to be able to recognize requests that require special treatment.
- protected virtual string BuildQueryString(IEnumerable queryParameters, string[] tags)
- {
- StringBuilder builder = new();
-
- if (queryParameters != null)
- foreach (HttpUriParameter parameter in queryParameters)
- {
- if (parameter.Value is ICollection collection)
- foreach (object item in collection)
- builder.Append($"{HttpUtility.UrlEncode(parameter.Name)}={HttpUtility.UrlEncode(item?.ToString())}&");
- else
- builder.Append($"{HttpUtility.UrlEncode(parameter.Name)}={HttpUtility.UrlEncode(parameter.Value?.ToString())}&");
- }
-
- if (builder.Length > 0)
- builder.Remove(builder.Length - 1, 1);
-
- return builder.ToString();
- }
-
- ///
- /// Builds an HTTP request.
- ///
- /// The HTTP verb of the request.
- /// The URI of the request.
- /// The content body of the request or if no body should be provided.
- /// The tags of the request. These are provided by the
- /// in order for this method to be able to recognize requests that require special treatment.
- protected abstract Task BuildRequestAsync(string verb, string uri, object body, string[] tags);
-
- ///
- /// Send an HTTP request as an asynchronous operation.
- ///
- /// The HTTP client to use.
- /// The request to send.
- /// The tags of the request. These are specified by the
- /// in order for this method to be able to recognize requests that require special treatment.
- protected virtual async Task SendRequestAsync(HttpClient httpClient, HttpRequestMessage request, string[] tags)
- {
- Guard.Argument(httpClient, nameof(httpClient)).NotNull();
- Guard.Argument(request, nameof(request)).NotNull();
-
- HttpResponseMessage response = await httpClient.SendAsync(request).ConfigureAwait(false);
- response.RequestMessage = request;
- return response;
- }
-
- ///
- /// Processes the response of an HTTP request.
- ///
- /// The HTTP response.
- /// The tags of the request. These are specified by the
- /// in order for this method to be able to recognize requests that require special treatment.
- protected abstract Task ProcessResponse(HttpResponseMessage response, string[] tags);
- ///
- /// Processes the response of an HTTP request and converts its body to .
- ///
- /// The type of the object the response message should be converted to.
- /// The HTTP response.
- /// The tags of the request. These are specified by the
- /// in order for this method to be able to recognize requests that require special treatment.
- protected abstract Task ProcessResponse(HttpResponseMessage response, string[] tags);
-
- object IDispatchHandler.Dispatch(MethodInfo targetMethod, object[] args)
- {
- if (targetMethod.GetCustomAttribute(true) == null)
- throw new InvalidOperationException($"The method should be decorated with the {typeof(HttpEndpointAttribute)}.");
-
- ParameterInfo[] parameters = targetMethod.GetParameters();
-
- if (parameters.Any(p => p.IsOut))
- throw new InvalidOperationException("Output parameters are not allowed.");
-
- if (parameters.Count(p => p.GetCustomAttribute() != null) > 1)
- throw new InvalidOperationException("Maximum of one body parameter is allowed per method.");
-
- if (returnsTask(targetMethod))
- return sendRequestAndParseResponseAsync(targetMethod, args);
- else if (returnsGenericTask(targetMethod))
- {
- MethodInfo sendRequestMethod = typeof(TypedHttpClient)
- .GetMethod(nameof(sendRequestAndParseGenericResponseAsync),BindingFlags.NonPublic | BindingFlags.Instance);
- sendRequestMethod = sendRequestMethod.MakeGenericMethod(targetMethod.ReturnType.GenericTypeArguments.Single());
- return sendRequestMethod.Invoke(this, new object[] { targetMethod, args });
- }
- else
- throw new InvalidOperationException("Method should return either Task or Task<>.");
- }
-
- private async Task sendRequestAndParseResponseAsync(MethodInfo targetMethod, object[] args)
- {
- HttpEndpointAttribute endpointAttribute = targetMethod.GetCustomAttribute(true);
- string[] tags = endpointAttribute.Tags;
-
- HttpRequestMessage request = await createRequestAsync(targetMethod, args).ConfigureAwait(false);
- HttpResponseMessage response = await SendRequestAsync(_httpClient, request, tags).ConfigureAwait(false);
-
- await ProcessResponse(response, tags).ConfigureAwait(false);
- }
- private async Task sendRequestAndParseGenericResponseAsync(MethodInfo targetMethod, object[] args)
- {
- HttpEndpointAttribute endpointAttribute = targetMethod.GetCustomAttribute(true);
- string[] tags = endpointAttribute.Tags;
- HttpRequestMessage request = await createRequestAsync(targetMethod, args).ConfigureAwait(false);
- HttpResponseMessage response = await SendRequestAsync(_httpClient, request, tags).ConfigureAwait(false);
-
- return await ProcessResponse(response, tags).ConfigureAwait(false);
- }
-
- private async Task createRequestAsync(MethodInfo targetMethod, object[] args)
- {
- HttpEndpointAttribute endpointAttribute = targetMethod.GetCustomAttribute(true);
-
- string verb = endpointAttribute.Verb;
- string routeTemplate = endpointAttribute.RouteTemplate;
- string[] tags = endpointAttribute.Tags;
-
- List uriParameters = getParameters(targetMethod, args, out object body);
- string uri = getUri(tags, routeTemplate, ref uriParameters);
-
- HttpRequestMessage request = await BuildRequestAsync(verb, uri, body, tags).ConfigureAwait(false);
- return request;
- }
- private static List getParameters(MethodInfo targetMethod, object[] args, out object body)
- {
- List result = new();
- body = null;
-
- ParameterInfo[] parameters = targetMethod.GetParameters();
-
- for (int i = 0; i < parameters.Length; i++)
- {
- ParameterInfo parameter = parameters[i];
- object value = args[i];
-
- if (isBodyParameter(parameter))
- body = value;
- else
- {
- HttpUriParameter uriParameter = new(parameter.Name, value);
- result.Add(uriParameter);
- }
- }
-
- return result;
-
- static bool isBodyParameter(ParameterInfo p)
- {
- HttpBodyAttribute bodyAttribute = p.GetCustomAttribute();
- return bodyAttribute != null;
- }
- }
- private string getUri(string[] tags, string routeTemplate, ref List uriParameters)
- {
-
- if (routeTemplate.StartsWith("/"))
- routeTemplate = routeTemplate[1..];
-
- for (int i = 0; i < uriParameters.Count; i++)
- {
- HttpUriParameter parameter = uriParameters[i];
-
- if (routeTemplate.Contains($"{{{parameter.Name}}}"))
- {
- routeTemplate = routeTemplate.Replace(
- $"{{{parameter.Name}}}",
- HttpUtility.UrlEncode(parameter.Value?.ToString() ?? string.Empty));
-
- uriParameters.RemoveAt(i);
- i--;
- }
- }
-
- routeTemplate = addQueryString(uriParameters, routeTemplate, tags);
-
- return routeTemplate;
-
- string addQueryString(List uriParameters, string uri, string[] tags)
- {
- string queryString = BuildQueryString(uriParameters, tags);
- if (!string.IsNullOrEmpty(queryString))
- {
- if (!queryString.StartsWith("?"))
- queryString = "?" + queryString;
-
- uri += queryString;
- }
-
- return uri;
- }
- }
-
- private static bool returnsTask(MethodInfo method)
- {
- return method.ReturnType == typeof(Task);
- }
- private static bool returnsGenericTask(MethodInfo method)
- {
- return method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>);
- }
- }
-}
+using Byteology.GuardClauses;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Net.Http;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using System.Web;
+
+namespace Byteology.TypedHttpClients
+{
+ ///
+ /// Provides a base class for HTTP clients that implement a service contract.
+ ///
+ /// The service contract. It should be an interface containing only
+ /// async methods decorated with and having no output parameters.
+ /// A single method parameter is allowed to be decorated with the
+ /// in order to be used as the request's content. All other parameters will be used either as route or query parameters
+ /// depending on whether their name has a match in the or not.
+ #pragma warning disable CS0618 // Type or member is obsolete
+ public abstract class TypedHttpClient : IDispatchHandler
+ #pragma warning restore CS0618 // Type or member is obsolete
+ where TServiceContract : class
+ {
+ private readonly HttpClient _httpClient;
+
+ ///
+ /// Initializes a new instance of the
+ /// class using an
+ /// that will send the HTTP requests.
+ ///
+ /// The HTTP client.
+ protected TypedHttpClient(HttpClient httpClient)
+ {
+ Guard.Argument(httpClient, nameof(httpClient)).NotNull();
+
+ _httpClient = httpClient;
+ #pragma warning disable CS0618 // Type or member is obsolete
+ Endpoints = DispatchProxyDelegator.Create(this);
+ #pragma warning restore CS0618 // Type or member is obsolete
+ }
+
+ ///
+ /// Gets the endpoints of the service.
+ ///
+ public TServiceContract Endpoints { get; }
+
+ [SuppressMessage("Major Code Smell",
+ "S3011:Reflection should not be used to increase accessibility of classes, methods, or fields")]
+ object? IDispatchHandler.Dispatch(MethodInfo targetMethod, object?[]? args)
+ {
+ if (targetMethod.GetCustomAttribute(true) == null)
+ throw new
+ InvalidOperationException($"The method should be decorated with the {typeof(HttpEndpointAttribute)}.");
+
+ ParameterInfo[] parameters = targetMethod.GetParameters();
+
+ if (parameters.Any(p => p.IsOut))
+ throw new InvalidOperationException("Output parameters are not allowed.");
+
+ if (parameters.Count(p => p.GetCustomAttribute() != null) > 1)
+ throw new InvalidOperationException("Maximum of one body parameter is allowed per method.");
+
+ if (returnsTask(targetMethod))
+ return sendRequestAndParseResponseAsync(targetMethod, args);
+ else if (returnsGenericTask(targetMethod))
+ {
+ MethodInfo sendRequestMethod = typeof(TypedHttpClient)
+ .GetMethod(nameof(sendRequestAndParseGenericResponseAsync),
+ BindingFlags.NonPublic | BindingFlags.Instance)!;
+ sendRequestMethod =
+ sendRequestMethod.MakeGenericMethod(targetMethod.ReturnType.GenericTypeArguments.Single());
+ return sendRequestMethod.Invoke(this, new object?[] { targetMethod, args });
+ }
+ else
+ throw new InvalidOperationException("Method should return either Task or Task<>.");
+ }
+
+ ///
+ /// Builds a query string.
+ ///
+ /// The query parameters.
+ /// The tags of the request. These are provided by the
+ /// in order for this method to be able to recognize requests that require special treatment.
+ protected virtual string BuildQueryString(IEnumerable queryParameters, string[]? tags)
+ {
+ StringBuilder builder = new();
+
+ if (queryParameters != null)
+ foreach (HttpUriParameter parameter in queryParameters)
+ {
+ if (parameter.Value is ICollection collection)
+ foreach (object item in collection)
+ builder.Append($"{HttpUtility.UrlEncode(parameter.Name)}={HttpUtility.UrlEncode(item?.ToString())}&");
+ else
+ builder.Append($"{HttpUtility.UrlEncode(parameter.Name)}={HttpUtility.UrlEncode(parameter.Value?.ToString())}&");
+ }
+
+ if (builder.Length > 0)
+ builder.Remove(builder.Length - 1, 1);
+
+ return builder.ToString();
+ }
+
+ ///
+ /// Builds an HTTP request.
+ ///
+ /// The HTTP verb of the request.
+ /// The URI of the request.
+ /// The content body of the request or if no body should be provided.
+ /// The tags of the request. These are provided by the
+ /// in order for this method to be able to recognize requests that require special treatment.
+ protected abstract Task BuildRequestAsync(string verb, string uri, object? body,
+ string[]? tags);
+
+ ///
+ /// Send an HTTP request as an asynchronous operation.
+ ///
+ /// The HTTP client to use.
+ /// The request to send.
+ /// The tags of the request. These are specified by the
+ /// in order for this method to be able to recognize requests that require special treatment.
+ protected virtual async Task SendRequestAsync(
+ HttpClient httpClient, HttpRequestMessage request, string[]? tags)
+ {
+ Guard.Argument(httpClient, nameof(httpClient)).NotNull();
+ Guard.Argument(request, nameof(request)).NotNull();
+
+ HttpResponseMessage response = await httpClient.SendAsync(request).ConfigureAwait(false);
+ response.RequestMessage = request;
+ return response;
+ }
+
+ ///
+ /// Processes the response of an HTTP request.
+ ///
+ /// The HTTP response.
+ /// The tags of the request. These are specified by the
+ /// in order for this method to be able to recognize requests that require special treatment.
+ protected abstract Task ProcessResponse(HttpResponseMessage response, string[]? tags);
+
+ ///
+ /// Processes the response of an HTTP request and converts its body to .
+ ///
+ /// The type of the object the response message should be converted to.
+ /// The HTTP response.
+ /// The tags of the request. These are specified by the
+ /// in order for this method to be able to recognize requests that require special treatment.
+ protected abstract Task ProcessResponse(HttpResponseMessage response, string[]? tags);
+
+ private async Task sendRequestAndParseResponseAsync(MethodInfo targetMethod, object?[]? args)
+ {
+ HttpEndpointAttribute endpointAttribute = targetMethod.GetCustomAttribute(true)!;
+ string[]? tags = endpointAttribute.Tags;
+
+ HttpRequestMessage request = await createRequestAsync(targetMethod, args).ConfigureAwait(false);
+ HttpResponseMessage response = await SendRequestAsync(_httpClient, request, tags).ConfigureAwait(false);
+
+ await ProcessResponse(response, tags).ConfigureAwait(false);
+ }
+
+ private async Task sendRequestAndParseGenericResponseAsync(
+ MethodInfo targetMethod, object[] args)
+ {
+ HttpEndpointAttribute endpointAttribute = targetMethod.GetCustomAttribute(true)!;
+ string[]? tags = endpointAttribute.Tags;
+ HttpRequestMessage request = await createRequestAsync(targetMethod, args).ConfigureAwait(false);
+ HttpResponseMessage response = await SendRequestAsync(_httpClient, request, tags).ConfigureAwait(false);
+
+ return await ProcessResponse(response, tags).ConfigureAwait(false);
+ }
+
+ private async Task createRequestAsync(MethodInfo targetMethod, object?[]? args)
+ {
+ HttpEndpointAttribute endpointAttribute = targetMethod.GetCustomAttribute(true)!;
+
+ string verb = endpointAttribute.Verb;
+ string routeTemplate = endpointAttribute.RouteTemplate;
+ string[]? tags = endpointAttribute.Tags;
+
+ List uriParameters = getParameters(targetMethod, args, out object? body);
+ string uri = getUri(tags, routeTemplate, ref uriParameters);
+
+ HttpRequestMessage request = await BuildRequestAsync(verb, uri, body, tags).ConfigureAwait(false);
+ return request;
+ }
+
+ private static List getParameters(MethodInfo targetMethod, object?[]? args, out object? body)
+ {
+ List result = new();
+ body = null;
+
+ ParameterInfo[] parameters = targetMethod.GetParameters();
+
+ for (int i = 0; i < parameters.Length; i++)
+ {
+ ParameterInfo parameter = parameters[i];
+ object? value = args![i];
+
+ if (isBodyParameter(parameter))
+ body = value;
+ else
+ {
+ HttpUriParameter uriParameter = new(parameter.Name!, value);
+ result.Add(uriParameter);
+ }
+ }
+
+ return result;
+
+ static bool isBodyParameter(ParameterInfo p)
+ {
+ HttpBodyAttribute? bodyAttribute = p.GetCustomAttribute();
+ return bodyAttribute != null;
+ }
+ }
+
+ private string getUri(string[]? tags, string routeTemplate, ref List uriParameters)
+ {
+ if (routeTemplate.StartsWith("/"))
+ routeTemplate = routeTemplate[1..];
+
+ for (int i = 0; i < uriParameters.Count; i++)
+ {
+ HttpUriParameter parameter = uriParameters[i];
+
+ if (routeTemplate.Contains($"{{{parameter.Name}}}"))
+ {
+ routeTemplate = routeTemplate.Replace($"{{{parameter.Name}}}",
+ HttpUtility.UrlEncode(parameter.Value?.ToString() ??
+ string.Empty));
+
+ uriParameters.RemoveAt(i);
+ i--;
+ }
+ }
+
+ routeTemplate = addQueryString(uriParameters, routeTemplate, tags);
+
+ return routeTemplate;
+
+ string addQueryString(List uriParameters, string uri, string[]? tags)
+ {
+ string queryString = BuildQueryString(uriParameters, tags);
+ if (!string.IsNullOrEmpty(queryString))
+ {
+ if (!queryString.StartsWith("?"))
+ queryString = "?" + queryString;
+
+ uri += queryString;
+ }
+
+ return uri;
+ }
+ }
+
+ private static bool returnsTask(MethodInfo method)
+ {
+ return method.ReturnType == typeof(Task);
+ }
+
+ private static bool returnsGenericTask(MethodInfo method)
+ {
+ return method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>);
+ }
+ }
+}
diff --git a/Byteology.TypedHttpClients/Dispatching/DispatchProxyDelegator.cs b/Byteology.TypedHttpClients/Dispatching/DispatchProxyDelegator.cs
index d1d9d73..aa4a673 100644
--- a/Byteology.TypedHttpClients/Dispatching/DispatchProxyDelegator.cs
+++ b/Byteology.TypedHttpClients/Dispatching/DispatchProxyDelegator.cs
@@ -1,47 +1,52 @@
-using System;
-using System.ComponentModel;
-using System.Reflection;
-
-namespace Byteology.TypedHttpClients
-{
- ///
- /// Used to transfer a calls to an .
- ///
- [Browsable(false)] // This class shouldn't be used externaly but needs to be seen by calling assemblies so we just hide it from intellisense.
- [EditorBrowsable(EditorBrowsableState.Never)]
- [Obsolete("Meant for internal use only.")]
- public class DispatchProxyDelegator : DispatchProxy
- {
- ///
- /// Creates an object instance that implements the provided interface type.
- ///
- /// The type of the interface to be implemented.
- /// The object that will handle the method calls.
- public static TInterface Create(IDispatchHandler handler)
- where TInterface : class
- {
- TInterface proxy = DispatchProxy.Create();
- MethodInfo initMethod = typeof(DispatchProxyDelegator).GetMethod(nameof(initialize), BindingFlags.NonPublic | BindingFlags.Instance);
- initMethod.Invoke(proxy, new object[] { handler });
- return proxy;
- }
-
- private IDispatchHandler _dispatcher;
-
- private void initialize(IDispatchHandler dispatcher)
- {
- _dispatcher = dispatcher;
- }
-
- ///
- /// Whenever any method on the generated proxy type is called, this method is invoked to dispatch control.
- ///
- /// The method the caller invoked.
- /// The arguments the caller passed to the method.
- /// The object to return to the caller, or for void methods.
- protected override object Invoke(MethodInfo targetMethod, object[] args)
- {
- return _dispatcher.Dispatch(targetMethod, args);
- }
- }
-}
+using System;
+using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+
+namespace Byteology.TypedHttpClients
+{
+ ///
+ /// Used to transfer a calls to an .
+ ///
+ [Browsable(false)] // This class shouldn't be used externally but needs to be seen by calling assemblies so we just hide it from intellisense.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Obsolete("Meant for internal use only.")]
+ public class DispatchProxyDelegator : DispatchProxy
+ {
+ private IDispatchHandler? _dispatcher;
+
+ ///
+ /// Creates an object instance that implements the provided interface type.
+ ///
+ /// The type of the interface to be implemented.
+ /// The object that will handle the method calls.
+ [SuppressMessage("Major Code Smell",
+ "S3011:Reflection should not be used to increase accessibility of classes, methods, or fields")]
+ public static TInterface Create(IDispatchHandler handler)
+ where TInterface : class
+ {
+ TInterface proxy = DispatchProxy.Create();
+ MethodInfo initMethod =
+ typeof(DispatchProxyDelegator).GetMethod(nameof(initialize),
+ BindingFlags.NonPublic | BindingFlags.Instance)!;
+ initMethod.Invoke(proxy, new object[] { handler });
+ return proxy;
+ }
+
+ private void initialize(IDispatchHandler dispatcher)
+ {
+ _dispatcher = dispatcher;
+ }
+
+ ///
+ /// Whenever any method on the generated proxy type is called, this method is invoked to dispatch control.
+ ///
+ /// The method the caller invoked.
+ /// The arguments the caller passed to the method.
+ /// The object to return to the caller, or for void methods.
+ protected override object? Invoke(MethodInfo? targetMethod, object?[]? args)
+ {
+ return _dispatcher!.Dispatch(targetMethod!, args);
+ }
+ }
+}
diff --git a/Byteology.TypedHttpClients/Dispatching/IDispatchHandler.cs b/Byteology.TypedHttpClients/Dispatching/IDispatchHandler.cs
index 3c00671..89b1b50 100644
--- a/Byteology.TypedHttpClients/Dispatching/IDispatchHandler.cs
+++ b/Byteology.TypedHttpClients/Dispatching/IDispatchHandler.cs
@@ -1,23 +1,23 @@
-using System;
-using System.ComponentModel;
-using System.Reflection;
-
-namespace Byteology.TypedHttpClients
-{
- ///
- /// Provides a functionality for handling method calls.
- ///
- [Browsable(false)] // This interface shouldn't be used externaly but needs to be seen by calling assemblies so we just hide it from intellisense.
- [EditorBrowsable(EditorBrowsableState.Never)]
- [Obsolete("Meant for internal use only.")]
- public interface IDispatchHandler
- {
- ///
- /// Dispatches a specified method call.
- ///
- /// The invoked method.
- /// The arguments with which the method was invoked.
- /// The result of the method execution.
- object Dispatch(MethodInfo targetMethod, object[] args);
- }
-}
+using System;
+using System.ComponentModel;
+using System.Reflection;
+
+namespace Byteology.TypedHttpClients
+{
+ ///
+ /// Provides a functionality for handling method calls.
+ ///
+ [Browsable(false)] // This interface shouldn't be used externally but needs to be seen by calling assemblies so we just hide it from intellisense.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Obsolete("Meant for internal use only.")]
+ public interface IDispatchHandler
+ {
+ ///
+ /// Dispatches a specified method call.
+ ///
+ /// The invoked method.
+ /// The arguments with which the method was invoked.
+ /// The result of the method execution.
+ object? Dispatch(MethodInfo targetMethod, object?[]? args);
+ }
+}
diff --git a/Byteology.TypedHttpClients/HttpEndpointAttribute.cs b/Byteology.TypedHttpClients/HttpEndpointAttribute.cs
index 8973868..dc83ced 100644
--- a/Byteology.TypedHttpClients/HttpEndpointAttribute.cs
+++ b/Byteology.TypedHttpClients/HttpEndpointAttribute.cs
@@ -1,51 +1,51 @@
-using Byteology.GuardClauses;
-using System;
-
-namespace Byteology.TypedHttpClients
-{
- ///
- /// Used to mark a method's signature as a description of an HTTP endpoint.
- /// See for more information.
- ///
- ///
- [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
- public class HttpEndpointAttribute : Attribute
- {
- ///
- /// Gets the endpoint route. May contain parameter names surrounded by curly brackets
- /// which will be replaced by the passed arguments.
- ///
- public string RouteTemplate { get; }
-
- ///
- /// Gets the HTTP verb of the endpoint.
- ///
- public string Verb { get; }
-
- ///
- /// Gets or sets tags for the endpoint. These tags will be
- /// passed to all methods connected to building HTTP requests and parsing their responses
- /// and can be used to distinguish the endpoints which must be treated differently.
- ///
- public string[] Tags { get; set; }
-
- ///
- /// Initializes a new instance of the class.
- /// It is used to mark a method's signature as a description of an HTTP endpoint.
- /// See for more information.
- ///
- /// The HTTP verb of the endpoint.
- /// The endpoint route.
- /// May contain parameter names surrounded by curly brackets which will be replaced by the passed argument.
- ///
- ///
- public HttpEndpointAttribute(string verb, string routeTemplate)
- {
- Guard.Argument(verb, nameof(verb)).NotNullOrWhiteSpace();
- Guard.Argument(routeTemplate, nameof(routeTemplate)).NotNull();
-
- Verb = verb;
- RouteTemplate = routeTemplate;
- }
- }
-}
+using Byteology.GuardClauses;
+using System;
+
+namespace Byteology.TypedHttpClients
+{
+ ///
+ /// Used to mark a method's signature as a description of an HTTP endpoint.
+ /// See for more information.
+ ///
+ ///
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public class HttpEndpointAttribute : Attribute
+ {
+ ///
+ /// Initializes a new instance of the class.
+ /// It is used to mark a method's signature as a description of an HTTP endpoint.
+ /// See for more information.
+ ///
+ /// The HTTP verb of the endpoint.
+ /// The endpoint route.
+ /// May contain parameter names surrounded by curly brackets which will be replaced by the passed argument.
+ ///
+ ///
+ public HttpEndpointAttribute(string verb, string routeTemplate)
+ {
+ Guard.Argument(verb, nameof(verb)).NotNullOrWhiteSpace();
+ Guard.Argument(routeTemplate, nameof(routeTemplate)).NotNull();
+
+ Verb = verb;
+ RouteTemplate = routeTemplate;
+ }
+
+ ///
+ /// Gets the endpoint route. May contain parameter names surrounded by curly brackets
+ /// which will be replaced by the passed arguments.
+ ///
+ public string RouteTemplate { get; }
+
+ ///
+ /// Gets the HTTP verb of the endpoint.
+ ///
+ public string Verb { get; }
+
+ ///
+ /// Gets or sets tags for the endpoint. These tags will be
+ /// passed to all methods connected to building HTTP requests and parsing their responses
+ /// and can be used to distinguish the endpoints which must be treated differently.
+ ///
+ public string[]? Tags { get; set; }
+ }
+}
diff --git a/Byteology.TypedHttpClients/HttpUriParameter.cs b/Byteology.TypedHttpClients/HttpUriParameter.cs
index 9741a49..ab6a9b3 100644
--- a/Byteology.TypedHttpClients/HttpUriParameter.cs
+++ b/Byteology.TypedHttpClients/HttpUriParameter.cs
@@ -1,28 +1,29 @@
-namespace Byteology.TypedHttpClients
-{
- ///
- /// Represents a URI parameter.
- ///
- public class HttpUriParameter
- {
- ///
- /// Gets the name of the parameter.
- ///
- public string Name { get; }
- ///
- /// Gets the value of the parameter.
- ///
- public object Value { get; }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The name of the parameter.
- /// The value of the parameter.
- public HttpUriParameter(string name, object value)
- {
- Name = name;
- Value = value;
- }
- }
-}
+namespace Byteology.TypedHttpClients
+{
+ ///
+ /// Represents a URI parameter.
+ ///
+ public class HttpUriParameter
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The name of the parameter.
+ /// The value of the parameter.
+ public HttpUriParameter(string name, object? value)
+ {
+ Name = name;
+ Value = value;
+ }
+
+ ///
+ /// Gets the name of the parameter.
+ ///
+ public string Name { get; }
+
+ ///
+ /// Gets the value of the parameter.
+ ///
+ public object? Value { get; }
+ }
+}
diff --git a/Byteology.TypedHttpClients/ServiceCollectionExtensions.cs b/Byteology.TypedHttpClients/ServiceCollectionExtensions.cs
index 6b13b59..cfe35f5 100644
--- a/Byteology.TypedHttpClients/ServiceCollectionExtensions.cs
+++ b/Byteology.TypedHttpClients/ServiceCollectionExtensions.cs
@@ -1,48 +1,48 @@
-using Byteology.GuardClauses;
-using Microsoft.Extensions.DependencyInjection;
-using System;
-using System.Net.Http;
-
-namespace Byteology.TypedHttpClients
-{
- ///
- /// Contains extensions methods for injecting instances.
- ///
- public static class ServiceCollectionExtensions
- {
- ///
- public static IServiceCollection AddTypedHttpClient(this IServiceCollection services)
- where TClient : TypedHttpClient
- where TServiceContract : class
- {
- return AddTypedHttpClient(services, x => { });
- }
-
- ///
- /// Configures a binding between and using an
- /// underlying named . The name will be set to the
- /// type name of .
- ///
- ///
- /// The type of the to use.
- /// The .
- /// A delegate that is used to configure an .
- /// A reference to this instance after the operation has completed.
- public static IServiceCollection AddTypedHttpClient(
- this IServiceCollection services,
- Action configureClient)
- where TClient : TypedHttpClient
- where TServiceContract : class
- {
- Guard.Argument(configureClient, nameof(configureClient)).NotNull();
-
- if (typeof(TClient).IsAbstract)
- throw new ArgumentException(typeof(TClient).Name + " must be a concrete implemntation.");
-
- services.AddHttpClient(typeof(TServiceContract).FullName, configureClient);
- services.AddScoped(sp => sp.GetRequiredService().Endpoints);
-
- return services;
- }
- }
-}
+using Byteology.GuardClauses;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Net.Http;
+
+namespace Byteology.TypedHttpClients
+{
+ ///
+ /// Contains extensions methods for injecting instances.
+ ///
+ public static class ServiceCollectionExtensions
+ {
+ ///
+ public static IServiceCollection AddTypedHttpClient(this IServiceCollection services)
+ where TClient : TypedHttpClient
+ where TServiceContract : class
+ {
+ return AddTypedHttpClient(services, _ => { });
+ }
+
+ ///
+ /// Configures a binding between and using an
+ /// underlying named . The name will be set to the
+ /// type name of .
+ ///
+ ///
+ /// The type of the to use.
+ /// The .
+ /// A delegate that is used to configure an .
+ /// A reference to this instance after the operation has completed.
+ public static IServiceCollection AddTypedHttpClient(
+ this IServiceCollection services,
+ Action configureClient)
+ where TClient : TypedHttpClient
+ where TServiceContract : class
+ {
+ Guard.Argument(configureClient, nameof(configureClient)).NotNull();
+
+ if (typeof(TClient).IsAbstract)
+ throw new ArgumentException(typeof(TClient).Name + " must be a concrete implementation.");
+
+ services.AddHttpClient(typeof(TServiceContract).FullName, configureClient);
+ services.AddScoped(sp => sp.GetRequiredService().Endpoints);
+
+ return services;
+ }
+ }
+}
diff --git a/Icon.png b/Icon.png
index b702aaa..9566f4a 100644
Binary files a/Icon.png and b/Icon.png differ