RESTween 1.0.27

dotnet add package RESTween --version 1.0.27
                    
NuGet\Install-Package RESTween -Version 1.0.27
                    
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="RESTween" Version="1.0.27" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="RESTween" Version="1.0.27" />
                    
Directory.Packages.props
<PackageReference Include="RESTween" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add RESTween --version 1.0.27
                    
#r "nuget: RESTween, 1.0.27"
                    
#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.
#:package RESTween@1.0.27
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=RESTween&version=1.0.27
                    
Install as a Cake Addin
#tool nuget:?package=RESTween&version=1.0.27
                    
Install as a Cake Tool

RESTween

RESTween is a library for dynamically invoking REST APIs using proxy interfaces, leveraging Castle DynamicProxy. It allows for the easy creation of REST API clients with minimal code.

Features

  • Easy creation of REST API clients using proxies.
  • Support for attributes to define the type of HTTP request (GET, POST, PUT, DELETE).
  • Integration with any HttpClient for executing requests.
  • Support for request and response handling through the IRequestHandler interface.

Installation

To install RESTween, use the NuGet Package Manager or .NET CLI:

dotnet add package RESTween

Usage Example

Define an Interface

First, define an interface for your REST API using attributes that correspond to the HTTP request type:

public interface IMyApi
{
    [Get("/users/{id}")]
    Task<User> GetUserAsync(int id);

    [Post("/users")]
    Task CreateUserAsync([Body] User user);

    [Put("/users/{id}")]
    Task UpdateUserAsync(int id, [Body] User user);

    [Delete("/users/{id}")]
    Task DeleteUserAsync(int id);
}

Register the API Client

Next, register this API client in your dependency injection container:

services.AddApiClient<IMyApi>(new Uri("https://api.example.com"));

Use the API Client

Now, you can use IMyApi in your services:

public class UserService
{
    private readonly IMyApi _api;

    public UserService(IMyApi api)
    {
        _api = api;
    }

    public async Task<User> GetUser(int id)
    {
        return await _api.GetUserAsync(id);
    }
}

IRequestHandler Interface

IRequestHandler is an interface responsible for handling HTTP requests and responses. You can implement your own handler to customize the behavior of the HTTP client, or you can use the built-in DefaultRequestHandler:

public interface IRequestHandler
{
    Task<T> HandleRequestAsync<T>(HttpRequestMessage request, HttpClient httpClient);
    Task HandleRequestAsync(HttpRequestMessage request, HttpClient httpClient);
}

Example of Custom IRequestHandler Implementation

Here�s an example of a custom IRequestHandler implementation that includes authentication, loading indicators, error handling, and logging of request time:

public class HttpHandler : IRequestHandler
{
    private readonly INavigationService navigationService;
    private readonly ILoadingService loadingService;
    private readonly IAlertService alertService;
    private readonly ILocalStorageService localStorageService;
    private Stopwatch stopwatch;

    public HttpHandler(INavigationService navigationService, ILoadingService loadingService, IAlertService alertService, ILocalStorageService localStorageService)
    {
        this.navigationService = navigationService;
        this.loadingService = loadingService;
        this.alertService = alertService;
        this.localStorageService = localStorageService;
        stopwatch = new Stopwatch();
    }

    public async Task<T> HandleRequestAsync<T>(HttpRequestMessage request, HttpClient httpClient)
    {
        stopwatch.Restart();
        try
        {
            loadingService.Show();
            var token = await localStorageService.GetAuthToken();
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
            var response = await httpClient.SendAsync(request);
            var content = await response.Content.ReadAsStringAsync();
            stopwatch.Stop();
            Console.WriteLine($"Sent request {request.RequestUri} with time: {stopwatch.ElapsedMilliseconds}ms");

            if (!response.IsSuccessStatusCode)
            {
                await HandleFailedResponse(response);
                return default;
            }

            if (response.StatusCode == HttpStatusCode.NoContent)
            {
                return default;
            }

            if (typeof(T) == typeof(string))
            {
                return (T)(object)content;
            }
            else if (typeof(T).IsPrimitive || typeof(T) == typeof(decimal))
            {
                return (T)Convert.ChangeType(content, typeof(T));
            }

            return JsonSerializer.Deserialize<T>(content) ?? default;
        }
        catch (Exception e)
        {
#if DEBUG
            alertService.ShowError($"{e.Message} {request.RequestUri?.ToString()}");
#else
            alertService.ShowError("Can't connect to server");
#endif
            return default;
        }
        finally
        {
            loadingService.Hide();
        }
    }

