I'm always excited to take on new projects and collaborate with innovative minds.
A step-by-step walkthrough of BlazorMcqCreator — an open-source Blazor Server app that turns uploaded PDFs and TXT files into polished, expert-reviewed multiple choice questions using fully local LLMs via Ollama.
Creating high-quality assessment material is time-consuming. Educators and corporate trainers spend hours reviewing documentation, drafting questions, formulating plausible distractors, and refining prompt clarity.
BlazorMcqCreator is an open-source Blazor Server application designed to automate multiple-choice question (MCQ) generation locally. It parses source documentation, generates customizable assessment sets, and conducts an automated second-stage review to evaluate cognitive depth and item quality.
By leveraging Ollama for local inference, the entire workflow operates completely offline. This eliminates subscription fees, token costs, and third-party data privacy risks.
Cloud-based LLM APIs present several operational challenges for academic and enterprise environments:
Data Privacy: Internal documentation, proprietary curriculum, and sensitive assessment materials remain on local infrastructure.
Cost Predictability: High-volume quiz generation avoids per-token billing structures.
Network Independence: Local execution guarantees system availability in offline or restricted-network environments.
Ollama serves open-source models—such as llama3.1, mistral, and phi3—via a local REST API endpoint (http://localhost:11434). The Blazor application communicates directly with this API using a standard HttpClient.
┌─────────────────────────────────────────────────────────────┐
│ Blazor Server App (.NET 8) │
│ │
│ File Upload (.pdf / .txt) ──► FileParserService │
│ (iText7 / StreamReader) │
│ │ │
│ ▼ │
│ Extracted Plain Text │
│ │ │
│ ▼ │
│ OllamaService (HttpClient) │
│ │ │
│ ┌────────────────────────┴──────────────┐ │
│ ▼ ▼ │
│ Stage 1: MCQ Generation Stage 2: Expert Review
│ (Strict JSON Output) (Complexity Analysis)
│ └────────────────────────┬──────────────┘ │
│ │ │
│ ▼ │
│ McqTableService │
│ │ │
│ ▼ │
│ Formatted Response UI │
└─────────────────────────────────────────────────────────────┘
The system is divided into three core service layers:
FileParserService (Document Ingestion)The ingestion service extracts text based on file extensions:
PDF Ingestion: Uses iText7 with SimpleTextExtractionStrategy for page-by-page parsing.
Text Ingestion: Uses StreamReader to process raw text files.
public async Task<string> ParseFileAsync(Stream fileStream, string fileName)
{
if (fileName.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase))
return await ParsePdfAsync(fileStream);
else if (fileName.EndsWith(".txt", StringComparison.OrdinalIgnoreCase))
return await ParseTextAsync(fileStream);
else
throw new NotSupportedException("Unsupported file format. Only PDF and TXT files are supported.");
}
Note: To prevent stream buffering issues during Blazor Server uploads, the PDF handler buffers the incoming stream into a byte array prior to processing.
OllamaService (Two-Stage Sequential Chain)Rather than relying on a single, complex system prompt, the system breaks generation into a two-pass sequential chain.
The initial prompt forces the model to return $N$ questions using a defined tone, enforcing strict JSON output:
var quizPrompt = $@"You are an expert MCQ maker. Given the text below, create exactly
{request.Number} multiple choice questions in {request.Tone} tone.
IMPORTANT: You must respond with ONLY valid JSON in the exact format shown below...
Text: {request.Text}
Required JSON format (respond with ONLY this JSON structure):
{request.ResponseJson}";
Data structures map directly to the JSON response using JsonPropertyName attributes to ensure clean deserialization:
public class McqQuestion
{
[JsonPropertyName("no")] public string No { get; set; }
[JsonPropertyName("mcq")] public string Question { get; set; }
[JsonPropertyName("options")] public McqOption Options { get; set; }
[JsonPropertyName("correct")] public string Correct { get; set; }
}
The output of Stage 1 is passed back to the model configured as an educational evaluator. The model assesses cognitive depth and rewrites low-quality items:
"You are an expert English grammarian and writer... evaluate the complexity of the question and give a complete analysis of the quiz... if the quiz is not at par with the cognitive and analytical abilities of the students, update the quiz questions which need to be changed..."
To prevent pipeline failures when LLM outputs deviate from schema guidelines:
Automatic retries execute up to MaxRetries with exponential backoff.
An ExtractJsonFromResponse fallback routine strips conversational headers, recovers JSON substrings, and handles line-by-line reconstruction if required.
var requestBody = new
{
model = _modelName,
prompt = $"{systemPrompt}\n\n{prompt}",
stream = false,
options = new { temperature = temperature, num_predict = maxTokens }
};
var response = await _httpClient.PostAsync($"{_ollamaUrl}/api/generate", content, cts.Token);
McqTableService (Data Normalization)Deserializes the raw JSON response into a structured Dictionary<string, McqQuestion> and flattens it into McqTableData objects for rendering in the Blazor Bootstrap UI.
Ollama installed and running
A supported local LLM model
# Pull preferred base model
ollama pull llama3.1
Clone and Restore:
git clone <your-repo-url>
cd BlazorMcqCreator
dotnet restore
dotnet add package itext7
Configure Endpoint Settings:
Update appsettings.json to reflect your local Ollama instance:
{
"Ollama": {
"BaseUrl": "http://localhost:11434",
"DefaultModel": "llama3.1"
}
}
Run Application:
# Terminal 1
ollama serve
# Terminal 2
dotnet run
Navigate to http://localhost:5000 to upload documents and generate assessment sets.
| Model | Size | Primary Use Case | Performance Rating |
|---|---|---|---|
llama3.1 | 4.7 GB | General-purpose MCQ generation | High |
mistral | 4.1 GB | Academic/educational content | High |
codellama | 3.8 GB | Technical and programming topics | Moderate-High |
phi3 | 2.3 GB | Low-resource / fast generation | Moderate |
Explicit Schemas Prevent Parsing Failures: Explicitly passing structural JSON templates inside the system prompt significantly improves structural adherence over natural language descriptions alone.
Sequential Evaluation Increases Quality: Isolating the critique pass into a secondary LLM call consistently yields better assessment quality than instructing a single call to self-correct during output generation.
Blazor Server Simplifies Local Integration: Leveraging SignalR for state management keeps model execution and API orchestration entirely server-side, keeping client payloads lightweight.
Your email address will not be published. Required fields are marked *