loading

Send a request

Send

Send issues the request and resolves with a FetchResponse carrying the status, final URL (after redirects), response headers and body bytes. Ok is true for any 2xx status; network or CORS failures surface in the Error property instead of throwing.

C#
FetchResponse response = await fetch.Send(new FetchRequest
{
    Url = "https://jsonplaceholder.typicode.com/todos/1",
});

if (response.Ok)
{
    var json = Encoding.UTF8.GetString(response.Body);
}
Live sample
URL
send output
Results will appear here when you interact with the samples.

Request options

FetchRequest

FetchRequest exposes the fetch() init options as plain properties: Method, Headers, Body bytes, plus the fetch-specific Credentials, Mode, Cache and Redirect strings. This demo POSTs a JSON document with an explicit Content-Type and cache bypass.

C#
var request = new FetchRequest
{
    Url = "https://jsonplaceholder.typicode.com/posts",
    Method = "POST",
    Headers = new() { ["Content-Type"] = "application/json; charset=utf-8" },
    Body = Encoding.UTF8.GetBytes("{\"title\":\"Bit.Butil\",\"userId\":1}"),
    Credentials = "omit",   // "omit" | "same-origin" | "include"
    Mode = "cors",          // "cors" | "no-cors" | "same-origin" | "navigate"
    Cache = "no-store",     // "default" | "no-store" | "reload" | "no-cache" | ...
    Redirect = "follow",    // "follow" | "error" | "manual"
};

FetchResponse response = await fetch.Send(request);
Live sample
request options output
Results will appear here when you interact with the samples.

Headers

FetchHeaders

FetchHeaders is the Headers half of the object model: an ordered list where a name may repeat, matched case-insensitively. Append keeps what is already there, Set replaces it, GetAll returns every value and Get joins them with a comma the way the Headers specification does. A Dictionary<string, string> still converts both ways, so code written against the old shape keeps working.

C#
var headers = new FetchHeaders()
    .Append("Accept", "application/json")
    .Append("X-Trace", "one")
    .Append("X-Trace", "two");

headers.GetAll("x-trace");   // ["one", "two"] - case-insensitive
headers.Get("X-Trace");      // "one, two"
headers.Set("Accept", "text/plain");   // replaces every Accept

// still assignable from - and to - a dictionary
FetchRequest request = new() { Headers = new() { ["Content-Type"] = "application/json" } };
Dictionary<string, string> flat = request.Headers;
Live sample
headers output
Results will appear here when you interact with the samples.

The response object

FetchResponse

Beyond the status and body, FetchResponse carries what only the browser knows: whether the request was redirected to get here, and the response Type - basic, cors, or opaque for a no-cors request whose status reads 0 and whose body is unreadable by design. A network or CORS failure is not an exception; it is a response with Ok false and Error set.

C#
FetchResponse response = await fetch.Send(new FetchRequest { Url = url });

// response.Redirected - true when at least one redirect got us here
// response.Type       - "basic" | "cors" | "opaque" | "opaqueredirect" | "error"
// response.Error      - set instead of throwing when the network or CORS said no

var opaque = await fetch.Send(new FetchRequest { Url = url, Mode = "no-cors" });
// opaque.Type == "opaque", opaque.Status == 0, opaque.Body is empty
Live sample
response object output
Results will appear here when you interact with the samples.

Streaming upload

SendStream / SupportsStreamingUpload

SendStream hands the browser a .NET Stream as the request body and lets it pull as the connection drains, so neither side holds the payload whole - the way to POST a file larger than memory. Chromium-only, over HTTP/2 or HTTP/3 in a secure context, which is what SupportsStreamingUpload answers; where it is unsupported the request comes back as a failed response rather than throwing.

C#
if (await fetch.SupportsStreamingUpload() is false) return;   // fall back to Send

await using var file = File.OpenRead(path);

FetchResponse response = await fetch.SendStream(
    new FetchRequest
    {
        Url = "https://example.com/upload",
        Method = "POST",
        Headers = new() { ["Content-Type"] = "application/octet-stream" },
    },
    file,
    onProgress: p => { /* p.Loaded of p.Total handed to the browser */ });
