Skip to content

Instantly share code, notes, and snippets.

@sirojiddin0198
Created August 26, 2025 03:23
Show Gist options
  • Select an option

  • Save sirojiddin0198/45cf57b88d6f51a440cee78b6b904c5d to your computer and use it in GitHub Desktop.

Select an option

Save sirojiddin0198/45cf57b88d6f51a440cee78b6b904c5d to your computer and use it in GitHub Desktop.
Middleware

Product Requirements Document (PRD): Library Book Management API Test Project

Introduction

As your instructor for the .NET course, I am assigning this test project to assess your proficiency in developing a fully functional CRUD application using ASP.NET Core, with a focus on implementing custom middlewares. This project will evaluate your ability to create a RESTful API, manage database operations with PostgreSQL, and integrate custom middlewares to enhance functionality, security, and maintainability.

The project is a Library Book Management API, a comprehensive CRUD application for managing books in a library inventory. You will build an ASP.NET Core Web API that interacts with a PostgreSQL database to perform Create, Read, Update, and Delete operations on books. You must implement three custom middlewares as specified below.

This PRD provides detailed requirements, including explanations for each CRUD operation, implementation instructions, and the custom middlewares. You are expected to deliver a working project that meets all criteria, submitted as a GitHub repository with a README.md file explaining setup and execution.

Project Overview

  • Technology Stack:
    • ASP.NET Core 8.0 (or latest stable version).
    • Entity Framework Core with Npgsql provider for PostgreSQL database interactions.
    • PostgreSQL as the database.
    • Use JSON for request/response payloads.
    • Implement proper error handling and HTTP status codes.
  • Project Structure:
    • Organize the solution with separate projects/folders for the API, models, services, and repositories.
    • Use dependency injection for services and repositories.
    • Include Swagger/OpenAPI for API documentation and testing.
  • Database Schema:
    • Create a Book entity with the following properties:
      • Id (integer, primary key, auto-increment using SERIAL).
      • Title (varchar(200), not null).
      • Author (varchar(100), not null).
      • ISBN (varchar(13), not null, unique).
      • PublicationYear (integer, not null).
      • Genre (varchar(50), nullable).
      • QuantityAvailable (integer, not null, default 1).
    • Use Entity Framework Core migrations to set up the PostgreSQL schema.
  • Authentication/Authorization:
    • Implement API key-based authentication for simplicity. The Authorization middleware will validate a custom API key header.
  • General Instructions:
    • All API endpoints should be under the route prefix /api/books.
    • Validate inputs using data annotations and return appropriate error messages (e.g., 400 Bad Request for invalid data).
    • Handle concurrency with optimistic locking if applicable (optional but bonus).
    • Ensure the API is testable via tools like Postman or Swagger.
    • Write unit tests for at least the service layer and middlewares (coverage of 50% or more is expected).
    • The application should run on HTTPS by default.
    • Include PostgreSQL connection string configuration in appsettings.json.

Detailed Requirements

CRUD Operations

You must implement a fully working CRUD API for managing books. Each operation is detailed below, including the HTTP method, endpoint, request/response details, and expected behavior.

  1. Create (POST):

    • Endpoint: POST /api/books
    • Description: Allows authorized users to add a new book to the library inventory. Validates input data, checks for duplicate ISBNs, and persists the book to the PostgreSQL database.
    • Request Body: JSON object representing the book (exclude Id as it’s auto-generated).
      • Example:
        {
          "title": "The Great Gatsby",
          "author": "F. Scott Fitzgerald",
          "isbn": "9780743273565",
          "publicationYear": 1925,
          "genre": "Fiction",
          "quantityAvailable": 5
        }
    • Response:
      • 201 Created: Returns the created book with its generated Id, including a Location header pointing to the GET endpoint for the new book.
      • 400 Bad Request: If validation fails (e.g., missing required fields, invalid ISBN format).
      • 409 Conflict: If a book with the same ISBN already exists.
      • 401 Unauthorized: If the API key is invalid (handled by middleware).
    • Implementation Instructions:
      • Use a service layer to handle business logic (e.g., check for existing ISBN using EF Core).
      • Configure EF Core with Npgsql to add the entity and save changes asynchronously.
      • Return the created entity in the response body.
  2. Read (GET):

    • Endpoint: GET /api/books (list all books) and GET /api/books/{id} (single book).
    • Description:
      • The list endpoint retrieves all books or supports query parameters for filtering (e.g., by genre or author).
      • The single book endpoint retrieves a specific book by its ID.
    • Query Parameters (for list):
      • genre (optional): Filter by genre (case-insensitive).
      • author (optional): Filter by author (partial match).
      • page (optional, default 1): For pagination.
      • pageSize (optional, default 10): Number of items per page.
    • Response:
      • 200 OK: Array of books for the list or a single book object. Include pagination metadata in headers or response (e.g., total count).
      • 404 Not Found: If the book ID does not exist.
      • 401 Unauthorized: If the API key is invalid.
    • Implementation Instructions:
      • Use LINQ queries with EF Core to filter and paginate results.
      • Implement pagination using Skip and Take methods.
      • Optimize queries to avoid loading unnecessary data (e.g., use .AsNoTracking() for read-only operations).
  3. Update (PUT or PATCH):

    • Endpoint: PUT /api/books/{id} (full update) or PATCH /api/books/{id} (partial update; implement at least one, bonus for both).
    • Description: Allows authorized users to modify an existing book’s details. For PUT, the entire object must be provided; for PATCH, only changed fields.
    • Request Body: JSON object with updated fields.
      • Example for PUT:
        {
          "id": 1,
          "title": "Updated Title",
          "author": "F. Scott Fitzgerald",
          "isbn": "9780743273565",
          "publicationYear": 1925,
          "genre": "Classic Fiction",
          "quantityAvailable": 3
        }
    • Response:
      • 200 OK or 204 No Content: Returns the updated book or no content.
      • 400 Bad Request: If validation fails or ID in body mismatches URL.
      • 404 Not Found: If the book does not exist.
      • 409 Conflict: If ISBN is changed to an existing one.
      • 401 Unauthorized: If the API key is invalid.
    • Implementation Instructions:
      • Retrieve the existing entity using EF Core, update properties, and save changes.
      • Handle concurrency with a row version or timestamp column if implemented.
      • For PATCH, use JsonPatchDocument if chosen.
  4. Delete (DELETE):

    • Endpoint: DELETE /api/books/{id}
    • Description: Allows authorized users to remove a book from the inventory. Checks if the book exists before deletion.
    • Request Body: None.
    • Response:
      • 204 No Content: On successful deletion.
      • 404 Not Found: If the book does not exist.
      • 401 Unauthorized: If the API key is invalid.
    • Implementation Instructions:
      • Retrieve the entity by ID using EF Core, remove it if found, and save changes.
      • Optionally, implement soft delete with an IsDeleted flag (bonus).

