Guides
Default Query Function
Default Query Function
SwrSharp allows you to set a default query function that will be used for all queries that don't specify their own.
Overview
Instead of providing a query function to every UseQuery call, you can set a global default that extracts the query key and fetches data automatically.
Setting Up Default Query Function
Configure Global Default
// In Program.cs
builder.Services.AddSwrSharp(options =>
{
options.DefaultQueryFn = async (ctx) =>
{
var (queryKey, signal) = ctx;
var url = BuildUrlFromQueryKey(queryKey);
return await Http.GetFromJsonAsync<object>(url, signal);
};
});
Using Default Query Function
@page "/todos"
@inject QueryClient QueryClient
@implements IAsyncDisposable
@if (Query?.Data is Todo[] todos)
{
@foreach (var todo in todos)
{
<div class="todo">@todo.Title</div>
}
}
@code {
private UseQueryResult<Todo[]>? Query;
protected override async Task OnInitializedAsync()
{
// No query function needed - uses default!
Query = await QueryClient.UseQuery(
queryKey: new QueryKey("todos")
);
}
async ValueTask IAsyncDisposable.DisposeAsync()
{
if (Query != null)
await Query.DisposeAsync();
}
}
Query Key Convention
URL Building from Query Key
private static string BuildUrlFromQueryKey(QueryKey key)
{
// Query key: ["todos"] -> "/api/todos"
// Query key: ["todos", 1] -> "/api/todos/1"
// Query key: ["users", 42, "posts"] -> "/api/users/42/posts"
var parts = key.Parts
.Select(p => Uri.EscapeDataString(p?.ToString() ?? ""))
.ToList();
var path = string.Join("/", parts);
return $"/api/{path}";
}
Override Default Query Function
Per-Query Override
You can still override the default for specific queries:
@code {
private UseQueryResult<Todo[]>? Query;
protected override async Task OnInitializedAsync()
{
// Override default for this specific query
Query = await QueryClient.UseQuery(
queryKey: new QueryKey("todos"),
queryFn: async (ctx) =>
{
// Custom logic for this query
var todos = await Http.GetFromJsonAsync<Todo[]>("/api/todos?completed=false", ctx.Signal);
return todos ?? Array.Empty<Todo>();
}
);
}
}
Advanced Pattern
Smart URL Building
public class SmartDefaultQueryFunction
{
private readonly HttpClient httpClient;
public SmartDefaultQueryFunction(HttpClient httpClient)
{
this.httpClient = httpClient;
}
public async Task<object?> FetchAsync(QueryFunctionContext ctx)
{
var (queryKey, signal) = ctx;
// Build URL intelligently
var url = BuildUrl(queryKey);
// Determine return type from context
var responseType = GetResponseType(queryKey);
// Fetch using reflection or type mapping
var method = typeof(HttpClientJsonExtensions)
.GetMethod("GetFromJsonAsync")!
.MakeGenericMethod(responseType);
return await (dynamic)method.Invoke(null, new object[] { httpClient, url, signal })!;
}
private string BuildUrl(QueryKey key)
{
var parts = key.Parts.Select(p => Uri.EscapeDataString(p?.ToString() ?? ""));
return $"/api/{string.Join("/", parts)}";
}
private Type GetResponseType(QueryKey key)
{
// Map query key to response type
return key[0]?.ToString() switch
{
"todos" => typeof(Todo[]),
"users" => typeof(User[]),
"posts" => typeof(Post[]),
_ => typeof(object)
};
}
}
Benefits
✅ Less Boilerplate: No need to define query functions for standard REST APIs
✅ Consistency: All queries follow the same pattern
✅ Flexibility: Can still override per-query when needed
✅ Scalability: Easy to add new endpoints
Best Practices
- Keep it simple: Default function should handle common cases
- Provide override: Allow specific queries to use custom logic
- Document conventions: Make URL building rules clear
- Handle errors: Ensure default function handles failures gracefully
- Consider types: Think about how to handle different response types