Skip to content

Commit c945712

Browse files
authored
Update AoT, Add Roslyn 5.0, Update serialisation (#2062)
* Fix recent issue regressions, refresh examples, and document behavior * Fix example analyzer wiring for project-reference builds * Improve source-generator-first AOT guidance and add Roslyn 5 support * Add trimming annotations to public API Add System.Diagnostics trimming annotations to public API surface used via reflection: mark methods that use reflection with RequiresUnreferencedCode and annotate Type/generic parameters with DynamicallyAccessedMembers to preserve invoked members when trimming. Update approval test snapshots for .NET 9.0 and 10.0 to reflect the new attributes. * Add Newtonsoft JSON serializer tests and deps Import Newtonsoft.Json/Reflection in SerializedContentTests. Introduce several tests for NewtonsoftJsonContentSerializer: handling null content, GetFieldNameForProperty returning attribute-defined names, returning null when no JsonProperty attribute exists, and throwing on null input. Add a helper model (NewtonsoftFieldNameModel) used by the new tests. * Add XML docs and generic IRequestBuilder APIs Add XML documentation and trimming remarks throughout RequestBuilder and IRequestBuilder, and introduce a generic IRequestBuilder<T> marker interface. Expand RequestBuilder with documented ForType overloads (generic and non-generic) and add guidance about reflection, trimming/linking (RequiresUnreferencedCode and DynamicallyAccessedMembers annotations), and usage notes. Changes improve discoverability, provide trimming safety guidance, and expose typed request-builder APIs for generating strongly-typed HTTP client calls. * Add System.Text.Json content serializer tests Introduce comprehensive unit tests for SystemTextJsonContentSerializer: field name resolution, default JsonSerializerOptions behavior for untyped/object values, enum serialization/deserialization edge cases (camelCase, case-insensitive, numeric, null/empty, invalid tokens), and use of source-generated metadata with a custom resolver. Add supporting types and contexts: SystemTextFieldNameModel, ObjectValueContainer, CamelCaseEnum, and ObjectValueContainerJsonSerializerContext, plus a SystemTextJsonSerializer alias for System.Text.Json.JsonSerializer.
1 parent cfe6862 commit c945712

16 files changed

Lines changed: 969 additions & 88 deletions
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>netstandard2.0</TargetFrameworks>
5+
<AssemblyName>InterfaceStubGeneratorV3</AssemblyName>
6+
<RootNamespace>Refit.Generator</RootNamespace>
7+
<IsPackable>false</IsPackable>
8+
<AssemblyOriginatorKeyFile>..\buildtask.snk</AssemblyOriginatorKeyFile>
9+
<SignAssembly>true</SignAssembly>
10+
<IsRoslynComponent>true</IsRoslynComponent>
11+
<Nullable>enable</Nullable>
12+
<DefineConstants>$(DefineConstants);ROSLYN_4;ROSLYN_5</DefineConstants>
13+
<MsCACSharpVersion>5.0.0</MsCACSharpVersion>
14+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
15+
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
16+
</PropertyGroup>
17+
18+
<ItemGroup>
19+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="$(MsCACSharpVersion)" PrivateAssets="all" />
20+
</ItemGroup>
21+
22+
<Target Name="SetBuildVer" AfterTargets="GetBuildVersion" BeforeTargets="SetCloudBuildVersionVars;SetCloudBuildNumberWithVersion">
23+
<PropertyGroup>
24+
<Version>$(BuildVersion)</Version>
25+
<AssemblyVersion>$(BuildVersionSimple)</AssemblyVersion>
26+
</PropertyGroup>
27+
</Target>
28+
29+
<Import Project="..\InterfaceStubGenerator.Shared\InterfaceStubGenerator.Shared.projitems" Label="Shared" />
30+
31+
</Project>

README.md

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,10 +1335,9 @@ RestService.For<ISomeApi>(new HttpClient()
13351335
});
13361336
```
13371337

1338-
However, when supplying a custom `HttpClient` instance the following `RefitSettings` properties will not work:
1338+
However, when supplying a custom `HttpClient` instance, `HttpMessageHandlerFactory` will not be used because you already control the handler pipeline.
13391339

1340-
* `AuthorizationHeaderValueGetter`
1341-
* `HttpMessageHandlerFactory`
1340+
`AuthorizationHeaderValueGetter` does still work with `RestService.For<T>(httpClient, settings)` when the request includes an `Authorization` header placeholder (for example `[Headers("Authorization: Bearer")]`).
13421341

13431342
If you still want to be able to configure the `HtttpClient` instance that `Refit` provides while still making use of the above settings, simply expose the `HttpClient` on the API interface:
13441343

@@ -1365,6 +1364,38 @@ SomeApi = RestService.For<ISomeApi>("https://www.someapi.com/api/", new RefitSet
13651364
SomeApi.Client.Timeout = timeout;
13661365
```
13671366