Custom Middlewares

You must implement three custom middlewares in the ASP.NET Core pipeline. Each should be a separate class implementing IMiddleware or using the delegate pattern. Register them in Program.cs and explain the order in your README.

  1. Request Logging Middleware:

    • Purpose: Logs details of incoming requests and outgoing responses for auditing and debugging.
    • How it Should Work:
      • On request: Log the HTTP method, path, query string, and headers (exclude sensitive headers like X-Api-Key).
      • Enable request body buffering to read JSON body without consuming it.
      • On response: Log the status code, response time (using a stopwatch), and response body if JSON.
      • Use ILogger to output logs to console or a file (e.g., via Serilog).
      • Truncate large request/response bodies to avoid excessive logging.
      • Place this middleware first in the pipeline to capture all requests.
    • Implementation Instructions:
      • Create a class (e.g., RequestLoggingMiddleware) with InvokeAsync.
      • Use HttpContext.Request.EnableBuffering() to read the request body.
      • Log using ILogger with appropriate log levels (e.g., Information for normal logs, Error for issues).
  2. Authorization Middleware:

    • Purpose: Authorizes users by validating an API key provided in a custom header.
    • How it Should Work:
      • Check for a custom header X-Api-Key in the request.
      • Validate the API key against a predefined value stored in configuration (e.g., appsettings.json or environment variable).
      • If valid, proceed to the next middleware.
      • If missing or invalid, return 401 Unauthorized with a WWW-Authenticate header (e.g., Bearer error="invalid_api_key").
      • Protect all CRUD endpoints; no public endpoints are required for this project.
      • Place after the logging middleware.
    • Implementation Instructions:
      • Create a class (e.g., ApiKeyAuthorizationMiddleware) with InvokeAsync.
      • Read the API key from HttpContext.Request.Headers["X-Api-Key"].
      • Compare with a configured key (e.g., IConfiguration["ApiKey"]).
      • Set response status and headers on failure.
  3. Exception Handling Middleware:

    • Purpose: Catches unhandled exceptions globally and returns consistent error responses.
    • How it Should Work:
      • Wrap the next middleware in a try-catch block.
      • On exception: Log the error (message, stack trace) using ILogger.
      • Return a JSON error response (e.g., { "error": "Internal Server Error", "details": "..." }) with appropriate status code:
        • 500 for general exceptions.
        • Map specific exceptions (e.g., KeyNotFoundException to 404).
      • In development, include detailed error info; in production, limit to generic messages.
      • Place early in the pipeline to catch all exceptions.
    • Implementation Instructions:
      • Create a class (e.g., ExceptionHandlingMiddleware) with InvokeAsync.
      • Use ILogger to log exceptions.
      • Serialize error responses using System.Text.Json.

Rubrics

Your submission will be graded out of 100 points based on the following criteria. Provide evidence in your README or code comments.

  • CRUD Implementation (40 points):

    • Create: 10 points (correct endpoint, validation, duplicate ISBN check).
    • Read: 10 points (list with filters/pagination, single retrieval).
    • Update: 10 points (full/partial update, conflict handling).
    • Delete: 10 points (existence check, successful removal).
  • Custom Middlewares (30 points):

    • Request Logging: 10 points (logs request/response details correctly, handles body reading).
    • Authorization: 10 points (validates X-Api-Key, protects endpoints, proper 401 response).
    • Exception Handling: 10 points (catches errors, logs, returns consistent JSON responses).
  • Database and Architecture (15 points):

    • Proper EF Core setup with Npgsql, migrations, and schema (5 points).
    • Clean architecture (services, repositories, DI) (5 points).
    • Input validation and error handling (5 points).
  • Testing and Documentation (10 points):

    • Unit tests for services and middlewares (5 points).
    • Swagger integration and README with PostgreSQL setup instructions (5 points).
  • Overall Quality (5 points):

    • Code cleanliness, REST principles, performance (e.g., efficient queries).
  • Bonus (up to 10 points):

    • Features like JWT authentication, soft delete, caching, or full PATCH support.

Submit by [due date]. Late submissions deduct 10 points per day. Ask questions in class or office hours.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment