Blog
Mert Özen's blog posts on software development, backend, frontend, and DevOps.
State Across Requests in MVC: TempData, Session, Cookies and Query Strings — Which One When?
Use the query string for filters, a consented cookie for preferences, a Data Protection cookie for an anonymous cart, session with Redis for a multi-step form and TempData for flash messages. Running Bookstore.Web on two instances, we show with real output how in-memory session silently resets the form, how separate key rings empty the cart and how cookie consent switches off TempData.
Error Pages in MVC and Razor Pages: UseExceptionHandler, Status Code Pages, Custom 404/500 Views and Error Ids
For pages, use UseExceptionHandler and UseStatusCodePagesWithReExecute: the status code is kept and the address doesn't change, while WithRedirects produces a soft 404 with a 302 and a 200. In Bookstore.Web we show a 404 with suggestions, a 503 page with a trace id, ProblemDetails for /api, IExceptionHandler for domain exceptions and the pitfalls where the error page itself breaks, with real output.
Data Access Options in Node.js: node:sqlite, pg, a Query Builder (Kysely) and an ORM (Prisma) — Which One When?
Data access in Node.js comes in four layers: the built-in node:sqlite for tests and tools, plain SQL with pg for full control, Kysely for compile-time type safety, and Prisma for schemas and migrations. With real output: prices concatenated because numeric arrives as a string, text stored in an INTEGER column, a limit that would not run on SQL Server, a relation difference caught by migrate diff, and an N+1 loop running five queries for four books.
CPU-Bound Work with worker_threads: Workers, MessageChannel, Transferables, Worker Pools (Piscina) and How It Differs from cluster
In Node.js you move CPU-bound work to worker_threads, preferably a worker pool such as Piscina, so the event loop never freezes; large data is transferred as an ArrayBuffer instead of copied, and the worker returns only a summary. With real output: a 947 ms event loop stall versus 11 ms, an 11 ms copy versus a zero-copy transfer, a pooled Buffer that cannot be transferred, a counter losing updates without Atomics, and a new worker per task running 40 times slower.
Everyday Cryptography with node:crypto: randomUUID, Hashes, HMAC, Passwords with scrypt, timingSafeEqual and Web Crypto
In Node.js you verify a webhook signature with createHmac over the raw body and compare it with timingSafeEqual, store passwords with scrypt or the built-in Argon2id, and encrypt sensitive fields with AES-256-GCM and a fresh IV every time. With real output: a signature broken by JSON.stringify, SHA-256 at over two million per second versus 169 ms scrypt, 622 MB for 16 concurrent sign-ins, and a phone number recovered without the key from a fixed IV.
Writing Scripts and CLI Tools in Node.js: process, util.parseArgs, Exit Codes, readline, child_process and Signals
A Node.js CLI parses process.argv with util.parseArgs, writes results to stdout and errors to stderr, exits through process.exitCode instead of process.exit, and calls external commands without a shell through execFile or spawn. With real output: an export file never created because of process.exit, strict parseArgs catching a typo, a confirmation that does not ask without a terminal, a command injection through exec, and a script that deletes its temp file on SIGINT.
HTTP Clients in Node.js: fetch, undici, AbortSignal.timeout, Keep-Alive, Streaming Bodies and Retries
In Node.js you call external services with the global fetch, but it is not safe without a response.ok check, AbortSignal.timeout, a body that is read or cancelled, and retries that respect idempotency. With real output: a catch that never runs on a 404, a request waiting 301 seconds, a connection lost to an unread body, a file streamed in 131 MB instead of 329 MB, retry waves without jitter, and a carrier tracking client tested with MockAgent.
End to End: Bookstore Order Documents — Invoice PDF, Confirmation Email and the Monthly Excel Report as a Job Chain
Connect the order documents in a two-step Hangfire chain: the invoice job writes the PDF to storage, the continuation puts the confirmation email in the outbox; each step's key is the very thing it writes, so repeats are harmless. In Bookstore only the invoice step was retried while storage was down, the same order placed twice gave one PDF and one email, and the monthly report is a recurring job on Istanbul time.
A File Storage Abstraction: Local Disk, Azure Blob Storage and S3; Direct Uploads with Presigned/SAS URLs
Keep files behind one IFileStore: validate the path twice on the local disk, hand out SAS or presigned URLs that live a few minutes for downloads and uploads on Azure Blob and S3, and check an uploaded file only in the "complete" step, because storage looks at neither size nor content. Bookstore's invoices and covers were tested with Azurite and RustFS; metadata stays in the database.
Image Processing in .NET: Resizing, Format Conversion, EXIF and Safe Processing with ImageSharp and SkiaSharp
Validate an uploaded image by its first bytes and pixel count first, then apply the EXIF orientation, shrink it and save it as WebP without metadata. Bookstore's cover thumbnails are made with SkiaSharp (MIT); ImageSharp 4 stays in a Debug-only example, because its Release build needs a license key and it keeps metadata by default.
Generating PDFs Part 2: HTML to PDF with Playwright, Reading and Merging PDFs and Which Approach When
Turn a document the designer built in HTML and CSS into a PDF with Playwright's headless Chromium: share the browser, open a context per document, turn JavaScript off and allow only your own fonts. Bookstore's institutional quote takes 38 milliseconds per document; merging and reading use PdfPig, because PDFsharp moved to the AGPL with 6.5.0.
Generating PDFs Part 1: Invoices and Reports with QuestPDF, Fonts and Turkish Characters
Build the invoice with QuestPDF's fluent layout, repeat the table header on every page, put page numbers in the footer and ship fonts with the app: since 2026.9 system fonts are off by default and a missing font or glyph throws. Bookstore's invoice prints Turkish characters correctly with the bundled Lato, in a container too; the Community License line is a declaration for companies under USD 1 million in annual revenue.