How to Build an AI Document Q&A API in ASP.NET Core 10 Using C# and PDF Processing (2026)
A PDF question-answering API can produce a fluent answer and still fail its most important job: showing where the answer came from. Preserve page numbers before splitting the text. Then cite the passage you actually retrieved rather than a page guessed by a model.
This guide builds an ASP.NET Core 10 PDF Q&A API with PdfPig. It accepts one bounded PDF, extracts page text, converts overlapping chunks into normalized feature vectors, ranks passages and returns extractive answers with page citations. The local baseline uses deterministic lexical feature hashing. The tutorial explains how to replace it with semantic embeddings and grounded generation after the upload and citation path works.
What happens to the PDF after upload?
| Stage | Decision | Why it matters |
|---|---|---|
| Upload | Limit bytes and check header | Prevent obviously invalid requests and oversized bodies |
| Extraction | Keep each page number | Preserve a source anchor |
| Chunking | Keep overlap and avoid crossing pages | Preserve nearby terms and make citations precise |
| Retrieval | Score normalized vectors | Return relevant chunks without sending text elsewhere |
| Answer | Return the best passage and source IDs | Keep the result grounded in actual extracted text |
A PDF header check is only a preliminary validation step. It cannot establish that a PDF is safe. PDF parsing may allocate memory or consume CPU before you can check how many words a page contains. This code is a loopback lab and uses one concurrent parse per process. A public service should parse in a separate restricted worker with process-level memory, CPU and execution limits, no network egress, a quarantine policy, and patch management.
Build the project
Install .NET 10 and a PDF reader or editor that can create a selectable-text PDF. Create a new directory with the files below. The API accepts raw application/pdf, keeping the upload contract small. For a multipart upload, validate the form boundary and size before reading the file and never use a client filename as a storage path. See ASP.NET Core file uploads and PdfPig.
The sample uses a normalized signed hash vector of 512 components. Similarity is the dot product of two unit vectors. This reflects term overlap, with possible collisions. It will miss paraphrases, and a positive score is not proof that a passage answers the question. The feature extractor accepts ASCII terms of at least two characters for reproducibility. Production retrieval may need language-aware tokenization and semantic embeddings. The response includes page and chunk so consumers can locate the source. For a persistent system, also store a document ID, immutable revision ID and, when appropriate, bounding boxes.
Run a reproducible PDF question
Create policy.pdf with the selectable text “Refunds are available within 30 days.” Keep it to one page. Run the server and send the raw bytes.
The expected answer includes the policy sentence. The first citation should show page 1 and chunk 0. Also try a text file with the PDF content type, a wrong media type, an image-only page, and a second call while the worker is busy. Expect HTTP 400 for an invalid header, 415 for the wrong media type, a no-match response for an image-only fixture, and 429 if the concurrency limit is reached. The size limit applies before full buffering. Test the HTTP 413 path under both your application server and ingress.
The project compiled on .NET SDK 10.0.100. A one-page selectable-text fixture returned the expected refund passage and page 1 citation. A payload with an invalid PDF header returned HTTP 400. OCR and hostile parser workloads were not tested.
Add semantic embeddings and generation carefully
Replace the hashing function with the same embedding model for queries and document chunks. Persist model identifier and dimensionality with each revision. Batch provider calls outside database transactions, deduplicate chunks where safe, and limit retries. Run retrieval only against authorized document IDs. Retrieve a small set of source passages and pass each source label and text to the model as untrusted context. Validate that every returned citation belongs to the actual result set. If the model provides an unknown ID, reject or regenerate the answer without pretending that citation exists.
For a scanned PDF, use an OCR stage with a confidence threshold. Preserve page numbering and mark OCR text separately so a user can verify it. For tables and columns, basic word order may be unsuitable. Add layout-aware extraction and fixtures for multi-column pages, repeated footers, ligatures, and unusual reading order. The ASP.NET PB RAG chatbot guide covers provider embeddings and answer generation in more detail.
| Symptom | Check |
|---|---|
| Page returns no words | Scanned image, unusual encoding, or encrypted PDF |
| Answer has jumbled columns | Reading order and layout analysis |
| A paraphrase finds nothing | Lexical baseline needs semantic embeddings |
| Source page seems wrong | Preserve parser page number before chunking |
| Upload accepted but worker stalls | Isolate parser and enforce process budgets |
FAQ and references
Can the PDF instruct the model to call a tool? Extracted text is untrusted data. Keep tool authorization independent and do not give the Q&A model tools it does not need.
Does this handle OCR? The sample extracts embedded text. An image-only PDF needs a separate OCR stage.
Why return an extractive answer first? It makes citation errors visible before a generator can hide them in a fluent response.
Keep page provenance through extraction, chunking and ranking. Add a generative layer only after retrieval and citations pass representative document tests.
Comments
Post a Comment