Skip to content

Commit 486d2fd

Browse files
HTTP AutoClient NuGet package content (#36970)
* Auto rest content * Added to the TOC * Fix warnings * Now the xref is better * Minor clean up * More updateS * Update docs/fundamentals/networking/http/http-autoclient.md Co-authored-by: Bill Wagner <[email protected]> * Use primary ctor * Clean up * Major clean up and edit pass * Rename link to article * Apply suggestions from code review Co-authored-by: Bill Wagner <[email protected]> * Snippets * Remove un-used usings * Remove incorrect alert * touch csproj * Adjust heading * Add note about experimental API --------- Co-authored-by: Bill Wagner <[email protected]>
1 parent b90dd04 commit 486d2fd

21 files changed

+427
-0
lines changed
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
---
2+
title: Use the REST API HTTP client generator
3+
description: Learn how to generate HttpClient-dependent code implementations of AutoClient decorated interfaces.
4+
author: IEvangelist
5+
ms.author: dapine
6+
ms.date: 09/29/2023
7+
---
8+
9+
# Use the REST API HTTP client generator
10+
11+
> [!NOTE]
12+
> This API is experimental. It might change in subsequent versions of the library, and backwards compatibility is not guaranteed.
13+
14+
The <xref:System.Net.Http.HttpClient> is a great way to consume REST APIs, but it's not without its challenges. One of the challenges is the amount of boilerplate code you need to write to consume the API. In this article, you learn how to use the [Microsoft.Extensions.Http.AutoClient](https://www.nuget.org/packages/Microsoft.Extensions.Http.AutoClient) NuGet package to decorate an interface and generate an HTTP client dependency. The AutoClient's underlying source generator generates the implementation of your interface, along with extension methods to register it into the dependency injection container. Additionally, the AutoClient generates telemetry for each HTTP request, which is sent with <xref:Microsoft.Extensions.Http.Telemetry>.
15+
16+
## Use the `AutoClientAttribute`
17+
18+
The <xref:Microsoft.Extensions.Http.AutoClient.AutoClientAttribute> is responsible for triggering the AutoClient generator to emit the corresponding implementation of the decorated interface. It accepts the `httpClientName` of the <xref:System.Net.Http.HttpClient> to be retrieved from the <xref:System.Net.Http.IHttpClientFactory>. Consider the following interface definition:
19+
20+
:::code source="snippets/autoclient/IProductClient.cs":::
21+
22+
> [!TIP]
23+
> The interface name must start with an `I`. The name is stripped of the leading `I`, and used as the <xref:Microsoft.Extensions.Http.Telemetry.RequestMetadata.DependencyName?displayProperty=nameWithType> for telemetry's <xref:Microsoft.Extensions.Http.Telemetry.RequestMetadata>. If the name ends in `Api` or `Client`, those are excluded. For example, if the interface is named `IProductClient`, the dependency name is `Product`.
24+
25+
To override the calculated dependency name, use the `customDependencyName` parameter of the <xref:Microsoft.Extensions.Http.AutoClient.AutoClientAttribute>.
26+
27+
:::code source="snippets/autoclient/IWidgetClient.cs":::
28+
29+
> [!NOTE]
30+
> Both preceding code examples result in a compilation warning, as the interface doesn't define any HTTP methods. The warning is emitted by the AutoClient generator, and it's a reminder that the emitted implementations are be useless.
31+
32+
## Define HTTP methods with verb attributes
33+
34+
An empty interface isn't a useful abstraction. To define HTTP methods, you must use the HTTP verb attributes. Each HTTP method returns a <xref:System.Threading.Tasks.Task%601> where `T` is any of the following types:
35+
36+
| Return type | Description |
37+
|--|--|
38+
| `Task<string>` | The raw content of the response is returned as a string. |
39+
| `Task<T>` | When `T` is any serializable type, the response content is deserialized from JSON and returned. |
40+
| `Task<HttpResponseMessage>` | If you need the <xref:System.Net.Http.HttpResponseMessage> itself, as returned from <xref:System.Net.Http.HttpClient.SendAsync%2A?displayProperty=nameWithType>, use this type. |
41+
42+
When the content type of the HTTP response isn't `application/json` and the method's return type isn't `Task<string>`, an exception is thrown.
43+
44+
### HTTP verb attributes
45+
46+
An HTTP method is defined using one of the following attributes:
47+
48+
- <xref:Microsoft.Extensions.Http.AutoClient.GetAttribute?displayProperty=fullName>
49+
- <xref:Microsoft.Extensions.Http.AutoClient.PostAttribute?displayProperty=fullName>
50+
- <xref:Microsoft.Extensions.Http.AutoClient.PutAttribute?displayProperty=fullName>
51+
- <xref:Microsoft.Extensions.Http.AutoClient.PatchAttribute?displayProperty=fullName>
52+
- <xref:Microsoft.Extensions.Http.AutoClient.DeleteAttribute?displayProperty=fullName>
53+
- <xref:Microsoft.Extensions.Http.AutoClient.HeadAttribute?displayProperty=fullName>
54+
- <xref:Microsoft.Extensions.Http.AutoClient.OptionsAttribute?displayProperty=fullName>
55+
56+
Each attribute requires a `path` argument that routes to the underlying REST API, and it should be relative to the <xref:System.Net.Http.HttpClient.BaseAddress?displayProperty=nameWithType>. The `path` can't contain query string parameters, instead the <xref:Microsoft.Extensions.Http.AutoClient.QueryAttribute> is used. From the perspective of telemetry, the `path` is used as the <xref:Microsoft.Extensions.Http.Telemetry.RequestMetadata.RequestRoute?displayProperty=nameWithType>.
57+
58+
HTTP methods decorated with any of the verb attributes must have a <xref:System.Threading.CancellationToken> parameter and it should be the last parameter defined. The `CancellationToken` parameter is used to cancel the HTTP request.
59+
60+
:::code source="snippets/autoclient/IUserClient.cs":::
61+
62+
The preceding code:
63+
64+
- Defines an HTTP method using the <xref:Microsoft.Extensions.Http.AutoClient.GetAttribute>.
65+
- The `path` is `/api/users`.
66+
- The method returns a <xref:System.Threading.Tasks.Task%601> where `T` is `User[]`.
67+
- The method accepts an optional <xref:System.Threading.CancellationToken> parameter that is assigned to `default` when an argument isn't provided.
68+
69+
### Route parameters
70+
71+
The URL may contain route parameters, for example, `"/api/users/{userId}"`. To define a route parameter the method must also accept a parameter with the same name, in this case, `userId`:
72+
73+
:::code source="snippets/autoclient/IRouteParameterUserClient.cs":::
74+
75+
In the preceding code:
76+
77+
- The `GetUserAsync` method has a route parameter named `userId`.
78+
- The `userId` parameter is used in the `path` of the request, replacing the `{userId}` placeholder.
79+
80+
### Telemetry request name
81+
82+
The method name is used as the <xref:Microsoft.Extensions.Http.Telemetry.RequestMetadata.RequestName?displayProperty=nameWithType>. If the method name includes the `Async` suffix, it's removed. For example, a method named `GetUsersAsync` is calculated as `"GetUsers"`.
83+
84+
To override the name, use the `RequestName` property of each attribute of the [HTTP verb attributes](#http-verb-attributes).
85+
86+
:::code source="snippets/autoclient/IRequestNameUserClient.cs":::
87+
88+
## HTTP payloads
89+
90+
To send an HTTP payload with your request, use the <xref:Microsoft.Extensions.Http.AutoClient.BodyAttribute> on a method's parameter. If you don't pass any parameter to it, it treats the content type as JSON, serializing your parameter before sending. Otherwise, you define an explicit
91+
<xref:Microsoft.Extensions.Http.AutoClient.BodyContentType> and use it within the <xref:Microsoft.Extensions.Http.AutoClient.BodyAttribute>.
92+
93+
:::code source="snippets/autoclient/IPayloadUserClient.cs":::
94+
95+
## HTTP headers
96+
97+
There are two ways of sending headers with your HTTP request. One of them is best suited for headers that never change value (static headers). The other way is headers that change based on the parameters of your methods.
98+
99+
### Static headers
100+
101+
To define a static header, use the <xref:Microsoft.Extensions.Http.AutoClient.StaticHeaderAttribute> on your interface definition. Pass the header name and value to its constructor.
102+
103+
You can also use more than one <xref:Microsoft.Extensions.Http.AutoClient.StaticHeaderAttribute> together and in methods as well. When the `StaticHeader` attribute is used on a method, that HTTP header is sent for that method only, whereas the interface-level `StaticHeader` attribute is sent for all methods.
104+
105+
:::code source="snippets/autoclient/IStaticHeaderUserClient.cs":::
106+
107+
### Parameter headers
108+
109+
Use the <xref:Microsoft.Extensions.Http.AutoClient.HeaderAttribute> to define parameter-based headers, where you can receive the value for a header from the attributes of your method. Pass the header name to its constructor.
110+
111+
The parameter may be of any type. When the header type is anything other than a `string`, the `.ToString()` method is called on the value of the parameter.
112+
113+
:::code source="snippets/autoclient/IParameterHeaderUserClient.cs":::
114+
115+
## Query parameters
116+
117+
Query parameters are defined using the <xref:Microsoft.Extensions.Http.AutoClient.QueryAttribute> on a method's parameter. All types are valid, and the query value relies on the `.ToString()` method to get the value of the parameter when not a `string` type.
118+
119+
The <xref:Microsoft.Extensions.Http.AutoClient.QueryAttribute.Key?displayProperty=nameWithType> is assigned from the name of the parameter.
120+
121+
:::code source="snippets/autoclient/IQueryUserClient.cs":::
122+
123+
The `GetUsersAsync` method generates an HTTP request with a URL formatted as `/api/users?search={search}`. This format is used as the <xref:Microsoft.Extensions.Http.Telemetry.RequestMetadata.RequestRoute?displayProperty=nameWithType> for telemetry.
124+
125+
If you need to change the query key, you may call the `key` parameter-based constructor, <xref:Microsoft.Extensions.Http.AutoClient.QueryAttribute.%23ctor(System.String)>.
126+
127+
:::code source="snippets/autoclient/ICustomQueryUserClient.cs":::
128+
129+
The `GetUsersAsync` method generates an HTTP request with a URL formatted like `/api/users?customQueryKey={customQueryKey}`, as the key name was overridden to `customQueryKey`.
130+
131+
## Dependency injection hooks
132+
133+
Along with the interface's implementation, extension methods are generated to register the client in the dependency injection container. The name of the generated extension method is the same as your interface name, replacing the leading `I` with `Add`.
134+
135+
For example, consider the following interface definition:
136+
137+
:::code source="snippets/autoclient/ICompleteUserClient.cs":::
138+
139+
While the generator emits the implementation of the `ICompleteUserClient` interface, it also generates the `AddCompleteUserClient` extension method on the `IServiceCollection`. Consider the following example _Program.cs_ code:
140+
141+
:::code source="snippets/autoclient/Program.cs" id="program":::
142+
143+
In the preceding example code:
144+
145+
- The <xref:Microsoft.Extensions.Hosting.Host.CreateEmptyApplicationBuilder%2A?displayProperty=nameWithType> is used to create a <xref:Microsoft.Extensions.Hosting.HostApplicationBuilder>.
146+
- The <xref:Microsoft.Extensions.DependencyInjection.IServiceCollection> is retrieved from the <xref:Microsoft.Extensions.Hosting.HostApplicationBuilder.Services?displayProperty=nameWithType> property, to call the <xref:Microsoft.Extensions.DependencyInjection.HttpClientFactoryServiceCollectionExtensions.AddHttpClient(Microsoft.Extensions.DependencyInjection.IServiceCollection,System.String,System.Action{System.Net.Http.HttpClient})> extension method.
147+
- The `AddHttpClient` extension method is called with the name of the <xref:System.Net.Http.HttpClient> to be registered, and a delegate to configure the <xref:System.Net.Http.HttpClient> instance.
148+
- The `AddCompleteUserClient` extension method is called to register the `ICompleteUserClient` interface and its implementation.
149+
150+
You can consume the client by injecting it into your service's constructor:
151+
152+
:::code source="snippets/autoclient/UserService.cs":::
153+
154+
For more information, see [.NET dependency injection](../../../core/extensions/dependency-injection.md).
155+
156+
The application is expected to output the following:
157+
158+
:::code source="snippets/autoclient/Program.cs" id="output":::

docs/fundamentals/networking/http/httpclient.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,4 +377,5 @@ For more information about configuring a proxy, see:
377377
- [Guidelines for using HttpClient](httpclient-guidelines.md)
378378
- [IHttpClientFactory with .NET](../../../core/extensions/httpclient-factory.md)
379379
- [Use HTTP/3 with HttpClient](../../../core/extensions/httpclient-http3.md)
380+
- [REST API HTTP client generator](http-autoclient.md)
380381
- [Test web APIs with the HttpRepl](/aspnet/core/web-api/http-repl)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
public sealed record class Address(
2+
string Street,
3+
string? Suite,
4+
string City,
5+
string ZipCode,
6+
Geo Geo);
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
public sealed record class Company(
2+
string Name,
3+
string CatchPhrase,
4+
string Bs);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
public sealed record class Geo(decimal Lat, decimal Lng);
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using Microsoft.Extensions.Http.AutoClient;
2+
3+
[AutoClient(nameof(ICompleteUserClient))]
4+
[StaticHeader("User-Agent", "dotnet-auto-client sample")]
5+
public interface ICompleteUserClient
6+
{
7+
[Get("users")]
8+
public Task<User[]> GetAllUsersAsync(
9+
CancellationToken cancellationToken = default);
10+
11+
[Get("users")]
12+
public Task<User[]> GetUserByNameAsync(
13+
[Query] string name,
14+
CancellationToken cancellationToken = default);
15+
16+
[Get("users/{userId}")]
17+
public Task<User> GetUserByIdAsync(
18+
int userId,
19+
CancellationToken cancellationToken = default);
20+
21+
[Post("users")]
22+
[StaticHeader("X-CustomHeader", "custom-value")]
23+
public Task<HttpResponseMessage> CreateUserAsync(
24+
[Body(BodyContentType.ApplicationJson)] User user,
25+
CancellationToken cancellationToken = default);
26+
27+
[Delete("user/{userId}")]
28+
public Task<User> DeleteUserAsync(
29+
int userId,
30+
[Header("If-None-Match")] string eTag,
31+
CancellationToken cancellationToken = default);
32+
}
33+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using Microsoft.Extensions.Http.AutoClient;
2+
3+
[AutoClient(nameof(ICustomQueryUserClient))]
4+
public interface ICustomQueryUserClient
5+
{
6+
[Get("/api/users")]
7+
public Task<List<User>> GetUsersAsync(
8+
[Query("customQueryKey")] string search,
9+
CancellationToken cancellationToken = default);
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using Microsoft.Extensions.Http.AutoClient;
2+
3+
[AutoClient(nameof(IParameterHeaderUserClient), "User Service")]
4+
public interface IParameterHeaderUserClient
5+
{
6+
[Get("/api/users")]
7+
public Task<List<User>> GetUsersAsync(
8+
[Header("X-MyHeader")] string myHeader,
9+
CancellationToken cancellationToken = default);
10+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using Microsoft.Extensions.Http.AutoClient;
2+
3+
[AutoClient(nameof(IPayloadUserClient), "User Service")]
4+
public interface IPayloadUserClient
5+
{
6+
[Post("/api/users")]
7+
public Task<User> CreateUserAsync(
8+
// The content type is JSON
9+
// The parameter is serialized before sending
10+
[Body] User user,
11+
CancellationToken cancellationToken = default);
12+
13+
[Put("/api/users/{userId}/displayName")]
14+
public Task<User> UpdateDisplayNameAsync(
15+
string userId,
16+
// The content type is text/plain
17+
// The parameter is sent as is
18+
[Body(BodyContentType.TextPlain)] string displayName,
19+
CancellationToken cancellationToken = default);
20+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
using Microsoft.Extensions.Http.AutoClient;
2+
3+
[AutoClient(httpClientName: "GeneratedClient")]
4+
public interface IProductClient
5+
{
6+
}

0 commit comments

Comments
 (0)