    public async Task HandleRequestAsync(HttpRequestMessage request, HttpClient httpClient)
    {
        stopwatch.Restart();
        try
        {
            loadingService.Show();
            var token = await localStorageService.GetAuthToken();
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
            var response = await httpClient.SendAsync(request);
            stopwatch.Stop();
            Console.WriteLine($"Sent request {request.RequestUri} with time: {stopwatch.ElapsedMilliseconds}ms");

            if (!response.IsSuccessStatusCode)
            {
                await HandleFailedResponse(response);
            }
        }
        catch (Exception e)
        {
#if DEBUG
            alertService.ShowError($"{e.Message} {request.RequestUri?.ToString()}");
#else
            alertService.ShowError("Can't connect to server");
#endif
        }
        finally
        {
            loadingService.Hide();
        }
    }

    private async Task HandleFailedResponse(HttpResponseMessage response)
    {
        if (response.IsSuccessStatusCode) return;
        var content = await response.Content.ReadAsStringAsync();
        if (response.StatusCode == HttpStatusCode.Unauthorized)
        {
            await localStorageService.RemoveAuthToken();
            navigationService.NavigateToLogin();
            return;
        }
        var errorResult = JsonSerializer.Deserialize<ErrorRESM>(content);
        if (errorResult == null) return;
#if DEBUG
        alertService.ShowError(errorResult.DebugMessage);
#else
        alertService.ShowError(errorResult.ReleaseMessage);
#endif
    }
}

Integrating Custom IRequestHandler

To make your custom IRequestHandler work with RESTween, you need to register it in your dependency injection container:

public void ConfigureServices(IServiceCollection services)
{
    // Register your custom IRequestHandler
    services.AddScoped<IRequestHandler, HttpHandler>();

    // Register your API client
    services.AddApiClient<IMyApi>(new Uri("https://api.example.com"));

    // Register other services used in HttpHandler
    services.AddScoped<INavigationService, NavigationService>();
    services.AddScoped<ILoadingService, LoadingService>();
    services.AddScoped<IAlertService, AlertService>();
    services.AddScoped<ILocalStorageService, LocalStorageService>();
}

After this setup, RESTween will use your HttpHandler to handle all requests sent through API clients created with RESTween.

Attributes

RESTween supports several custom attributes for defining the type of HTTP request and parameters:

  • GetAttribute: Specifies that the method performs an HTTP GET request.

    [Get("/users/{id}")]
    
  • PostAttribute: Specifies that the method performs an HTTP POST request.

    [Post("/users")]
    
  • PutAttribute: Specifies that the method performs an HTTP PUT request.

    [Put("/users/{id}")]
    
  • DeleteAttribute: Specifies that the method performs an HTTP DELETE request.

    [Delete("/users/{id}")]
    
  • QueryAttribute: Used to mark query parameters in a method.

    [Get("/search")]
    Task SearchAsync([Query("term")] string searchTerm);
    
  • BodyAttribute: Indicates that the method parameter should be serialized and sent in the request body.

    Task CreateUserAsync([Body] User user);
    

Conclusion

RESTween provides a convenient and flexible way to create clients for REST APIs, minimizing the amount of necessary code and simplifying the integration process. The use of attributes and the ability to customize request handling make this package a valuable tool for developers working with REST APIs in .NET.

Product Compatible and additional computed target framework versions.
.NET net5.0 is compatible.  net5.0-windows was computed.  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 is compatible.  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 is compatible.  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.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.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
1.0.27 195 4/2/2025
1.0.26 167 4/2/2025
1.0.25 157 4/2/2025
1.0.24 123 3/21/2025
1.0.23 127 3/21/2025
1.0.22 143 3/21/2025
1.0.21 110 3/21/2025
1.0.20 127 2/3/2025
1.0.19 124 2/3/2025