reactiveui/refit · error · ArgumentException
Method "{methodInfo.Name}" is invalid. All REST Methods must
Error message
Method "{methodInfo.Name}" is invalid. All REST Methods must return either Task<T> or ValueTask<T> or IObservable<T> What it means
Thrown by DetermineReturnTypeInfo when a public, top-level Refit interface method has a return type that is not an async-compatible shape (Task<T>, ValueTask<T>, IObservable<T>, IAsyncEnumerable<T>, or a non-generic Task) and is not handled by a registered return-type adapter. Refit needs an awaitable/observable return so it can drive the HTTP pipeline; a bare synchronous return (e.g. string, int, void) has no way to surface the async HTTP work.
Source
Thrown at src/Refit.Reflection/RestMethodInfoInternal.cs:465
if (adapterResultType is not null)
{
return (returnType, adapterResultType, DetermineDeserializedResultType(adapterResultType));
}
// Allow synchronous return types only for methods that are implemented by generated stubs
// (for example explicit/default interface implementations). Public top-level Refit methods must
// still use async-compatible return shapes.
#if NET8_0_OR_GREATER
var isExplicitInterfaceMember = methodInfo.Name.Contains('.');
#else
var isExplicitInterfaceMember = methodInfo.Name.Contains(".");
#endif
var isNonPublic = !methodInfo.IsPublic;
if (!isExplicitInterfaceMember && !isNonPublic)
{
throw new ArgumentException(
$"Method \"{methodInfo.Name}\" is invalid. All REST Methods must return either Task<T> or ValueTask<T> or IObservable<T>");
}
return (returnType, returnType, DetermineDeserializedResultType(returnType));
}
/// <summary>Determines the type that response content is deserialized into for the given result type.</summary>
/// <param name="returnResultType">The result type wrapped by the return type.</param>
/// <returns>The type to deserialize response content into.</returns>
internal static Type DetermineDeserializedResultType(Type returnResultType)
{
if (
returnResultType.IsGenericType
&& (
returnResultType.GetGenericTypeDefinition() == typeof(ApiResponse<>)
|| returnResultType.GetGenericTypeDefinition() == typeof(IApiResponse<>)
)
)View on GitHub (pinned to b455f65ecc)
Solutions
- Wrap the return type in Task<T> (or ValueTask<T>), e.g. change `User GetUser()` to `Task<User> GetUser()`.
- For streaming/reactive scenarios use IObservable<T> or IAsyncEnumerable<T>.
- If you genuinely need the raw response, use Task<HttpResponseMessage> or Task<IApiResponse<T>> (these are Task<>, so they are accepted).
- For custom async return shapes, register an IReturnTypeAdapter in RefitSettings so adapterResultType is populated.
Example fix
// before
public interface IApi
{
[Get("/users/{id}")]
User GetUser(int id);
}
// after
public interface IApi
{
[Get("/users/{id}")]
Task<User> GetUser(int id);
} Defensive patterns
Strategy: type-guard
Type guard
// Reject interfaces whose Refit methods lack an async-compatible return.
static bool IsRefitAsyncReturn(Type t) =>
t == typeof(Task) ||
(t.IsGenericType && t.GetGenericTypeDefinition() is var g &&
(g == typeof(Task<>) || g == typeof(ValueTask<>) ||
g == typeof(IObservable<>) || g == typeof(IAsyncEnumerable<>)));
foreach (var m in typeof(IMyApi).GetMethods())
if (!IsRefitAsyncReturn(m.ReturnType))
throw new InvalidOperationException($"{m.Name} must return Task<T>/ValueTask<T>/IObservable<T>/IAsyncEnumerable<T>"); Prevention
- Always wrap Refit method results in Task<T> (or ValueTask<T>) by convention.
- Add a Roslyn analyzer/test that asserts every public method on Refit interfaces returns Task/ValueTask/IObservable/IAsyncEnumerable.
- Use Task<IApiResponse<T>> or Task<HttpResponseMessage> when you need the raw response.
When it happens
Trigger: Declaring a Refit interface method that returns a non-async type, e.g. `string GetUser()` or `void Delete()` or `HttpResponseMessage Get()` (HttpResponseMessage alone is not awaitable). The check is skipped for explicit interface members and non-public methods (generated stubs), so it only hits public declared methods.
Common situations: Newcomer mistake of returning the deserialized type directly instead of wrapping it in Task<T>; migrating from HttpClient.GetAwaiter patterns; forgetting ValueTask; a method returning Task (non-generic) when content is expected — note plain `Task` IS allowed and returns void, so the real trigger is a truly synchronous signature.
Related errors
- URL path {relativePath} must not contain CR or LF characters
- Multipart requests may not contain a Body parameter
- Only one parameter can be a Body parameter
- Multiple complex types found. Specify one parameter as the b
AI-assisted analysis of reactiveui/refit@b455f65ecc (2026-08-13).
Data as JSON: /api/errors/fa80dd9441c42bd4.
Report an issue: GitHub.