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
- Set client.BaseAddress before invoking any interface method (e.g., client.BaseAddress = new Uri("https://api.example.com/"))
- If you pass the host through Refit, use RestService.For<T>(hostUrl) or CreateHttpClient(hostUrl, settings) so BaseAddress is assigned for you
- Switch to UrlResolutionMode.Rfc3986 via RefitSettings if you intentionally merge paths yourself and have no single base host
- 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
- Always construct the client via RestService.For<T>(hostUrl) or CreateHttpClient(hostUrl, settings) so BaseAddress is set
- In DI, configure BaseAddress through ConfigureHttpClient in AddRefitClient/AddRefitGeneratedClient
- Add a startup health check asserting client.BaseAddress is not null
- Keep a single client-creation helper so BaseAddress cannot be forgotten
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
- BaseAddress must be set on the HttpClient instance
- `hostUrl` must not be null or whitespace.
- Sequence contains more than one matching element
- Sequence contains no matching element
- targetInterface must be an Interface
AI-assisted analysis of reactiveui/refit@b455f65ecc (2026-08-13).
Data as JSON: /api/errors/6bdc0308b08dc84c.
Report an issue: GitHub.