Skip to content

Generating REST Clients for .NET Minimal / Web API APIs. #36636

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
2 tasks
rafikiassumani-msft opened this issue Sep 17, 2021 · 5 comments
Open
2 tasks

Generating REST Clients for .NET Minimal / Web API APIs. #36636

rafikiassumani-msft opened this issue Sep 17, 2021 · 5 comments
Assignees
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc feature-minimal-actions Controller-like actions for endpoint routing feature-openapi investigate Priority:1 Work that is critical for the release, but we could probably ship without
Milestone

Comments

@rafikiassumani-msft
Copy link
Contributor

rafikiassumani-msft commented Sep 17, 2021

Is your feature request related to a problem? Please describe.

Investigate how retrofit or refit can be used for REST Client generations with dotnet-api tooling.

Describe the solution you'd like

Retrofit or refit are great libraries for generating REST clients for mostly android apps. They allow you to define an interface containing a set of endpoints that your application will be interacting with, and then you can generate the implementation code. We would like to investigate the possibility of using retrofit/refit/or something new for generating REST Client implementations with dotnet-api tooling.

Additional context

@rafikiassumani-msft rafikiassumani-msft added this to the .NET 7 Planning milestone Sep 17, 2021
@rafikiassumani-msft rafikiassumani-msft added old-area-web-frameworks-do-not-use *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels feature-openapi labels Sep 17, 2021
@davidfowl
Copy link
Member

@bradygaster is interested in this as well

@rafikiassumani-msft rafikiassumani-msft changed the title Generating REST Clients for .NET APIs. Generating REST Clients for .NET Minimal / Web API APIs. Sep 17, 2021
@rafikiassumani-msft
Copy link
Contributor Author

@davidfowl I just assigned it to him. We had a conversation on this.

@rafikiassumani-msft rafikiassumani-msft added the feature-minimal-actions Controller-like actions for endpoint routing label Jan 4, 2022
@rafikiassumani-msft rafikiassumani-msft added Priority:1 Work that is critical for the release, but we could probably ship without Cost:XL labels Jan 25, 2022
@WhatzGames
Copy link
Contributor

Just putting down some thoughts that came to mind today.

Would't it be possible to use a Source generator for this case?

Using Attributes in a way of something like this:

//Example partially taken from reactiveui/refit
public interface IGithubApi
{
    [Get("/users")]
    [Response(ResponseType.Xml)] //Using XmlDeserializer on this endpoint
    public IAsyncEnumerable<User> GetUserAsync();
    
    [Get("/users")]
    [Response(ResponseType.Json)] //Using JsonDeserializer
    public IAsyncEnumerable<User> GetUsersAsync(CancellationToken cancellationToken);

    [Get("/users/{user}")]
    public Task<User> GetUserAsync(string user, CancellationToken cancellationToken);
    
    
    [Get("/users/{user}")]
    public Stream GetUserStreamAsync(string user, CancellationToken cancellationToken);
}

[Apiclient<IGithubApi>] //[ApiClient(typeof(IGithubClient))]
public partial class GithubApi
{
    
}

which would result in something like this:

public partial class GithubApi : IGithubApi
{
    private readonly HttpClient _client;

    public GithubApi(HttpClient client)
    {
        _client = client;
    }

    public async IAsyncEnumerable<User> GetUsersAsync()
    {
        using var responseMessage = await _client.GetAsync("/users");
        if (!responseMessage.IsSuccessStatusCode)
        {
            //something doing here?
        }
        await using var stream = await responseMessage.Content.ReadAsStreamAsync();
        var reader = XmlReader.Create(stream);
        var serializer = new XmlSerializer(typeof(User[]));
        if (!serializer.CanDeserialize(reader))
        {
            throw new InvalidOperationException("Not Deserializable");
        }

        var entries =  (User[])serializer.Deserialize(reader);
        if (entries == null) yield break;
        
        foreach (var entry in entries)
        {
            yield return entry;
        }
    }
    
    public async IAsyncEnumerable<User> GetUsersAsync([EnumeratorCancellation]CancellationToken cancellationToken)
    {
        using var responseMessage = await _client.GetAsync("/users", cancellationToken);
        if (!responseMessage.IsSuccessStatusCode)
        {
            //something doing here?
        }
        await using var stream = await responseMessage.Content.ReadAsStreamAsync(cancellationToken);
        var entries = JsonSerializer.DeserializeAsyncEnumerable<User>(stream, JsonSerializerOptions.Default, cancellationToken);
        await foreach (var entry in entries.WithCancellation(cancellationToken))
        {
            yield return entry;
        }
    }

    public Task<User> GetUserAsync(string user, CancellationToken cancellationToken)
    {
        
    }
    
    
    public async Task<Stream> GetUserStreamAsync(string user, CancellationToken cancellationToken)
    {
        using var responseMessage = await _client.GetAsync($"/users/{user}", cancellationToken);
        if (!responseMessage.IsSuccessStatusCode)
        {
            //something doing here?
        }
        return await responseMessage.Content.ReadAsStreamAsync(cancellationToken);
    }

    public async Task<string> GetUserStringAsync(string user, CancellationToken cancellationToken)
    {
        using var responseMessage = await _client.GetAsync($"/users/{user}", cancellationToken);
        if (!responseMessage.IsSuccessStatusCode)
        {
            //something doing here?
        }
        return stream = await responseMessage.Content.ReadAsStringAsync(cancellationToken);
    }
}

It's just something to show what I had in mind and maybe it's something worth thinking about?

I was also thinking of a way to proccess different StatusCodes, and thought of the TypedResults.NotFound, Ok etc....
When using Results<> With the corresponding Typed parameters as returnparameter, then based on that the StatusCode could be filtered out when processing the responsemessage and returned, but I haven't thought of it any further.

@davidfowl
Copy link
Member

See https://github.com/reactiveui/refit

@bradygaster
Copy link
Member

I love the idea of a source generator that outputs Refit clients. I spoke with @clairernovotny about this a few times. My goal would be to have a Refit provider for the REST API client generation feature in VS Connected Services. So, cc @vijayrkn, with whom I've discussed this idea a few times and seemed receptive to it.

@amcasey amcasey added the area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc label Jun 2, 2023
@captainsafia captainsafia removed the old-area-web-frameworks-do-not-use *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels label Jun 6, 2023
@captainsafia captainsafia modified the milestones: .NET 8 Planning, Backlog Mar 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc feature-minimal-actions Controller-like actions for endpoint routing feature-openapi investigate Priority:1 Work that is critical for the release, but we could probably ship without
Projects
None yet
Development

No branches or pull requests

6 participants