Family expense and contribution tracking app built with Blazor Server (.NET 9) and EF Core (SQLite). It lets family members record expenses and contributions, categorize entries, view lists with filters and sorting, and visualize summaries with charts.
- Add
ExpenseandContributionentries viaEntryForm - Manage entries: list, sort, filter, paginate, edit, delete
- Category and user filters (with date range)
- Dashboard with charts:
- Pie: expenses by category
- Bar: expenses vs. contributions by user
- Configurable categories and default currency via
ExpenseOptions - SQLite storage with automatic DB creation and category seeding
- .NET 9, C# 13
- Blazor Server
- EF Core with SQLite
ChartJs.Blazorfor charts
Pages:Expense.razor: add expenseContribution.razor: add contributionList.razor: list and manage entriesEditEntry.razor: edit a specific entryDashboard.razor: charts and summaries
Components:EntryForm.razor: shared entry form component
Services:ExpenseService: CRUD and querying entriesUserService: users listCategoryService: categories listDashboardService: aggregated data for charts
Models:ExpenseEntry,EntryKind,EntryQuery,ExpenseUser,ExpenseCategory
Data:ExpenseDbContext: EF Core DbContext
Options:ExpenseOptions: app configuration (categories, default currency)
- .NET SDK 9
- SQLite (optional; the app creates the DB file automatically)
- Docker Desktop (for containerized run)
Edit appsettings.json:
{
"ConnectionStrings": {
"ExpenseDb": "Data Source=expense.db"
},
"ExpenseOptions": {
"DefaultCurrency": "USD",
"Categories": [
"Groceries",
"Utilities",
"Transport",
"Education",
"Healthcare"
]
}
}Notes:
DefaultCurrencyis used across UI labels and summaries.Categoriesare seeded into theCategoriestable, excluding "Other" and duplicates.
- Restore & build:
dotnet restoredotnet build
- Start the app:
dotnet run
- Open in browser:
https://localhost:****(port shown in console)
On first run, the app:
- Ensures the database is created
- Creates
Categoriestable and unique index - Seeds categories from
ExpenseOptionsand existing entry categories
The repository includes Dockerfile and docker-compose.yml for a one?click containerized run on port 9000.
- Windows/macOS/Linux with Docker installed
- In the repo root (same folder as
docker-compose.yml), run:
docker compose up --build -dThen open:
http://localhost:9000
- The container listens on port 9000 (
ASPNETCORE_URLS=http://0.0.0.0:9000). - A named volume
expense-datastores the SQLite DB at/data/expense.db. - Environment overrides in compose:
ASPNETCORE_ENVIRONMENT=ProductionConnectionStrings__ExpenseDb=Data Source=/data/expense.db
Use the helper script to bind?mount an existing or new DB file and start Compose:
- Run from repo root or any folder:
powershell -ExecutionPolicy Bypass -File scripts/start-compose.ps1- Or provide the path:
powershell -ExecutionPolicy Bypass -File scripts/start-compose.ps1 -DbPath "C:\\DataLab\\expense.db"
- The script will:
- Prompt for the DB path if not provided
- Ensure the directory/file exist
- Write
.envin repo root withEXPENSE_DB_HOST_PATH=<your path>(UTF?8 without BOM) - Create/update
docker-compose.override.ymlto add:- "${EXPENSE_DB_HOST_PATH}:/data/expense.db"
- Run
docker compose up --build -d
To change the DB file later, rerun the script with a new path. To switch back to the named volume, delete docker-compose.override.yml (and optionally the .env entry).
- Stop:
docker compose down - View logs:
docker compose logs -f - Rebuild after code changes:
docker compose up --build -d
- Launch profile
Container (Dockerfile)runs and attaches the VS debugger. - VS may expose 80/443 by default. To use port 9000 consistently, prefer running via Compose and attach the debugger:
- Build and start:
docker compose up --build -d - In Visual Studio: Debug > Attach to Process… > Connection type: Docker > Select the
expense-reportcontainer > pick thedotnetprocess.
- Build and start:
- Alternatively, adjust the VS Dockerfile to target .NET 9 and expose port 9000.
- Add expense: navigate to
/or/expense - Add contribution: navigate to
/contribution - List entries:
/list- Filter by
Type,Category,User,From/Todates - Sort by
Date,Amount,UserName(toggle direction) - Paginate with page size controls
- Edit/Delete actions per entry
- Filter by
- Dashboard:
/dashboard- Pie chart for expenses by category
- Bar chart comparing expenses vs contributions by user
- Date range filter with validation
ExpenseEntry:Id,Date,EntryKind,UserId,User,Category,Amount,Details
EntryKind:Expense,Contribution
EntryQuery:- Filtering, sorting, pagination parameters for listing
ExpenseUser/ExpenseCategory:- Basic user and category records
ExpenseService:GetEntriesAsync(EntryQuery): returns paged results with filters/sortingDeleteAsync(id), create/update methods used byEntryForm
DashboardService:GetSummaryAsync(start, end): returns totals by category, by user, and per-user expense/contribution amounts
CategoryService/UserService:- Provide categories and users for filters and forms
Dashboard.razorbuilds chart configs usingChartJs.Blazor:- Pie (
PieConfig) for category totals with color mapping - Bar (
BarConfig) for per-user expenses vs contributions
- Pie (
- Add or rename categories via
ExpenseOptions.Categoriesinappsettings.json - Change default currency via
ExpenseOptions.DefaultCurrency - Update color palette in
Dashboard.razor(_chartColors)
- Compose cannot find files:
- Run from the repo root (where
docker-compose.ymlresides). - Ensure
.envanddocker-compose.override.ymlare in the same folder asdocker-compose.yml.
- Run from the repo root (where
- Port shows 80/443 instead of 9000 in VS:
- VS Docker profiles default to 80/443. Use Compose and attach the debugger to the container for port 9000.
- YAML errors like "did not find expected '-' indicator":
- Use 2?space indentation; make sure a dash
-precedes the bind mount line undervolumes:.
- Use 2?space indentation; make sure a dash