Skip to content

Commit

Permalink
feature: Make the RestMethodInfo available in the request options (#…
Browse files Browse the repository at this point in the history
…1317)

* Make the `RestMethodInfo` available in the request options

While having the interface type available is nice, it might not be enough if using reflection on the called method is desired. Providing the `RestMethodInfo` opens new possibilities to introspect the called method.

Currently, to workaround this missing information, I'm using a `methodName` parameter decorated with a `[Property]` attribute and a default value to make it possible to introspect the return type of the called method.

At the interface method:

```csharp
Task<ApiResponse<User>> GetUserAsync(string id, [Property] string methodName = nameof(GetUserAsync));
```

Inside the HTTP message handler:

```csharp
request.Options.TryGetValue(new HttpRequestOptionsKey<Type>(HttpRequestMessageOptions.InterfaceType), out var refitInterfaceType)
request.Options.TryGetValue(new HttpRequestOptionsKey<string>("methodName"), out var methodName)
var methodReturnType = refitInterfaceType.GetMethod(methodName).ReturnType;
```

With the new `RestMethodInfo`, it becomes possible to access the method without having to pollute the interface definition:

```csharp
request.Options.TryGetValue(HttpRequestMessageOptions.RestMethodInfoKey, out var restMethodInfo);
var methodReturnType = restMethodInfo.MethodInfo.ReturnType;
```

Also, the new `HttpRequestMessageOptions.InterfaceTypeKey` and  `HttpRequestMessageOptions.RestMethodInfoKey` (available on .NET 5 onwards) make it easier to access the request options.

* Update and merge from Main

Add Net 7 testing

* Ensure all targets are net 6 for benchmark

---------

Co-authored-by: Chris Pulman <chris.pulman@yahoo.com>
  • Loading branch information
