reactiveui/refit · error · InvalidOperationException

BaseAddress must be set on the HttpClient instance

Error message

BaseAddress must be set on the HttpClient instance

What it means

Thrown by BuildRelativeUri when the client runs under UrlResolutionMode.Legacy and HttpClient.BaseAddress is null. Legacy resolution prefixes the method's relative path with the client's base absolute path, so a missing base address leaves no path to prefix. This is an InvalidOperationException signaling a configuration gap, not a transient network fault.

Source

Thrown at src/Refit/GeneratedRequestRunner.cs:39

    private static readonly Uri QueryUriFormatBase = new("https://api", UriKind.Absolute);

    /// <summary>Builds the relative request URI for a generated request, joining the client base address with the method path.</summary>
    /// <param name="client">The HTTP client whose base address is used under legacy resolution.</param>
    /// <param name="relativePath">The method's relative path, including any leading slash and query string.</param>
    /// <param name="urlResolution">The configured URL resolution mode.</param>
    /// <returns>A relative <see cref="Uri"/> to assign to the request, which the client merges with its base address.</returns>
    /// <exception cref="InvalidOperationException">Legacy resolution is in effect and <paramref name="client"/> has no base address to prefix the path with.</exception>
    public static Uri BuildRelativeUri(HttpClient client, string relativePath, UrlResolutionMode urlResolution)
    {
        if (urlResolution == UrlResolutionMode.Rfc3986)
        {
            // Let the HttpClient merge the base address with the relative path per RFC 3986; emit the path verbatim.
            return new(relativePath, UriKind.Relative);
        }

        RequireLeadingSlashUnderLegacy(relativePath);
        var basePath = client.BaseAddress?.AbsolutePath
                       ?? throw new InvalidOperationException("BaseAddress must be set on the HttpClient instance");
        basePath = basePath == "/" ? string.Empty : basePath.TrimEnd('/');
        return new(basePath + relativePath, UriKind.Relative);
    }

    /// <summary>Builds the relative request URI, re-encoding the whole path and query with a <c>[QueryUriFormat]</c> mode.</summary>
    /// <param name="client">The HTTP client whose base address is used under legacy resolution.</param>
    /// <param name="relativePath">The method's relative path, including any leading slash and query string.</param>
    /// <param name="urlResolution">The configured URL resolution mode.</param>
    /// <param name="queryUriFormat">The escaping mode from the method's <c>[QueryUriFormat]</c> attribute.</param>
    /// <returns>A relative <see cref="Uri"/> whose path and query are re-encoded with <paramref name="queryUriFormat"/>.</returns>
    /// <remarks>Mirrors the reflection request builder: it always assembles the path and query with the escaping query
    /// builder, then re-encodes the whole thing through <see cref="Uri.GetComponents(UriComponents, UriFormat)"/> with the
    /// method's <c>QueryUriFormat</c> (so <see cref="UriFormat.Unescaped"/> decodes it). Rfc3986 resolution ignores the
    /// format, exactly as the reflection builder does.</remarks>
    /// <exception cref="InvalidOperationException">Legacy resolution is in effect and <paramref name="client"/> has no base address to prefix the path with.</exception>
    public static Uri BuildRelativeUri(HttpClient client, string relativePath, UrlResolutionMode urlResolution, UriFormat queryUriFormat)
    {
        if (urlResolution == UrlResolutionMode.Rfc3986)

View on GitHub (pinned to b455f65ecc)

Solutions

  1. Set client.BaseAddress before invoking any interface method (e.g., client.BaseAddress = new Uri("https://api.example.com/"))
  2. If you pass the host through Refit, use RestService.For<T>(hostUrl) or CreateHttpClient(hostUrl, settings) so BaseAddress is assigned for you
  3. Switch to UrlResolutionMode.Rfc3986 via RefitSettings if you intentionally merge paths yourself and have no single base host
  4. In DI, configure the HttpClient with .ConfigureHttpClient(c => c.BaseAddress = new Uri(...)) inside AddRefitClient/AddRefitGeneratedClient

Example fix

// before
var client = new HttpClient();
var api = RestService.ForGenerated<IMyApi>(client, settings);
await api.GetThingsAsync(); // throws - no base address

// after
var client = new HttpClient { BaseAddress = new Uri("https://api.example.com/") };
var api = RestService.ForGenerated<IMyApi>(client, settings);
await api.GetThingsAsync();
Defensive patterns

Strategy: validation

Validate before calling

if (client.BaseAddress is null)
    throw new InvalidOperationException(
        "HttpClient.BaseAddress must be set before using Refit under legacy URL resolution.");
var api = RestService.ForGenerated<IMyApi>(client, settings);

Type guard

static bool HasBaseAddress(HttpClient client) => client.BaseAddress is not null;

Try / catch

try { await api.GetAsync(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("BaseAddress must be set"))
{
    // reconfigure the client with a BaseAddress and retry, or surface a config error
}

Prevention

When it happens

Trigger: Configuring Refit with UrlResolutionMode.Legacy (or running on a target where legacy is the default) and calling a generated interface method on an HttpClient whose BaseAddress was never assigned. Switching from Rfc3986 to Legacy resolution on an existing client that relied solely on relative merging also triggers it.

Common situations: Calling RestService.ForGenerated with a bare HttpClient() (no base address); DI registering a named HttpClient without setting BaseAddress; migrating an app from an older Refit version that defaulted to legacy-style path concatenation; unit tests constructing HttpClient directly without a host.

Related errors


AI-assisted analysis of reactiveui/refit@b455f65ecc (2026-08-13). Data as JSON: /api/errors/6bdc0308b08dc84c. Report an issue: GitHub.