1367+
### Native AoT / trimming guidance
1368+
1369+
Refit's recommended **source-generator-first** setup for Native AoT and trimmed applications is:
1370+
1371+
1. Use normal Refit interfaces so the Refit source generator produces the client implementation at build time.
1372+
2. Prefer `RestService.For<T>(...)` over reflection-heavy manual patterns around `Type` where possible.
1373+
3. Supply source-generated `System.Text.Json` metadata for your DTOs.
1374+
1375+
For the default `SystemTextJsonContentSerializer` on .NET 8+, Refit prefers `JsonTypeInfo` metadata from your configured `JsonSerializerOptions` when it is available. That means Native AoT apps can improve compatibility by supplying source-generated metadata through a `JsonSerializerContext` or `TypeInfoResolver` on the serializer options they pass into `SystemTextJsonContentSerializer`.
1376+
1377+
```csharp
1378+
[JsonSerializable(typeof(Todo))]
1379+
public partial class TodoJsonContext : JsonSerializerContext
1380+
{
1381+
}
1382+
1383+
var settings = new RefitSettings(
1384+
new SystemTextJsonContentSerializer(
1385+
new JsonSerializerOptions(JsonSerializerDefaults.Web)
1386+
{
1387+
TypeInfoResolver = TodoJsonContext.Default
1388+
}
1389+
)
1390+
);
1391+
1392+
var api = RestService.For<ITodoApi>("https://api.example.com", settings);
1393+
```
1394+
1395+
If a generated Refit client cannot be found at runtime, Refit now explicitly points you back to the source generator/build output and recommends generated clients plus source-generated `System.Text.Json` metadata for Native AoT scenarios.
1396+
1397+
Refit also ships analyzers for newer Roslyn toolchains, including a Roslyn 5.0 build for newer Visual Studio versions.
1398+
13681399
### Handling exceptions
13691400
Refit has different exception handling behavior depending on if your Refit interface methods return `Task<T>` or if they return `Task<IApiResponse>`, `Task<IApiResponse<T>>`, or `Task<ApiResponse<T>>`.
13701401

