Gossip4Net.Http.DependencyInjection
0.0.2
See the version list below for details.
dotnet add package Gossip4Net.Http.DependencyInjection --version 0.0.2
NuGet\Install-Package Gossip4Net.Http.DependencyInjection -Version 0.0.2
<PackageReference Include="Gossip4Net.Http.DependencyInjection" Version="0.0.2" />
paket add Gossip4Net.Http.DependencyInjection --version 0.0.2
#r "nuget: Gossip4Net.Http.DependencyInjection, 0.0.2"
// Install Gossip4Net.Http.DependencyInjection as a Cake Addin #addin nuget:?package=Gossip4Net.Http.DependencyInjection&version=0.0.2 // Install Gossip4Net.Http.DependencyInjection as a Cake Tool #tool nuget:?package=Gossip4Net.Http.DependencyInjection&version=0.0.2
Gossip4Net
Gossip4Net is an extensible http client middleware similar to Spring Feign. It allows developers to easily consume APIs using an interface contract, which gets automatically implemented.
Getting started
0. Get Gossip4Net
For standalone usage:
Install-Package Gossip4Net.Http
For usage within ASP.NET core:
Install-Package Gossip4Net.Http.DependencyInjection
1. Define your API contract and models
namespace MyDemo {
public record HttpBinResponseDto(IDictionary<string, string> Headers);
public record PersonRequestDto(string Fistname, string Lastname);
[HttpApi("https://httpbin.org")]
public interface HttpBinApi {
[GetMapping("/get")]
Task<HttpBinResponseDto> GetAsync();
[GetMapping("/get")]
HttpBinResponseDto GetSync();
[PostMapping("/post")]
Task PostAsync(Person person);
}
}
2. Obtain and configure a HttpGossipBuilder
using Gossip4Net.Http;
namespace MyDemo {
public class Demo {
public async Task Startup() {
IHttpGossipBuilder<HttpBinApi> builder = new HttpGossipBuilder<HttpBinApi>();
builder.AddDefaultBehavior();
}
}
}
For ASP.NET core, dependency injection can be used:
using Gossip4Net.Http.DependencyInjection;
namespace AspNetDemo {
public class Startup {
public void ConfigureServices(IServiceCollection services)
{
services.AddGossipHttpClient<HttpBinApi>();
}
}
}
3. Let Gossip4Net implement your API interface
using Microsoft.Extensions.DependencyInjection;
namespace MyDemo {
public class Demo {
public async Task Startup() {
IHttpGossipBuilder<HttpBinApi> builder = new HttpGossipBuilder<HttpBinApi>();
builder.AddDefaultBehavior();
HttpBinApi api = builder.Build();
}
}
}
4. Use your API
using Gossip4Net.Http;
namespace MyDemo {
public class Demo {
public async Task Startup() {
IHttpGossipBuilder<HttpBinApi> builder = new HttpGossipBuilder<HttpBinApi>();
builder.AddDefaultBehavior();
HttpBinApi api = builder.Build();
HttpBinResponseDto response = await api.GetAync();
}
}
}
Features
Url mapping
The API-Url can be specified using the [HttpApi]
attribute
[HttpApi("https://httpbin.org")]
public interface MyApi {}
and/or using the method mapping attributes (e.g. [GetMapping]
, [PostMapping]
, [PatchMapping]
):
[HttpApi]
public interface MyApi {
[GetMapping("https://httpbin.org/get")]
Task<HttpResponseMessage> GetResponseAync();
}
If both are present and the url specified on the method mapping is relative, it will be resolved/appended to the url given in [HttpApi]
.
[HttpApi("https://httpbin.org")]
public interface MyApi {
[GetMapping("/get")]
Task<HttpResponseMessage> GetResponseAync();
}
The above example will result in a call to https://httpbin.org/get
.
Path variables
Parameter values can be interpolated into the request path using the [PathVariable]
attribute.
[HttpApi("https://httpbin.org")]
public interface MyApi {
[GetMapping("/{method}")]
Task<HttpResponseMessage> GetResponseAync([PathVariable] string method);
[GetMapping("/{operation}")]
Task<HttpResponseMessage> GetResponseWithExplicitPathVariableNameAync([PathVariable("operation")] string method);
}
By default, the placeholder name is determined by the parameter name (e.g. "method"). It can also be specified manually.
Available properties
| Property | Description | Default |
|----------|-------------|---------|
| Name
| The path variable name. | The annotated parameter's name. |
| EscapePath
| If false
, special characters (like /
and ?
) are not escaped. | true
|
Query variables
Parameter values can be send as a query parameter using the [QueryVariable]
attribute.
[HttpApi("https://httpbin.org")]
public interface MyApi {
[GetMapping("/get")]
HttpResponseMessage GetWithQuery([QueryVariable] string testParam);
[GetMapping("/get")]
HttpResponseMessage GetWithExplicitlyNamedQuery([QueryVariable("testParam")] string myValue);
}
By default, the query variable name is determined by the parameter name (e.g. "method"). It can also be specified manually.
Available properties
| Property | Description | Default |
|----------|-------------|---------|
| Name
| The query parameter name. | The annotated parameter's name. |
| OmitEmpty
| If true
, the query parameter will be omitted for given null
values. | true
|
| EnumerateUsingMultipleParams
| If the parameter type is an IEnumerable
and this is set to true
, the query parameter name will be repeated for each entry. | true
|
Header variables
Parameter values can be sent as request headers using the [HeaderVariable]
attribute.
[HttpApi("https://httpbin.org")]
public interface MyApi {
[DeleteMapping("/delete")]
Task<HttpResponseMessage> DeleteAsyncWithHeader([HeaderVariable] string actor);
[DeleteMapping("/delete")]
Task<HttpResponseMessage> DeleteAsyncWithExplicitHeader([HeaderVariable("actor")] string myValue);
}
By default, the header name is determined by the parameter name (e.g. "method"). It can also be specified manually.
Available properties
| Property | Description | Default |
|----------|-------------|---------|
| Name
| The header name to be used. | The annotated parameter's name. |
| OmitEmpty
| If true
, the header will be omitted for given null
values. | true
|
Static header values
In order to always send a specific header, the [HeaderValue]
attribute can be applied to a method or to the entire interface.
[HttpApi("https://httpbin.org")]
[HeaderValue("Interface-Header", "interface")]
public interface MyApi {
[GetMapping("/get")]
[HeaderValue("Method-Header", "method")]
Task<HttpResponseMessage> GetAyncWithStaticHeader();
}
Raw http response
If a method's return type is HttpResponseMessage
or Task<HttpResponseMessage>
the raw HttpResponseMessage
will be returned.
[HttpApi("https://httpbin.org")]
public interface MyApi {
[GetMapping("/get")]
Task<HttpResponseMessage> GetRawResponseAsnc();
}
Note: Even though the response body will not be parsed and the entire http response body is getting returned, other response processing (e.g. checking the response status) still applies.
Async and synchronous requests
You can send both asynchronous and synchronous requests.
If a request should be performed asynchronously is determined be the method's return type (being Task<>
or Task
) only.
Aync methods do not have to end with the Async
-suffix.
[HttpApi("https://httpbin.org")]
public interface MyApi
{
[GetMapping("/get")]
Task<HttpResponseMessage> GetAsync();
[GetMapping("/get")]
HttpResponseMessage Get();
}
Void methods
Methods returning either void
or Task
, will not return anything.
Nevertheless, the response will still be received and processed (e.g. checking the response code) by the library.
A call will be blocked and a returned Task will not complete until a response is received.
[HttpApi("https://httpbin.org")]
public interface MyApi
{
[GetMapping("/get")]
Task GetAsync();
[GetMapping("/get")]
void Get();
}
Request body
Parameter values without an attribute will be serialized and sent using the request body.
public class Person {
public string? Firstname { get; set; }
public string? Lastname { get; set; }
public int Age { get; set; }
}
[HttpApi("https://httpbin.org")]
public interface MyApi
{
[PostMapping("/post")]
HttpResponseMessage Post(Person p);
[PostMapping("/post")]
Task<HttpResponseMessage> PostAsync(Person p);
}
The serialization format depends on the current configuration and can be customized using the HttpGossipBuilder
.
The default is JSON.
See also:
Response body
Gossip4Net will attempt to return an instance of the method's specified return type.
It will be deserialized using the response body.
The deserialization format depends on the current configuration and the received response headers.
It can be customized using the HttpGossipBuilder
.
public record HttpBinResponse(IDictionary<string, string> Headers, string Origin, string Url, IDictionary<string, string> Args);
[HttpApi("https://httpbin.org")]
public interface MyApi
{
[GetMapping("/get")]
Task<HttpBinResponse> GetAsync();
[GetMapping("/get")]
HttpBinResponse Get();
}
Json (de)serialization
By default, JSON is used to serialize request and response bodies.
JSON serialization is added by the JsonRequestBodyRegistration
and JsonResponseConstructorRegistration
.
You can configure the JSON serializer using one of the following extension/helper methods.
Constructing a builder and adding default behavior
IHttpGossipBuilder<MyApi> builder = new HttpGossipBuilder<MyApi>()
.AddDefaultBehavior(new JsonSerializerOptions()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
});
Creating a builder using a builder helper method
IHttpGossipBuilder<MyApi> builder = HttpGossipBuilder<MyApi>.NewDefaultBuilder(
new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
});
While using dependency injection
IServiceCollection services = new ServiceCollection();
services.AddGossipHttpClient<MyApi>(
builder => builder.AddDefaultBehavior(new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
})
);
If you don't intend to use the helper/extension methods, you can add JSON support like this:
IHttpGossipBuilder<MyApi> builder = new HttpGossipBuilder<MyApi>();
JsonSerializerOptions options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
builder.Registrations.RequestAttributes.Add(new JsonRequestBodyRegistration(options));
builder.Registrations.ResponseConstructors.Add(new JsonResponseConstructorRegistration(options));
Testing
Testing a component that relies on the API is as easy as just implementing/mocking the API interface.
Assuming your API definition, model and service are looking like these:
namespace Demo
{
public record ExampleResponse(
IDictionary<string, string> Headers,
string Origin,
string Url,
IDictionary<string, string> Args);
[HttpApi("https://httpbin.org")]
public interface IExampleApi
{
[GetMapping("/get")]
Task<ExampleResponse> Get();
}
public class ExampleService
{
private readonly IExampleApi exampleApi;
public ExampleService(IExampleApi exampleApi)
{
this.exampleApi = exampleApi;
}
public async Task<int> CountHeaders()
{
return (await exampleApi.Get()).Headers.Count;
}
}
}
Then your service test could look tike that (using Moq and FluentAssertions):
public class DemoMockTest
{
[Fact]
public async Task CountHeadersShouldReturnNumberOfReceivedHeaders()
{
// Arrange
Mock<IExampleApi> apiMock = new Mock<IExampleApi>();
apiMock.Setup(api => api.Get())
.ReturnsAsync(new ExampleResponse(
Headers: new Dictionary<string, string> { { "Content-Type", "Example" }, { "Foo", "Bar" } },
Origin: "a string",
Url: "a url",
Args: new Dictionary<string, string>()
));
IExampleApi exampleApi = apiMock.Object;
ExampleService serviceUnderTest = new ExampleService(exampleApi);
// Act
int headerCount = await serviceUnderTest.CountHeaders();
// Assert
headerCount.Should().Be(2);
}
}
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
-
net6.0
- Gossip4Net.Http (>= 0.0.2)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 6.0.0)
- Microsoft.Extensions.Http (>= 6.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.