Live sample
streaming upload output
Results will appear here when you interact with the samples.

Progress reporting

Send + onProgress

Pass an onProgress callback and it fires as body chunks arrive, with Loaded bytes so far and Total from Content-Length (null for chunked responses). Ideal for progress bars on large downloads - something HttpClient on WebAssembly can't observe.

C#
var response = await fetch.Send(
    new FetchRequest { Url = "https://example.com/large-file" },
    onProgress: p =>
    {
        var percent = p.Total.HasValue ? $"{100.0 * p.Loaded / p.Total.Value:F0}%" : "?";
        // update UI: p.Loaded of p.Total bytes
    });
Live sample
progress output
Results will appear here when you interact with the samples.

Cancellation

Send + CancellationToken

Send accepts a CancellationToken; triggering it aborts the underlying fetch through an AbortController. Cancellation does not throw - it resolves with a FetchResponse whose Aborted flag is true, matching the abort-handle path.

C#
using var cts = new CancellationTokenSource();

var responseTask = fetch.Send(
    new FetchRequest { Url = "https://example.com/large-file" },
    cancellationToken: cts.Token);

cts.Cancel(); // abort mid-flight

var response = await responseTask;
// response.Aborted == true
Live sample
cancellation output
Results will appear here when you interact with the samples.

Fire-and-forget abort handle

Start / AbortableFetch

Start launches the request and immediately returns an AbortableFetch handle without waiting for (or returning) the response payload. Call Abort to stop it; disposing the handle also aborts unless the request already completed. Prefer Send whenever you need the response.

Razor
@inject Bit.Butil.Fetch fetch

@code {
    private AbortableFetch? handle;

    private async Task Start() =>
        handle = await fetch.Start(new FetchRequest
        {
            Url = "https://example.com/large-file",
        });

    // Nothing waits for the response, so this is the only way to stop the transfer.
    private async Task Cancel() => await handle!.Abort();
}
Live sample
abort handle output
Results will appear here when you interact with the samples.
Note:
When to prefer HttpClient For ordinary API calls, HttpClient remains the right tool - it integrates with delegating handlers, typed clients and the rest of .NET. Reach for Bit.Butil.Fetch when you need what only the browser's fetch() offers: download progress, abort semantics, credentials / CORS modes and cache control. Cross-origin demo requests on this page are still subject to the target server's CORS policy.

API reference

