Gossip4Net.Http.DependencyInjection 0.0.2

There is a newer version of this package available.
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                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Gossip4Net.Http.DependencyInjection" Version="0.0.2" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Gossip4Net.Http.DependencyInjection --version 0.0.2                
#r "nuget: Gossip4Net.Http.DependencyInjection, 0.0.2"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// 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.

  1. Getting started
    1. Get Gossip4Net
    2. Define your API contract and models
    3. Obtain and configure a HttpGossipBuilder
    4. Let Gossip4Net implement your API interface
    5. Use your API
  2. Feaures
  3. Testing

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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.0.4 389 10/6/2022
0.0.3 378 10/5/2022
0.0.2 406 10/5/2022
0.0.1 110 10/4/2022