Earn More by Sharing What You Love
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!

Tim Corey
1h 27m 22s
In Tim Corey's "C# App Start To Finish" series, Lesson 09 focuses on wiring up a prize form. On the surface, this form seems simple - just gather user input, validate it, create a model, and save it. But Tim explains that the real complexity lies in deciding where to save the data: a database, a text file, or both. Tim's video walks us through the solution by introducing a core concept in C# programming: interfaces.
In this article, we will take a deeper look at interfaces through Tim's explanation so you can better understand how they help make scalable, maintainable applications.
Tim begins by stating the purpose of the prize form: it accepts input, validates it, and saves it to storage. But he warns that the tricky part is deciding where to store the data. He emphasizes that tutorials often skip this because it is not easy, but he wants learners to tackle it head-on.
He explains that initially, you might attempt a simple solution: check if you are using SQL or a text file, then execute the correct saving process. But Tim quickly shows how ugly and unmaintainable that becomes. If every form has to check which storage type to use, the code becomes duplicated, messy, and difficult to change.
Tim sketches out a pseudo-code example. He explains that you might start by checking a Boolean value like usingSQL == true, then opening a database connection, saving the model, and returning it with an ID. Then you might do the same for text files, manually generating an ID because text files don't do it automatically.
He points out that this quickly becomes repetitive. Multiple forms need this logic, and every time you add a new data source like MySQL, you must update every form. Tim calls this "not scalable" and emphasizes it violates the "DRY" principle (Don't Repeat Yourself). He states clearly: "There's got to be a better way."
Tim introduces his strategy: pulling the thread. He starts by asking what information the code needs and where it comes from. He identifies two key questions:
How do we know which data source to use?
How do we connect to two different data sources to do the same task?
Tim explains that the actual act of saving is the only thing that differs. From the form's perspective, it only needs to say: "Here is the model. Save it." The form should not care whether it's saving to SQL or a text file.
Tim suggests a global configuration system. He says that to know which data source to use, the application needs globally accessible data, and he proposes using a static class to store this information. He acknowledges that global variables are usually avoided, but in this case, global data is exactly what is needed.
Next, Tim explains the key concept: interfaces. He defines an interface as a contract - a promise that any class implementing it will contain certain methods or properties. Tim emphasizes that this allows the application to call the same method regardless of the data source. The form doesn't care if it's SQL or text file; it only cares about calling the method.
Tim says, "If you need to do the same task but behind the scenes it will be done two different ways, you need an interface."
Tim moves to the practical implementation by creating an interface in the Tracker Library. He names it IDataConnection and explains the convention of prefixing interfaces with "I". He highlights that this is important to clearly identify it as an interface.
Tim adds a single method to the interface:
PrizeModel CreatePrize(PrizeModel model);
He explains that this method is a contract: it must exist in any class that implements IDataConnection. The form will call this method and expect to get back a PrizeModel with an ID. Tim explains that this is how the form stays agnostic to the storage type.
Next, Tim creates a static class named GlobalConfig. He explains that a static class cannot be instantiated and is globally accessible. This is where the application will store the list of available data connections.
He defines a property:
public static List<IDataConnection> Connections { get; private set; }
Tim explains the use of private set so that only the class itself can modify the list, while other parts of the application can only read it.
He then creates the method:
public static void InitializeConnections(bool database, bool textFiles)
This method sets up the available data connections. Tim emphasizes that the list allows multiple connections, which means the application can save to SQL, text files, or both.
Tim pauses to reassure learners that this is complex material, but achievable. He recommends watching the video once, then re-watching while coding along.
He explains that the interface is a contract, and any class implementing it must follow the contract. He demonstrates this by creating a SQLConnector class that implements IDataConnection.
When the class is created, Visual Studio warns that the contract is not fulfilled. Tim shows how to use "Implement Interface" to automatically generate the method CreatePrize. He also explains the NotImplementedException scaffold and why it exists - it allows the code to compile without pretending the method works.
Tim adds the SQLConnector class and the TextConnector class, both implementing IDataConnection. He explains that even though saving to a SQL database and saving to a text file are very different processes, they both satisfy the same interface contract.
He adds a simple sample return value for now and places TODO comments to remind himself to implement the real saving logic later. This keeps the application functional while still progressing through the lesson.
Tim returns to the GlobalConfig class and wires up the actual connections. He shows how to initialize the Connections list and add instances of SQLConnector and TextConnector to it.
He explains why two separate if statements are necessary instead of if-else - because the user may want to save to both data sources simultaneously.
Tim explains that InitializeConnections must be called at the start of the application. He modifies Program.cs and calls:
GlobalConfig.InitializeConnections(true, true);
before launching the form. This ensures the connection list is ready and accessible throughout the application.
He then changes the startup form to CreatePrizeForm so he can test the functionality immediately.
Tim opens the form and explains the first task: validating the four fields. He prefers to keep event handlers clean, so he creates a private method named ValidateForm().
Tim explains that this method can be called from anywhere, not just the button click. It returns a Boolean indicating whether the form is valid or not. He shows his pattern of using an output variable:
bool output = true; return output;
He says he likes starting with true because it's easier to change to false when something is wrong than it is to set it to true after every check.
Tim explains the first validation: the place number must be an integer greater than zero.
He uses int.TryParse to convert the PlaceNumberValue.Text (a string) into an integer. Tim breaks down how TryParse works:
Tim emphasizes that TryParse is safer than Parse because it doesn't crash on bad input - it returns false and sets the output to zero.
He then explains the logic:
Tim warns against using else statements here because this method has multiple checks. If one check fails, the method should still evaluate the others to collect all errors.
Tim moves to the next validation: the place name must not be empty.
He checks:
if (placeNameValue.Text.Length == 0) { output = false; }
Tim explains that in a real application, you would display error messages for each failed validation. But for now, he keeps it simple and only returns true/false.
Tim explains that the form must contain either a prize amount or a prize percentage (one of them must be greater than zero). He notes the important difference:
He creates variables:
decimal prizeAmount = 0; int prizePercentage = 0;
Then he uses TryParse for both:
bool prizeAmountValid = decimal.TryParse(prizeAmountValue.Text, out prizeAmount); bool prizePercentageValid = int.TryParse(prizePercentageValue.Text, out prizePercentage);
Tim explains that both must be valid numbers. If either is invalid, the form is invalid.
Next, he checks that at least one is greater than zero:
if (prizeAmount <= 0 && prizePercentage <= 0) { output = false; }
Tim also adds a check to ensure percentage is between 0 and 100:
if (prizePercentage < 0 || prizePercentage > 100) { output = false; }
He explains why: 150% would mean you're giving away more than the prize pool, which is impossible.
After all checks are done, Tim explains how to use the result:
if (ValidateForm()) { // create model and save } else { MessageBox.Show("This form has invalid information. Please check it and try again."); }
Tim notes that you could return early on the first failure, but he chooses to run all checks so users can see all validation errors at once. This reduces frustration because they can fix everything in one go.
Tim explains that once the form is valid, the next step is to create a PrizeModel.
He demonstrates how to instantiate a model:
PrizeModel model = new PrizeModel(); model.PlaceName = placeNameValue.Text; model.PlaceNumber = placeNumberValue.Text; // problem: this is a string
Tim highlights the issue: PlaceNumber is an int, but the form value is a string. To solve this, he explains two options:
Tim chooses option 2.
Tim adds an overloaded constructor that accepts four strings:
public PrizeModel(string placeName, string placeNumber, string prizeAmount, string prizePercentage)
{
PlaceName = placeName;
PlaceNumber = int.TryParse(placeNumber, out int placeNumberValue) ? placeNumberValue : 0;
PrizeAmount = decimal.TryParse(prizeAmount, out decimal prizeAmountValue) ? prizeAmountValue : 0;
PrizePercentage = double.TryParse(prizePercentage, out double prizePercentageValue) ? prizePercentageValue : 0;
}
Tim explains that he doesn't care if the parsing fails because it will default to zero, which is the default value for numbers anyway.
This constructor allows the form to create a PrizeModel directly with string inputs and let the model handle parsing.
Now that the model exists, Tim explains how to save it using the global list of connections.
He uses a foreach loop:
foreach (IDataConnection db in GlobalConfig.Connections) { db.CreatePrize(model); }
Tim explains that this loop calls CreatePrize() on each connection (SQL and text file). Even though the methods aren't implemented yet, the form works and pretends to save the data. This proves that the interface and global configuration pattern is working.
Tim stresses the importance of testing early. He adds breakpoints and runs the application.
Tim demonstrates that the form is functional and the pattern is validated.
Tim makes a couple of final tweaks:
He then confirms the form clears correctly after a valid submission.
Tim ends the video by saying the next step is to wire up the SQL and text connection classes so they actually save the data.
He reminds viewers to stay tuned for the next lesson, where he will implement the SQL connector and actually connect to the database.
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!
Join our newsletter, you’ll get exclusive access on article updates. We value your privacy