C# Beginner Capstone: Build a Study Tracker
Plan, build, test, and extend a file-backed console application that combines the complete beginner course without hiding the design decisions.
Before this lesson
- All earlier lessons, especially objects, collections, files, exceptions, LINQ, and project organization
Turn requirements into data, operations, and failure cases
Build a usable feature end to end before adding another
Evaluate the finished program with behavior-based tests and a next-step plan
The short answer
Build the capstone in vertical slices: define one user outcome, model valid data, implement the rule in memory, add a console path, persist it, test boundary cases, then refactor only where the working code reveals a useful boundary.
Start from user outcomes, not a list of language features
The study tracker should let one user record a session, list sessions, view totals by topic, save data, and reopen it later. Those are observable outcomes. ‘Use inheritance’ is not a requirement; add a language feature only when it helps implement or protect an outcome.
Write acceptance examples before code. Recording C# for 30 minutes should add one session. Blank topic or zero minutes should be rejected. After two C# sessions of 30 and 20 minutes, the C# total should be 50. Restarting should preserve successfully saved sessions.
- Record a topic, positive duration, and timestamp.
- List all sessions in a readable order.
- Show total minutes overall and grouped by topic.
- Persist data to a file and recover from a missing first-run file.
- Reject malformed input without terminating the menu.
- Allow the user to quit explicitly.
Build the first vertical slice in memory
First create StudySession with constructor validation, a List<StudySession>, and an Add session menu path. Do not add file formats, interfaces, and async I/O before one valid session can travel from input through parsing into the collection and back to visible output.
Then add listing and summaries. A loop can print sessions; LINQ can group by normalized topic and sum minutes. Keep normalization policy explicit—perhaps trim surrounding spaces and compare topics case-insensitively—so C# and c# do not accidentally become separate report categories.
var totalsByTopic = sessions
.GroupBy(session => session.Topic, StringComparer.OrdinalIgnoreCase)
.Select(group => new
{
Topic = group.Key,
Minutes = group.Sum(session => session.Minutes)
})
.OrderByDescending(item => item.Minutes);Add persistence behind a clear boundary
Choose a file format with an explicit schema. A beginner version can use one escaped or serialized record per line. Hand-splitting comma-separated text fails when topics contain commas, so use a real CSV library for CSV or System.Text.Json in a current local .NET project for JSON.
A repository class can own Load and Save while the domain model owns valid session rules. Treat a missing data file as an empty first run, but treat malformed existing content as a visible problem instead of silently discarding the user's history. Save through a temporary file and replace the destination when durability matters.
| Concern | Owner | Reason |
|---|---|---|
| Prompt and menu | Console UI / Program | Human interaction changes independently |
| Positive duration and valid topic | StudySession | Object must protect its invariant |
| Totals by topic | Study log/service | Business query over sessions |
| File path and serialization | Repository | Storage is an external boundary |
Test behavior, boundaries, and recovery
Manually test blank text, non-numeric duration, zero, one, a large allowed value, repeated topics with different case, an empty first run, an unreadable file, and a malformed record. Automated tests should cover constructor rules and summary calculations without depending on Console.
When a test is difficult to write, inspect whether input, storage, and calculation are mixed. Extract a boundary because it clarifies ownership, not merely to satisfy a mocking tool. Keep an end-to-end manual checklist because unit tests do not prove that prompts, paths, and serialization work together.
Finish with a useful review, not endless features
A complete beginner project has a documented run command, understandable structure, reliable core flows, and no known data-loss path for ordinary use. It does not need accounts, a database, a web UI, cloud deployment, and ten design patterns. Finish the promised scope before expanding it.
Afterward, choose a direction. For backend development, build an ASP.NET Core API over similar domain rules. For desktop or cross-platform UI, study a current .NET UI framework. For games, learn Unity's component model. In every path, add Git, automated testing, HTTP, JSON, dependency injection, databases, and security through real projects rather than disconnected syntax pages.
Good habits
- Commit a working slice before structural refactors.
- Keep a small sample data file that contains edge cases but no private information.
- Write down format and normalization decisions so future changes preserve compatibility.
- Measure completion by user outcomes, not the number of classes created.
- Ask another person to run the project from the instructions without your help.
Quick knowledge check
Answer before you reveal.
01Should you create every planned interface before the first feature works?
No. Build a vertical slice, then add boundaries where actual variation, ownership, or testing pressure makes them useful.
02What makes this course ‘complete’ for a beginner?
You can independently model, build, debug, persist, and extend a small program, and you understand which intermediate path to take next—not that every C# feature has been memorized.
Practice challenge
Now build it without copying.
Build the Study Tracker as a local .NET project with add, list, summary, save, load, and quit flows. Use the acceptance examples in this lesson as the definition of done.
You are done when
- A new user can build and run the project from written instructions
- Invalid input returns to the menu with an actionable message
- Valid sessions survive a restart
- Totals are correct for empty, single-topic, and repeated-topic data
- Domain calculations can be tested without Console or a real file
- The project contains no secrets, machine-specific absolute paths, or swallowed exceptions
Stretch: Replace synchronous persistence with async file APIs, propagate CancellationToken where useful, and explain why the small local app may not become visibly faster.
Open challenge in playgroundLesson checkpoint
One small step locks it in
Mark this lesson complete, then keep the momentum going.
Clear up the details
Frequently asked questions
Should the capstone use a database?
Not for the required version. A file keeps focus on language, modeling, validation, and failure handling. Add SQLite later when you are ready to learn queries, schema changes, and transactions.
Can I choose a different capstone domain?
Yes. An expense log, reading tracker, inventory, or habit log can use the same outcomes. Keep the scope and acceptance criteria equivalent.
What should I learn immediately after this course?
Build another small project without following a step-by-step tutorial, learn Git and automated tests, then choose a framework path such as ASP.NET Core, a .NET UI framework, or Unity based on your goal.