# HTTP & files

Fetching JSON over HTTP and moving files in and out of the browser are plain .NET in Rask: a
dependency-injected `HttpClient`, the typed file-picker input, and the `Download` facade. The
*same* component code runs server-rendered over a WebSocket or client-side on WebAssembly — only the host
wiring differs. This guide walks the three, each with a live demo.

- [Fetching data with `HttpClient`](#fetching-data-with-httpclient) — register a DI'd client, fetch in `OnMount`
- [Uploading files](#uploading-files) — a typed file picker and `IRaskFile` metadata
- [Keeping an upload](#keeping-an-upload) — where the bytes go once the handler returns
- [Downloading files](#downloading-files) — hand the browser a file with `Download.File`

For **server-side persistence** (EF Core + SQLite, `IDbContextFactory`, vertical slices), see the
[Data access](https://rask.sh/docs/guides/data-access/) guide — this one is about data and file *transfer*.

---

## Fetching data with `HttpClient`

`HttpClient` is registered once in `Program.cs` and injected into components through the **constructor** —
no `[Inject]`, no service locator. Point its `BaseAddress` at the app's own origin so relative URLs
(`data/posts-1.json`) resolve against the static files the app serves itself; the demos below fetch a small
static JSON file, so the showcase stays self-contained and offline-safe.

The base address differs per host: on the Server it's the server's own origin; on WASM it's
`WasmHostBuilder.BaseAddress` — the app root, carrying any sub-path (a GitHub Pages deploy under
`/rask/`, say). Read it **lazily** inside the factory so it fires after the JS module imports:


Inject the configured client and load in `OnMount` — it runs once on first render, and the framework's
async lifecycle handler re-renders when the awaited task completes. `Component.CancellationToken` cancels on
unmount, so navigating away mid-fetch aborts the in-flight request instead of writing to a dead component:


> **Same demo, two hosts.** On a Server host the request is a loopback call to the server's own static
> file; on the browser-WASM host (which is what <https://rask.sh> serves) the browser fetches the same
> file from the AppBundle. The page code is identical — only the `BaseAddress` differs per host.

---

## Uploading files

`Input.Of<string>().Type(InputType.File).OnFiles(…)` wires a file picker to a typed handler. Each change event
hands the handler an `IReadOnlyList<IRaskFile>`; `IRaskFile` carries the metadata (name, size, content type,
last-modified) and `OpenReadStream` gives you a `Stream` for the bytes — over a multipart POST on the
Server, via JS chunked reads on WASM. The same component code runs unchanged on both hosts:


The kit draws the same input two ways, both reporting through `OnFiles`. A plain file field is
`Ui.Input.Of<string>().Type(InputType.File).Label("Logo")`. An area to drop files on is
[`Ui.FileUpload`](https://rask.sh/docs/guides/ui-kit/) — Flux UI's file upload — with a `Ui.FileItem` for each file the page kept:

```csharp
Ui.FileUpload.Label("Upload files").Multiple().OnFiles(Keep)[
    Ui.FileUploadDropzone.Heading("Drop files here or click to browse").Text("JPG, PNG, GIF up to 10MB")
],
_photos.Select((photo, index) =>
    Ui.FileItem.Key(photo.Name).Heading(photo.Name).Size(photo.Size)
        .Actions(Ui.FileItemRemove.OnClick(() => _photos.RemoveAt(index))))
```

A dropped file takes the same road as a chosen one: the drop lands in the input, and the input uploads. While
it does, the upload carries `data-loading` and the dropzone shows a spinner, or with `WithProgress()` a bar: the
request's own progress on the Server host, and in a WebAssembly app — where nothing is sent — how much of the
files the handler has read through `OpenReadStream`
([the `data-rask-loading` hook](https://rask.sh/docs/guides/js-interop-runtime/#behaviour-hooks-data-rask-)).

> A `IRaskFile` is only valid while the handler is on the stack — read whatever you need (bytes, metadata)
> before returning. The mutating handler lives inside the component so its field updates re-render the right
> subtree.

---

## Keeping an upload

Because a `IRaskFile` stops being readable when its handler returns, a file you mean to keep has to be written
somewhere before then. That is what [file storage](https://rask.sh/docs/guides/file-storage/) is for, and in a `RaskApp` it is already
on — save from the handler with nothing injected, then keep the id on your own entity:

```csharp
var file = picked[0];
var saved = await Files.Save(file.OpenReadStream, file.Name, file.Size);
order.AttachReceipt(saved.Id);
```

`Files.Save` writes the bytes to disk, an S3-compatible bucket or Azure Blob, records a row whose content type
is sniffed from the bytes rather than taken from the browser, and refuses a file over the size limit or of a
type you haven't allowed. The file comes back later as a public URL, a temporary URL, or a download behind
your own authorization check. It runs on the server: a WebAssembly page uploads there first.

---

## Downloading files

`Download.File` stages bytes (or a stream) on the active session: on the Server they're served from
`/_rask/download/{sid}/{token}`; on WASM the bytes stay on the .NET side and the render carries only a short
token, which the page trades for the bytes through a JS-to-.NET call when it starts the download. The
component code is the same. It must be called from an **event handler** — outside that scope it throws, because there's no live
render round-trip to attach the download to. The handler can make other state changes too (here it bumps a
counter); both ship in the same render:


---

See also: [Data access](https://rask.sh/docs/guides/data-access/) for EF Core persistence, [Forms & validation](https://rask.sh/docs/guides/forms/) for the
`HTMLFormElement<T>` pipeline and typed inputs, and [Lifecycle](https://rask.sh/docs/guides/lifecycle/) for `OnMount` and cancellation.
