Urgent.News

What's breaking now, across thousands of outlets.

Tech

I built a real quote system with .NET 10 and Blazor Server — and this is what I learned

Hace unas semanas empecé un proyecto personal con un objetivo simple: automatizar el proceso de cotización de una vidriería que hasta entonces armaba cada presupuesto a mano. Cálculos repetidos, formato distinto cada vez, cero historial ordenado. Terminó siendo bastante más de lo que imaginé al principio: un sistema completo de gestión de presupuestos, con catálogo de productos, múltiples precios…

Translated from Spanish Read in Spanish

A few weeks ago, I started a personal project with a simple goal: to automate the quoting process of a glassworks company that, until then, had been preparing each budget by hand. Repeated calculations, different formats each time, zero ordered history. It ended up being much more than I initially imagined: a complete budget management system, with a product catalog, multiple prices per supplier, margin calculation, export to PDF with the actual format already used by the business, and deployment as a service on a local network.

Stack: .NET 10 + Blazor Server — server-rendered interactivity, without the need for a separate API

Entity Framework Core + SQLite

MudBlazor for the interface

QuestPDF for generating the final document

It wasn't a tutorial followed to the letter. It was about solving a real problem at a time. Here are three moments that I thought were interesting to share.

Thinking well about deletion rules, not just the model

When modeling relationships in Entity Framework Core, it's easy to focus only on which field references which other, and leave OnDelete in its default behavior. But each relationship has a business question behind it: what has to happen if I delete this?

In this project, a Client with associated budgets cannot be deleted — we have to avoid losing commercial history by accident. On the other hand, an ItemPresupuesto doesn't make sense to exist without its parent Presupuesto, so if the budget is deleted, its items go with it.

Block 1 — Relationships and deletion rules (VidrieriaContext.cs)

entity.HasOne(p => p.Cliente)

.WithMany(c => c.Presupuestos)

.HasForeignKey(p => p.ClienteId)

.OnDelete(DeleteBehavior.Restrict);

entity.HasOne(p => p.Cotizador)

.WithMany()

.HasForeignKey(p => p.CotizadorId)

.OnDelete(DeleteBehavior.Restrict);

entity.HasOne(i => i.Presupuesto)

.WithMany(p => p.Items)

.HasForeignKey(i => i.PresupuestoId)

.OnDelete(DeleteBehavior.Cascade);

Same tool (OnDelete), two opposite decisions — and both correct, depending on what each relationship represents in the business.

A catalog selector that scales without becoming illegible

The system has categories with subcategories, products within each one, and each product with several reference prices depending on the supplier. With few loaded data, a simple tree works well — but as the catalog grows, navigating by force of clicks becomes tedious and increases the risk of choosing the wrong item.

The solution was a component with a search that filters the entire tree in memory and auto-expands the branches that match:

Block 2 — Recursive filtering (SelectorProductoCatalogo.razor)

private bool CategoriaVisible(Categoria categoria) =>

!HayBusqueda ||

CategoriaNombreCoincide(categoria) ||

productosPorCategoria[categoria.Id].Any(ProductoCoincide) ||

categoriasPorPadre[categoria.Id].Any(CategoriaVisible);

A category is visible if its name matches, if it has a product that matches, or if any of its subcategories is visible (recursively).

That's enough for the tree to be reduced to only the relevant branches when writing in the search — without touching the database on each keystroke, everything solved on data already loaded in memory with ILookup.

The bug that didn't throw any error

This was the most difficult to diagnose. A button stopped responding — without exceptions, without anything in the browser console, without anything on the server. The Blazor circuit was alive, other buttons worked.

The cause: some global components (notification and dialog providers) lived in the root layout of the application, outside the tree of any page with interactive render mode. They were rendered only once, in static mode, and never connected to the real circuit.

Block 3 — The fix of the render mode that left buttons "dead"

The lesson I take away: in Blazor Web App, the render mode is not inherited between "brother" components in the tree — it only propagates to the descendants of the one that declares it.

A small detail, but one that, without understanding it, can make you lose hours.

Closing

None of these problems appeared in a tutorial. They appeared using the real system, with real data, with a real user giving me feedback.

That was the most valuable part of the project: not just writing code that works, but understanding why something didn't work before fixing it.

If you're interested in the architecture, some specific technical detail, or just want to chat about the project, comments are open.

Translated by urgent.news. Machine-written — may contain errors; check the original before relying on it.

Read the original at dev.to →

More in Tech

Claude Code plugin structure: the minimal layout that actually loads

You wrote a skill, it works in ~/.claude/skills/ , and now you want to hand it to a teammate — so you wrap it in a plugin.

  • Minimal Claude Code plugin structure includes plugin root and .claude-plugin subdirectory
  • .claude-plugin directory contains only plugin.json manifest file
  • SKILL.md file can serve as sole skill for single-skill plugin

Escaping the Event Loop — A Deep Dive into worker_threads (Part 3/3)

In part 1, we built the core stack/microtask/macrotask model. In part 2, we saw how the browser and Node implement that model differently — rendering interleaved with tasks in the browser, libuv's…

  • Workerthreads enable CPU-bound tasks parallelization within Node.js process.
  • Main thread remains responsive while offloading specific computations to worker threads.
  • workerthreads offer balance between childprocess isolation and cluster memory sharing.

Keep Docker Engine as Your Kubernetes Runtime on Ubuntu with cri-dockerd

Sometimes you genuinely want Docker Engine as the Kubernetes node runtime — a team standardized on the Docker CLI/API for tooling, an image-build box that doubles as a node, or a legacy playbook you…

  • Install Docker Engine using official repository on Ubuntu
  • Install cri-dockerd adapter for Kubernetes node runtime
  • Verify Kubernetes node status with "docker://" prefix

A/B Test AI Prompts at the Edge with Telnyx Stateful Actors

Changing a prompt is easy. Knowing whether the new prompt is actually better is the hard part. This example builds a small prompt A/B testing API on Telnyx Edge Compute.

  • Telnyx Edge Compute powers A/B test API for AI prompts
  • Users create experiments with two prompt variants and vote on results
  • Stateful Actor stores experiment state without separate database

More from Tuesday 4 August →