I'm always excited to take on new projects and collaborate with innovative minds.

Social Links

Building an AI-Powered MCQ Generator with Blazor Server & Ollama

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.

The Advantages of Local LLM Deployment

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.

Solution Architecture

┌─────────────────────────────────────────────────────────────┐
│                 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:

1. 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.

C#
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.

2. OllamaService (Two-Stage Sequential Chain)

Rather than relying on a single, complex system prompt, the system breaks generation into a two-pass sequential chain.

Stage 1: MCQ Generation

The initial prompt forces the model to return $N$ questions using a defined tone, enforcing strict JSON output:

C#
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:

C#
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; }
}

Stage 2: Expert Review Pass

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..."

Fault Tolerance & Recovery

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.

C#
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);

3. 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.

Local Setup & Quickstart

Prerequisites

Bash
# Pull preferred base model
ollama pull llama3.1

Installation Steps

  1. Clone and Restore:

    Bash
    git clone <your-repo-url>
    cd BlazorMcqCreator
    dotnet restore
    dotnet add package itext7
    
  2. Configure Endpoint Settings:

    Update appsettings.json to reflect your local Ollama instance:

    JSON
    {
      "Ollama": {
        "BaseUrl": "http://localhost:11434",
        "DefaultModel": "llama3.1"
      }
    }
    
  3. Run Application:

    Bash
    # Terminal 1
    ollama serve
    
    # Terminal 2
    dotnet run
    

Navigate to http://localhost:5000 to upload documents and generate assessment sets.

Model Selection Matrix

ModelSizePrimary Use CasePerformance Rating
llama3.14.7 GBGeneral-purpose MCQ generationHigh
mistral4.1 GBAcademic/educational contentHigh
codellama3.8 GBTechnical and programming topicsModerate-High
phi32.3 GBLow-resource / fast generationModerate

Key Design Insights

  1. Explicit Schemas Prevent Parsing Failures: Explicitly passing structural JSON templates inside the system prompt significantly improves structural adherence over natural language descriptions alone.

  2. 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.

  3. Blazor Server Simplifies Local Integration: Leveraging SignalR for state management keeps model execution and API orchestration entirely server-side, keeping client payloads lightweight.
     

5 min read
Aug 17, 2025
By Dheer Gupta
Share

Leave a comment

Your email address will not be published. Required fields are marked *