Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -476,3 +476,28 @@ $RECYCLE.BIN/
# Windows shortcuts
*.lnk
/HabitTracker.chrisjamiecarter/HabitTracker.UI/HabitTracker.db


#Ignore vscode AI rules
.github/instructions/codacy.instructions.md

## SQLite databases
*.db
*.db-shm
*.db-wal
*.sqlite
*.sqlite3

## Environment / secrets
.env
.env.*
!.env.example
secrets.json
usersecrets.json
**/Properties/launchSettings.json

## JetBrains Rider
.idea/

## User secrets (ASP.NET Core)
**/secrets.json
26 changes: 26 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"cSpell.words": [
"ackages",
"aclocal",
"appxbundle",
"appxupload",
"buildtransitive",
"contentfiles",
"dlldata",
"docstates",
"ebug",
"ehthumbs",
"esktop",
"esult",
"Fody",
"Fractors",
"fseventsd",
"healthcheck",
"healthchecksdb",
"Nsight",
"ntvs",
"userosscache",
"userprefs",
"wwwroot"
]
}
179 changes: 179 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# Water Drinking Logger

A web-based CRUD application for tracking daily water intake, built with
ASP.NET Core Razor Pages and SQLite. Submitted as a solution to the
[C# Academy — Project #24: Water Drinking Logger](https://thecsharpacademy.com/project/24/water-drinking-logger).

---

## Features

- Log water intake entries with a date, quantity, and unit of measure
- View all records in a paginated table
- Edit and delete individual records
- Calculate the total quantity of water logged
- Export all records to an Excel spreadsheet (`.xlsx`)

---

## Project Requirements & How They Are Met

| Requirement | Implementation |
| --- | --- |
| This is a web application | ASP.NET Core Razor Pages web app |
| Users should be able to insert, delete, update and view their drinking water | Full CRUD via Create, Delete, Update, Index Razor Pages |
| You should use Entity Framework Core as your ORM | `WaterLoggerContext` extends `DbContext`; all DB operations use EF Core LINQ methods |
| Your database should be SQLite | `Microsoft.EntityFrameworkCore.Sqlite` provider; DB file stored at `DB/drinking_water` |
| You should have a model to represent the data | `DrinkingWaterModel` with `Id`, `Date`, `Quantity`, and `Measure` fields |

### Challenge — Export to Excel

The application includes an **Export to Spreadsheet** button on the Index page.
Clicking it downloads a `WaterLog.xlsx` file containing all logged records, with
a bold header row and auto-fitted column widths. This is implemented via the
`OnGetExport()` handler in `Index.cshtml.cs` using the **ClosedXML** library.

---

## Tech Stack

### Framework & Runtime

| Tool | Version | Purpose |
| --- | --- | --- |
| [.NET](https://dotnet.microsoft.com/) | 10.0 | Runtime and SDK |
| [ASP.NET Core Razor Pages](https://learn.microsoft.com/aspnet/core/razor-pages/) | 10.0 | Web framework and page routing |

### NuGet Packages

| Package | Version | Purpose |
| --- | --- | --- |
| [Microsoft.EntityFrameworkCore.Sqlite](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.Sqlite) | 9.0.3 | EF Core provider for SQLite |
| [Microsoft.EntityFrameworkCore.Design](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.Design) | 9.0.3 | Design-time EF Core tools (migrations) |
| [ClosedXML](https://www.nuget.org/packages/ClosedXML) | 0.105.0 | Generate `.xlsx` Excel files |

### Frontend Libraries (bundled via `wwwroot/lib/`)

| Library | Purpose |
| --- | --- |
| [Bootstrap 5](https://getbootstrap.com/) | Responsive layout and button styles |
| [jQuery](https://jquery.com/) | DOM manipulation |
| [jQuery Validation](https://jqueryvalidation.org/) | Client-side form validation |
| [jQuery Validation Unobtrusive](https://github.com/aspnet/jquery-validation-unobtrusive) | ASP.NET Core's unobtrusive validation adapter |

### Database

| Tool | Purpose |
| --- | --- |
| [SQLite](https://www.sqlite.org/) | Embedded, file-based relational database (no server required) |

### VS Code Extensions (recommended)

| Extension | Purpose |
| --- | --- |
| [C# Dev Kit](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit) | C# language support, IntelliSense, debugging |
| [SQLite Viewer](https://marketplace.visualstudio.com/items?itemName=qwtel.sqlite-viewer) | Browse the SQLite database file directly in VS Code |

---

## Prerequisites

### Both Platforms

- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)
- Git

### Windows

- Windows 10 / 11 (x64)
- [Visual Studio 2022](https://visualstudio.microsoft.com/) (with **ASP.NET and
web development** workload) **or** [VS Code](https://code.visualstudio.com/)
with the C# Dev Kit extension

### Linux

- Any modern distro (Ubuntu 20.04+, Fedora 36+, Arch, etc.)
- [VS Code](https://code.visualstudio.com/) with the C# Dev Kit extension
(recommended)
- `libssl` and `libicu` — usually pre-installed; if missing, install via your
package manager

---

## Installation & Running

### 1. Clone the repository

```bash
git clone https://github.com/RyanW84/WaterLoggerPractice.git
cd WaterLoggerPractice
```

### 2. Restore dependencies

```bash
dotnet restore WaterLogger.Ryanw84/WaterLogger.Ryanw84.csproj
```

### 3. Run the application

```bash
dotnet run --project WaterLogger.Ryanw84/WaterLogger.Ryanw84.csproj
```

The app will start and print the local URL (usually `http://localhost:5000`).
Open it in your browser.

> **Note:** The SQLite database file is created automatically at
> `WaterLogger.Ryanw84/DB/drinking_water` on first run — no manual setup
> required.

### Windows — running via Visual Studio

1. Open the solution or `WaterLogger.Ryanw84.csproj` in Visual Studio 2022.
2. Press **F5** to build and launch with the built-in browser.

### Linux — common issues

| Issue | Fix |
| --- | --- |
| `dotnet: command not found` | Install the .NET 10 SDK from [dotnet.microsoft.com](https://dotnet.microsoft.com/download) and ensure `~/.dotnet/tools` or `/usr/share/dotnet` is on your `PATH` |
| `libssl` errors on older distros | `sudo apt install libssl-dev libicu-dev` (Debian/Ubuntu) |
| Port already in use | Change the port: `dotnet run --urls "http://localhost:5001"` |

---

## Project Structure

```text
WaterLoggerPractice/
└── WaterLogger.Ryanw84/
├── Data/
│ └── WaterLoggerContext.cs # EF Core DbContext
├── DB/
│ └── drinking_water # SQLite database file (auto-created)
├── Models/
│ └── DrinkingWaterModel.cs # Entity model (maps to the DB table)
├── Pages/
│ ├── Index.cshtml(.cs) # List all records + export
│ ├── Create.cshtml(.cs) # Add a new record
│ ├── Update.cshtml(.cs) # Edit an existing record
│ ├── Delete.cshtml(.cs) # Delete a record
│ └── Shared/
│ └── _Layout.cshtml # Shared Bootstrap layout
├── wwwroot/ # Static assets (CSS, JS, lib)
├── appsettings.json # App config (connection string)
└── Program.cs # App bootstrap and DI setup
```

---

## Usage

| Action | How |
| --- | --- |
| Add a record | Click **Add Record**, fill in the date, quantity, and measure, then submit |
| Edit a record | Click the **pencil icon** on any row |
| Delete a record | Click the **trash icon** on any row and confirm |
| Calculate total | Click **Calculate Total** to sum all quantities |
| Export to Excel | Click **Export to Spreadsheet** to download `WaterLog.xlsx` |
Binary file added WaterLogger.Ryanw84/DB/drinking_water
Binary file not shown.
13 changes: 13 additions & 0 deletions WaterLogger.Ryanw84/Data/WaterLoggerContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Microsoft.EntityFrameworkCore;
using WaterLogger.Ryanw84.Models;

namespace WaterLogger.Ryanw84.Data
{
// DbContext is the EF Core equivalent of manually opening a SqliteConnection.
// It manages the connection, translates LINQ to SQL, and tracks changes.
public class WaterLoggerContext(DbContextOptions<WaterLoggerContext> options) : DbContext(options)
{
// DbSet<T> maps to a table. You query it with LINQ instead of writing SQL strings.
public DbSet<DrinkingWaterModel> DrinkingWater { get; set; }
}
}
20 changes: 20 additions & 0 deletions WaterLogger.Ryanw84/Models/DrinkingWaterModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace WaterLogger.Ryanw84.Models
{
[Table("drinking_water")]
public class DrinkingWaterModel
{
public int Id { get; set; }

[DisplayFormat(DataFormatString = "{0:dd-MM-yy}", ApplyFormatInEditMode = true)]
public DateTime Date { get; set; }

[Range(0, float.MaxValue, ErrorMessage = "Value for {0} must be a positive.")]
public float Quantity { get; set; }

[Required]
public string? Measure { get; set; }
}
}
48 changes: 48 additions & 0 deletions WaterLogger.Ryanw84/Pages/Create.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
@page
@model WaterLogger.Ryanw84.Pages.Create
@{
}

<h2>Add New Record</h2>

<h4>Add Record</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<dl class="row g-3">
<dt class="col-sm-3pe-4"><label asp-for="DrinkingWater.Date" class="control-label"></label></dt>
<dd class="col-sm-9-pe2">
<input type="date" asp-for="DrinkingWater.Date" class="form-control" />
<span asp-validation-for="DrinkingWater.Date" class="text-danger"></span>
</dd>
<dt class="col-sm-3-pe2"><label asp-for="DrinkingWater.Quantity" class="control-label"></label></dt>
<dd class="col-sm-9-pe2">
<input type="number" asp-for="DrinkingWater.Quantity" class="form-control" />
<span asp-validation-for="DrinkingWater.Quantity" class="text-danger"></span>
</dd>
<dt class="col-sm-3-pe2"><label asp-for="DrinkingWater.Measure" class="control-label"></label></dt>
<dd class="col-sm-9">
<select asp-for="DrinkingWater.Measure" class="form-control">
<option value="">Select Measure </option>
<option value="Bottle">Bottle</option>
<option value="Big Bottle">Big Bottle</option>
<option value="Glass">Glass</option>
</select>
<span asp-validation-for="DrinkingWater.Measure" class="text-danger"></span>
</dd>
<dd class="col-sm-9 offset-sm-3">
<input type="submit" value="create" class="btn btn-success" />
</dd>
</dl>
</form>
</div>
</div>

<div>
<a asp-page="Index">Back To List</a>
</div>

@section Scripts{
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
35 changes: 35 additions & 0 deletions WaterLogger.Ryanw84/Pages/Create.cshtml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using WaterLogger.Ryanw84.Data;
using WaterLogger.Ryanw84.Models;

namespace WaterLogger.Ryanw84.Pages
{
public class Create(WaterLoggerContext db) : PageModel
{
private readonly WaterLoggerContext _db = db;

[BindProperty]
public DrinkingWaterModel? DrinkingWater { get; set; }

public IActionResult OnGet()
{
return Page();
}

public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid || DrinkingWater is null)
{
return Page();
}

// Add() stages the new record in EF's change tracker.
// SaveChangesAsync() translates that to: INSERT INTO drinking_water (...) VALUES (...)
_db.DrinkingWater.Add(DrinkingWater);
await _db.SaveChangesAsync();

return RedirectToPage("./Index");
}
}
}
37 changes: 37 additions & 0 deletions WaterLogger.Ryanw84/Pages/Delete.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
@page
@model WaterLogger.Ryanw84.Pages.DeleteModel
@{
}

<h3>Are you sure you want to delete this record?</h3>

<div>
@if (Model?.DrinkingWater == null)
{
<p>Record not found.</p>
<a asp-page="./Index">Back to List</a>
}
else
{
<dl class="row">
<dt class="col-sm-2">
Date
</dt>
<dd class="col-sm-10">
@Model.DrinkingWater?.Date
</dd>
<dt class="col-sm-2">
Quantity
</dt>
<dd class="col-sm-10">
@Model.DrinkingWater?.Quantity
</dd>
</dl>

<form method="post">
<input type="hidden" asp-for="DrinkingWater.Id" />
<input type="submit" value="Delete" class="btn btn-danger" /><br />
<a asp-page="./Index">Back to List</a>
</form>
}
</div>
Loading