Refit.Tests/API/_snapshots/ApiApprovalTests.Refit.DotNet10_0.verified.txt

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,8 @@ namespace Refit
237237
}
238238
public interface IRequestBuilder
239239
{
240+
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Refit uses reflection to analyze interface methods. Ensure referenced interfaces " +
241+
"and DTOs are preserved when trimming.")]
240242
System.Func<System.Net.Http.HttpClient, object[], object?> BuildRestResultFuncForMethod(string methodName, System.Type[]? parameterTypes = null, System.Type[]? genericArgumentTypes = null);
241243
}
242244
public interface IRequestBuilder<T> : Refit.IRequestBuilder { }
@@ -405,16 +407,16 @@ namespace Refit
405407
public static class RestService
406408
{
407409
public static System.Net.Http.HttpClient CreateHttpClient(string hostUrl, Refit.RefitSettings? settings) { }
408-
public static object For(System.Type refitInterfaceType, System.Net.Http.HttpClient client) { }
409-
public static object For(System.Type refitInterfaceType, string hostUrl) { }
410-
public static object For(System.Type refitInterfaceType, System.Net.Http.HttpClient client, Refit.IRequestBuilder builder) { }
411-
public static object For(System.Type refitInterfaceType, System.Net.Http.HttpClient client, Refit.RefitSettings? settings) { }
412-
public static object For(System.Type refitInterfaceType, string hostUrl, Refit.RefitSettings? settings) { }
413-
public static T For<T>(System.Net.Http.HttpClient client) { }
414-
public static T For<T>(string hostUrl) { }
415-
public static T For<T>(System.Net.Http.HttpClient client, Refit.IRequestBuilder<T> builder) { }
416-
public static T For<T>(System.Net.Http.HttpClient client, Refit.RefitSettings? settings) { }
417-
public static T For<T>(string hostUrl, Refit.RefitSettings? settings) { }
410+
public static object For([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, System.Net.Http.HttpClient client) { }
411+
public static object For([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, string hostUrl) { }
412+
public static object For([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, System.Net.Http.HttpClient client, Refit.IRequestBuilder builder) { }
413+
public static object For([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, System.Net.Http.HttpClient client, Refit.RefitSettings? settings) { }
414+
public static object For([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, string hostUrl, Refit.RefitSettings? settings) { }
415+
public static T For<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(System.Net.Http.HttpClient client) { }
416+
public static T For<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(string hostUrl) { }
417+
public static T For<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(System.Net.Http.HttpClient client, Refit.IRequestBuilder<T> builder) { }
418+
public static T For<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(System.Net.Http.HttpClient client, Refit.RefitSettings? settings) { }
419+
public static T For<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(string hostUrl, Refit.RefitSettings? settings) { }
418420
}
419421
public class StreamPart : Refit.MultipartItem
420422
{

Refit.Tests/API/_snapshots/ApiApprovalTests.Refit.DotNet8_0.verified.txt

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ namespace Refit
236236
}
237237
public interface IRequestBuilder
238238
{
239+
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Refit uses reflection to analyze interface methods. Ensure referenced interfaces " +
240+
"and DTOs are preserved when trimming.")]
239241
System.Func<System.Net.Http.HttpClient, object[], object?> BuildRestResultFuncForMethod(string methodName, System.Type[]? parameterTypes = null, System.Type[]? genericArgumentTypes = null);
240242
}
241243
public interface IRequestBuilder<T> : Refit.IRequestBuilder { }
@@ -367,10 +369,14 @@ namespace Refit
367369
}
368370
public static class RequestBuilder
369371
{
372+
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Refit uses reflection to analyze interface methods. Ensure referenced interfaces " +
373+
"and DTOs are preserved when trimming.")]
370374
public static Refit.IRequestBuilder ForType(System.Type refitInterfaceType) { }
371-
public static Refit.IRequestBuilder ForType(System.Type refitInterfaceType, Refit.RefitSettings? settings) { }
372-
public static Refit.IRequestBuilder<T> ForType<T>() { }
373-
public static Refit.IRequestBuilder<T> ForType<T>(Refit.RefitSettings? settings) { }
375+
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Refit uses reflection to analyze interface methods. Ensure referenced interfaces " +
376+
"and DTOs are preserved when trimming.")]
377+
public static Refit.IRequestBuilder ForType([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, Refit.RefitSettings? settings) { }
378+
public static Refit.IRequestBuilder<T> ForType<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>() { }
379+
public static Refit.IRequestBuilder<T> ForType<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(Refit.RefitSettings? settings) { }
374380
}
375381
public class RestMethodInfo : System.IEquatable<Refit.RestMethodInfo>
376382
{
@@ -400,16 +406,16 @@ namespace Refit
400406
public static class RestService
401407
{
402408
public static System.Net.Http.HttpClient CreateHttpClient(string hostUrl, Refit.RefitSettings? settings) { }
403-
public static object For(System.Type refitInterfaceType, System.Net.Http.HttpClient client) { }
404-
public static object For(System.Type refitInterfaceType, string hostUrl) { }
405-
public static object For(System.Type refitInterfaceType, System.Net.Http.HttpClient client, Refit.IRequestBuilder builder) { }
406-
public static object For(System.Type refitInterfaceType, System.Net.Http.HttpClient client, Refit.RefitSettings? settings) { }
407-
public static object For(System.Type refitInterfaceType, string hostUrl, Refit.RefitSettings? settings) { }
408-
public static T For<T>(System.Net.Http.HttpClient client) { }
409-
public static T For<T>(string hostUrl) { }
410-
public static T For<T>(System.Net.Http.HttpClient client, Refit.IRequestBuilder<T> builder) { }
411-
public static T For<T>(System.Net.Http.HttpClient client, Refit.RefitSettings? settings) { }
412-
public static T For<T>(string hostUrl, Refit.RefitSettings? settings) { }
409+
public static object For([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, System.Net.Http.HttpClient client) { }
410+
public static object For([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, string hostUrl) { }
411+
public static object For([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, System.Net.Http.HttpClient client, Refit.IRequestBuilder builder) { }
412+
public static object For([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, System.Net.Http.HttpClient client, Refit.RefitSettings? settings) { }
413+
public static object For([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type refitInterfaceType, string hostUrl, Refit.RefitSettings? settings) { }
414+
public static T For<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(System.Net.Http.HttpClient client) { }
415+
public static T For<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(string hostUrl) { }
416+
public static T For<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(System.Net.Http.HttpClient client, Refit.IRequestBuilder<T> builder) { }
417+
public static T For<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(System.Net.Http.HttpClient client, Refit.RefitSettings? settings) { }
418+
public static T For<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.None | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] T>(string hostUrl, Refit.RefitSettings? settings) { }
413419
}
414420
public class StreamPart : Refit.MultipartItem
415421
{

0 commit comments

Comments
 (0)