Guide Home 1. Solution Topology 2. Aspire as 3. Shared Configuration 4. Metadata with 5. Upload API 6. Object Storage 7. Worker Ingestion 8. Extracting and 9. Literary Artifacts 10. AI Provider 11. Qdrant Vector 12. Ask Flow 13. Prompting and 14. Testing the 15. Local Development All Guides
Guide navigationIndex and chapters
Chapter 5

Upload API and UI

The upload endpoint is in RAG.Api/Program.cs:

Decorative chapter image for Upload API and UI
The upload endpoint queues work instead of doing it
app.MapPost("/api/documents", async (HttpRequest request, RagDbContext dbContext, IObjectStorage storage, IOptions<RagOptions> options, CancellationToken cancellationToken) =>
{
    var documentId = Guid.NewGuid();
    var objectKey = $"{documentId:N}/{fileName}";
    await storage.UploadAsync(objectKey, stream, contentType, cancellationToken);
    return Results.Accepted($"/api/documents/{documentId}");
});

The upload endpoint is in RAG.Api/Program.cs:

POST /api/documents

The endpoint:

  1. verifies that the request is multipart form data;
  2. requires a PDF or TXT file;
  3. enforces the configured upload limit;
  4. writes the original file to object storage;
  5. creates a DocumentRecord with Pending status;
  6. returns 202 Accepted.

The API deliberately avoids doing extraction and embedding in the request. It queues work by creating a pending database row.

Production note: the upload limit protects the request body, but this sample still trusts the uploaded file enough to store it and later process it. A production system would usually add stronger content validation, malware scanning, tighter per-user quotas, and generic error responses instead of returning internal exception details. This project keeps the behavior simple because it is a learning project, not production software.

The UI in RAG.Api/wwwroot is intentionally plain:

The UI polls GET /api/documents every few seconds. For large books, ingestion can take a while, so the worker updates:

Production note: polling is simple and works well for this local sample, but a production UI would usually subscribe to status updates instead. For example, the API could publish document progress events through SignalR, WebSockets, Server-Sent Events, or a message broker-backed notification service, and the browser could receive updates as they happen instead of repeatedly asking the API for the full document list.

This lets the UI show useful progress instead of leaving a book stuck at a vague Processing state.

Delete and Reindex Controls

The API now exposes document lifecycle controls in addition to upload and status polling:

DELETE /api/documents/{id}
POST   /api/documents/{id}/reindex

DocumentManagementService owns those operations. Deletion removes the metadata row, deletes the original object, and removes vector points for the document. Reindexing resets status and progress back to Pending so the worker can rebuild chunks and vectors from the original file.

This matters because RAG systems need maintenance workflows. Once chunking, extraction, prompts, embeddings, or generated artifacts change, users need a way to rebuild the derived index rather than upload the same document again.