Member
Signature
Description
Send
Task<FetchResponse> Send(FetchRequest request, Action<FetchProgress>? onProgress = null, CancellationToken cancellationToken = default)
Sends the request and returns the full response. onProgress fires as bytes arrive; the token aborts the request.
SendStream
Task<FetchResponse> SendStream(FetchRequest request, Stream body, Action<FetchProgress>? onProgress = null, CancellationToken cancellationToken = default)
Sends the request with a .NET Stream as a streamed upload body; the caller keeps ownership of the stream.
SupportsStreamingUpload
ValueTask<bool> SupportsStreamingUpload()
True when the engine can send a request body as a stream - Chromium only, over HTTP/2 or HTTP/3.
Start
Task<AbortableFetch> Start(FetchRequest request)
Starts the request and immediately returns an abort handle; does not return the response payload.
InvokeFetchProgress
void InvokeFetchProgress(Guid id, FetchProgress progress)
JSInvokable interop plumbing invoked from JavaScript as bytes arrive - not intended for direct use.
DisposeAsync
ValueTask DisposeAsync()
Releases the interop callback reference held for progress reporting.
FetchRequest.Url
string Url
The request URL.
FetchRequest.Method
string Method = "GET"
HTTP verb. Defaults to GET.
FetchRequest.Headers
FetchHeaders Headers
Request headers. Assignable from a Dictionary<string, string>; use Append when a name has to repeat.
FetchRequest.Body
byte[]? Body
Optional body bytes. Set a Content-Type header when needed.
FetchRequest.Credentials
string Credentials = "same-origin"
One of "omit", "same-origin", "include".
FetchRequest.Mode
string Mode = "cors"
One of "cors", "no-cors", "same-origin", "navigate".
FetchRequest.Cache
string Cache = "default"
Cache mode: "default", "no-store", "reload", "no-cache", "force-cache", "only-if-cached".
FetchRequest.Redirect
string Redirect = "follow"
One of "follow", "error", "manual".
FetchRequest.Referrer
string? Referrer
The referrer to send: a same-origin URL, "about:client" for the default, or "" for none.
FetchRequest.ReferrerPolicy
string? ReferrerPolicy
How much of the referrer to send - "no-referrer", "origin", "strict-origin-when-cross-origin"...
FetchRequest.Integrity
string? Integrity
A subresource integrity digest ("sha256-...") the response must match, or the fetch fails.
FetchRequest.KeepAlive
bool KeepAlive
Lets the request outlive the page, for a beacon sent during unload. Capped at 64 KiB of body.
FetchRequest.Priority
string? Priority
A scheduling hint: "high", "low" or "auto". Advisory - the browser decides.
FetchResponse.Ok
bool Ok
True when the status is in [200, 300).
FetchResponse.Status
int Status
HTTP status, or 0 when the request was aborted or failed before headers.
FetchResponse.StatusText
string StatusText
The HTTP status text.
FetchResponse.Url
string Url
Final URL after redirects.
FetchResponse.Headers
FetchHeaders Headers
Response headers, repeats included, so Link and Vary arrive whole. Only the headers fetch is permitted to expose are present - the CORS-safelisted ones plus whatever Access-Control-Expose-Headers names; forbidden ones such as Set-Cookie are filtered out by the browser.
FetchResponse.Body
byte[] Body
Body bytes. May be empty for 204/304 or aborted responses.
FetchResponse.Redirected
bool Redirected
Whether the request went through at least one redirect to get here.
FetchResponse.Type
string Type
"basic", "cors", "opaque", "opaqueredirect" or "error". An opaque response has status 0 and no readable body.
FetchResponse.Aborted
bool Aborted
True when the request was aborted via the handle or a cancellation token.
FetchResponse.Error
string? Error
Network/CORS error description, when one occurred.
FetchHeaders.Append
FetchHeaders Append(string name, string value)
Adds a header, keeping any already there under the same name.
FetchHeaders.Set
FetchHeaders Set(string name, string? value)
Replaces every occurrence of the name with one value, or removes it when the value is null.
FetchHeaders.Get
string? Get(string name)
The value, repeats joined by ", " as the Headers specification defines. Null when absent.
FetchHeaders.GetAll
string[] GetAll(string name)
Every value sent under the name, in order.
FetchHeaders.Has
bool Has(string name)
Whether the header is present at all - absent versus present-but-empty.
FetchHeaders.Remove
bool Remove(string name)
Removes every occurrence of the name; returns whether anything was removed.
FetchHeaders.Names
IEnumerable<string> Names
The distinct header names present, in the order they first appear.
FetchHeaders.ToDictionary
Dictionary<string, string> ToDictionary()
Flattens to a dictionary, repeats joined by ", ". The lossy direction.
FetchHeaders (implicit)
implicit operator FetchHeaders?(Dictionary<string, string>?)
A dictionary of headers reads as a FetchHeaders, so code written against the old shape keeps working. Null converts to null rather than throwing.
Dictionary (implicit)
implicit operator Dictionary<string, string>?(FetchHeaders?)
The flattening conversion - ToDictionary applied implicitly, with the same loss. Null converts to null.
FetchProgress.Loaded
long Loaded
Bytes received so far.
FetchProgress.Total
long? Total
Total bytes expected, or null when unknown (chunked / no Content-Length).
AbortableFetch.Id
Guid Id
The internal request id.
AbortableFetch.Abort
ValueTask Abort()
Aborts the request immediately if it's still in flight; disposing the handle does the same.
An unhandled error has occurred. Reload 🗙