0xced and ChrisPulman authored Apr 12, 2023
1 parent ed61774 commit c0af5c2
Show file tree
Hide file tree
Showing 18 changed files with 131 additions and 72 deletions.
32 changes: 29 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ services
* [Removing headers](#removing-headers)
* [Passing state into DelegatingHandlers](#passing-state-into-delegatinghandlers)
* [Support for Polly and Polly.Context](#support-for-polly-and-pollycontext)
* [Target Interface type](#target-interface-type)
* [Target Interface type and method info](#target-interface-type-and-method-info)
* [MethodInfo of the method on the Refit client interface that was invoked](#methodinfo-of-the-method-on-the-refit-client-interface-that-was-invoked)
* [Multipart uploads](#multipart-uploads)
* [Retrieving the response](#retrieving-the-response)
Expand Down Expand Up @@ -811,7 +811,7 @@ Because Refit supports `HttpClientFactory` it is possible to configure Polly pol
If your policy makes use of `Polly.Context` this can be passed via Refit by adding `[Property("PolicyExecutionContext")] Polly.Context context`
as behind the scenes `Polly.Context` is simply stored in `HttpRequestMessage.Properties` under the key `PolicyExecutionContext` and is of type `Polly.Context`. It's only recommended to pass the `Polly.Context` this way if your use case requires that the `Polly.Context` be initialized with dynamic content only known at runtime. If your `Polly.Context` only requires the same content every time (e.g an `ILogger` that you want to use to log from inside your policies) a cleaner approach is to inject the `Polly.Context` via a `DelegatingHandler` as described in [#801](https://github.com/reactiveui/refit/issues/801#issuecomment-1137318526)

#### Target Interface Type
#### Target Interface Type and method info

There may be times when you want to know what the target interface type is of the Refit instance. An example is where you
have a derived interface that implements a common base like this:
Expand Down Expand Up @@ -857,7 +857,33 @@ class RequestPropertyHandler : DelegatingHandler
```
[//]: # ({% endraw %})

Note: in .NET 5 `HttpRequestMessage.Properties` has been marked `Obsolete` and Refit will instead populate the value into the new `HttpRequestMessage.Options`.
The full method information (`RestMethodInfo`) is also always available in the request options. The `RestMethodInfo` contains more information about the method being called such as the full `MethodInfo` when using reflection is needed:

[//]: # ({% raw %})
```csharp
class RequestPropertyHandler : DelegatingHandler
{
public RequestPropertyHandler(HttpMessageHandler innerHandler = null) : base(innerHandler ?? new HttpClientHandler()) {}

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// Get the method info
if (request.Options.TryGetValue(HttpRequestMessageOptions.RestMethodInfoKey, out RestMethodInfo restMethodInfo))
{
var builder = new UriBuilder(request.RequestUri);
// Alter the Path in some way based on the method info or an attribute on it
builder.Path = $"/{restMethodInfo.MethodInfo.Name}{builder.Path}";
// Set the new Uri on the outgoing message
request.RequestUri = builder.Uri;
}

return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
}
}
```
[//]: # ({% endraw %})

Note: in .NET 5 `HttpRequestMessage.Properties` has been marked `Obsolete` and Refit will instead populate the value into the new `HttpRequestMessage.Options`. Refit provides `HttpRequestMessageOptions.InterfaceTypeKey` and `HttpRequestMessageOptions.RestMethodInfoKey` to respectively access the interface type and REST method info from the options.

#### MethodInfo of the method on the Refit client interface that was invoked

Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
dotnet run --project ../../Refit.Benchmarks.csproj -c Release --framework=net5.0
dotnet run --project ../../Refit.Benchmarks.csproj -c Release --framework=net6.0
Original file line number Diff line number Diff line change
@@ -1 +1 @@
dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *ObservableHttpResponseMessage* --framework=net5.0
dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *ObservableHttpResponseMessage* --framework=net6.0
2 changes: 1 addition & 1 deletion Refit.Benchmarks/Benchmarks/net5.0/Benchmark_Task.bat
Original file line number Diff line number Diff line change
@@ -1 +1 @@
dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *Task_Async* --framework=net5.0
dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *Task_Async* --framework=net6.0
Original file line number Diff line number Diff line change
@@ -1 +1 @@
dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskApiResponseT_Async* --framework=net5.0
dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskApiResponseT_Async* --framework=net6.0
Original file line number Diff line number Diff line change
@@ -1 +1 @@
dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskHttpContent_Async* --framework=net5.0
dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskHttpContent_Async* --framework=net6.0
Original file line number Diff line number Diff line change
@@ -1 +1 @@
dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskHttpResponseMessage_Async* --framework=net5.0
dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskHttpResponseMessage_Async* --framework=net6.0
Original file line number Diff line number Diff line change
@@ -1 +1 @@
dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskStream_Async* --framework=net5.0
dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskStream_Async* --framework=net6.0
Original file line number Diff line number Diff line change
@@ -1 +1 @@
dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskString_Async* --framework=net5.0
dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskString_Async* --framework=net6.0
2 changes: 1 addition & 1 deletion Refit.Benchmarks/Benchmarks/net5.0/Benchmark_TaskT.bat
Original file line number Diff line number Diff line change
@@ -1 +1 @@
dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskT_Async* --framework=net5.0
dotnet run --project ../../Refit.Benchmarks.csproj -c Release --filter *TaskT_Async* --framework=net6.0
2 changes: 2 additions & 0 deletions Refit.Tests/InterfaceStubGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ static InterfaceStubGeneratorTests()
ReferenceAssemblies = ReferenceAssemblies.Net.Net50;
#elif NET6_0
ReferenceAssemblies = ReferenceAssemblies.Net.Net60;
#elif NET7_0
ReferenceAssemblies = ReferenceAssemblies.Net.Net70;
#else
ReferenceAssemblies = ReferenceAssemblies.Default
.AddPackages(ImmutableArray.Create(new PackageIdentity("System.Text.Json", "7.0.2")));
Expand Down
5 changes: 3 additions & 2 deletions Refit.Tests/Refit.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
<Import Project="..\Refit\targets\refit.props" />

<PropertyGroup>
<TargetFrameworks>net462;net6.0</TargetFrameworks>
<TargetFrameworks>net462;net6.0;net7.0</TargetFrameworks>
<Deterministic>false</Deterministic> <!-- Some tests rely on CallerFilePath -->
<RestoreAdditionalProjectSources>https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json</RestoreAdditionalProjectSources>
</PropertyGroup>

<ItemGroup>
Expand All @@ -19,7 +20,7 @@
<PackageReference Include="System.Reactive" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit" Version="1.1.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit" Version="1.1.2-beta1.23205.1" />
<PackageReference Include="RichardSzalay.MockHttp" Version="6.0.0" />
<ProjectReference Include="..\Refit.HttpClientFactory\Refit.HttpClientFactory.csproj" />
<ProjectReference Include="..\Refit.Newtonsoft.Json\Refit.Newtonsoft.Json.csproj" />
Expand Down
Loading

0 comments on commit c0af5c2

Please sign in to comment.