Subsections of CIS 400: Object-Oriented Design, Implementation, and Testing

Chapter 0

Course Information

Getting Oriented

Web Only

This textbook was authored for the CIS 400 - Object-Oriented Design, Implementation, and Testing course at Kansas State University. This front matter is specific to that course. If you are not enrolled in the course, please disregard this section.

Subsections of Course Information

Course Structure

Web Only

This textbook was authored for the CIS 400 - Object-Oriented Design, Implementation, and Testing course at Kansas State University. This front matter is specific to that course. If you are not enrolled in the course, please disregard this section.

This course is taught in the “flipped” style. This means you will be watching videos and working through tutorials before you come to class. Your class sessions will be used for asking questions, working on and getting help with your projects, and taking exams.

The Big Software Solution

Up to this point, you’ve likely done a lot of what I like to call “Baby Projects” - programming projects that are useful to demonstrate a concept or technique, but really don’t do anything beyond that. In contrast, we’ll be building a large, multi-project software solution to meet a real-world problem - the software needed to run a fast-food franchise chain!

We’ll be building this software iteratively over the entire semester! Each week, you’ll turn in one milestone as a GitHub release, just like you might do as a professional software developer. Because each milestone builds upon your prior work, it is critical that you keep up. Falling behind will very quickly tank your grade and impact your ability to learn and develop strong programming skills.

Modules

The course is organized into modules focused on a specific topic, accessible from the Canvas modules menu. Each module introduces one or more topics, and 1) covers the vocabulary and concepts with assigned readings, 2) puts those concepts into practice with guided tutorials, and 3) tasks you with applying those techniques you just practiced in a weekly milestone.

Info

Most non-exam weeks will consist of two video-guided tutorials and the weekly milestone. The first module will be due by Monday, the second Tuesday, and the milestone on Friday. Each regular tutorial will take several hours to complete, and milestones can often take ten hours or more. You should plan your learning schedule accordingly.

The modules, and all of their associated assignments, are available through Canvas. You must complete each module item in order, and the prior week’s module must be finished before you can move on to those in the next week. Be aware that if you procrastinate and don’t start until Friday, it is unlikely that you will finish. Which means you will fall behind. You can very quickly find yourself in a hole you cannot climb out of. So time management is a critical skill you need to be developing.

Warning

Remember that in the CS Professional Program, a grade of less than C in a professional program course (like CIS 400) results in a “Warning of Unsatisfactory Progress.” This warning sticks with you the rest of your time in Computer Science at K-State. If you earn a second, similar grade, you will be dismissed from the CS Professional Program!

Where to Find Help

Web Only

This textbook was authored for the CIS 400 - Object-Oriented Design, Implementation, and Testing course at Kansas State University. This front matter is specific to that course. If you are not enrolled in the course, please disregard this section.

As you work on the materials in this course, you may run into questions or problems and need assistance.

Course Sessions

As mentioned before, the course sessions are one of the best time to get help with your assignments - during this time the instructor and TAs are scheduled to be available and on-hand in the computer lab.

Discord

For questions that crop up outside of class times, your first line of communication for this course is the departmental Discord server. If you have not yet signed into the course discord channel, or are not yet a Discord user, please visit https://discordbot.cs.ksu.edu. This assistant will link your K-State and Discord accounts and set your username for the server in accordance with K-State policy.

Tip

In addition to class channels, the Discord server hosts channels for student clubs, announcements, and general discussion. It is a good place to find information and socialize when you are unable to meet face-to-face.

Course Channel

Discord uses channels - the equivalent of chat rooms - to provide a place for related conversations. The channel for this course is #cis-400. It is the best place to go to get help with anything related to this course, from the tutorials and projects to issues with Visual Studio and Canvas. Before you post on Discord, use the search feature in the channel or scroll back in the chat history to make sure the question has not already been posted before. It will save everyone quite a bit of time.

Additionally, all course announcements will be made in the course channel (as well as through the Canvas announcements), so make a habit of checking the channel regularly.

Tip

The helping hand extra credit assignment provides bonus points for students who are caught helping other students in the class Discord channel.

Other Features

Discord includes lots of useful features:

  • Use the @ symbol with a username in a message to create a mention, which notifies that user immediately, i.e. @Nathan Bean (he/him) will alert me that you’ve made a post that mentions me.
  • Use Shift+Enter for new lines in a multi-line message
  • Use the backtick mark (, i.e. `var c = 4;`) to enclose code snippets to format them as programming code, and triple backtick marks to enclose multiline code comments (```[multiline code]```).
  • You can also set your status to indicate your current availability.

Email

The Discord class channel is the preferred communication medium for the course because 1) you will generally get a faster response than email, and 2) writing code in email is a terrible experience, both to write and to read. Discord’s support of markdown syntax makes including code comments much easier on both of us.

For general questions, asking them in the course channel will give you the best chance of a fast answer, from the course instructors, TAs, or your fellow students. And if you help a fellow student, you might get bonus points!

If you have a more personal question, however, you are welcome to email the instructor.

Other Avenues for Help

There are a few resources available to you that you should be aware of. First, if you have any issues working with K-State Canvas, K-State IT resources, or any other technology related to the delivery of the course, your first source of help is the K-State IT Helpdesk. They can easily be reached via email at helpdesk@ksu.edu. Beyond them, there are many online resources for using Canvas, all of which are linked in the resources section below the video. As a last resort, you may also want to post in Discord, but in most cases we may simply redirect you to the K-State helpdesk for assistance.

If you have issues with the technical content of the course, specifically related to completing the tutorials and projects, there are several resources available to you. First and foremost, make sure you consult the vast amount of material available in the course modules, including the links to resources. Usually, most answers you need can be found there.

If you are still stuck or unsure of where to go, the next best thing is to post your question on Discord, or review existing discussions for a possible answer. You can find the link to the left of this video. As discussed earlier, the instructors, TAs, and fellow students can all help answer your question quickly.

Of course, as another step you can always exercise your information-gathering skills and use online search tools such as Google to answer your question. While you are not allowed to search online for direct solutions to assignments or projects, you are more than welcome to use Google to access programming resources such as the Microsoft Developer Network, C# language documentation, WPF-tutorial.com, and other tutorials. I can definitely assure you that programmers working in industry are often using Google and other online resources to solve problems, so there is no reason why you shouldn’t start building that skill now.

Next, we have grading and administrative issues. This could include problems or mistakes in the grade you received on a project, missing course resources, or any concerns you have regarding the course and the conduct of myself and your peers. You’ll be interacting with us on a variety of online platforms and sometimes things happen that are inappropriate or offensive. There are lots of resources at K-State to help you with those situations. First and foremost, please DM me on Discord as soon as possible and let me know about your concern, if it is appropriate for me to be involved. If not, or if you’d rather talk with someone other than me about your issue, I encourage you to contact either your academic advisor, the CS department staff, College of Engineering Student Services, or the K-State Office of Student Life. Finally, if you have any concerns that you feel should be reported to K-State, you can do so at https://www.k-state.edu/report/. That site also has links to a large number of resources at K-State that you can use when you need help.

Finally, if you find any errors or omissions in the course content, or have suggestions for additional resources to include in the course, either post on Discord or email the instructor. There are some extra credit points available for helping to improve the course, so be on the lookout for anything that you feel could be changed or improved.

Info

The Bug Bounty extra credit assignment gives points for finding errors in the course materials. Remember, your instructors are human, and do make mistakes! But we don’t want those occasional mistakes to trip you and your peers up in your learning efforts, so bringing them to our attention is appreciated.

So, in summary, Discord should always be your first stop when you have a question or run into a problem. For issues with Canvas or Visual Studio, you are also welcome to refer directly to the resources for those platforms. For questions specifically related to the projects, use Discord for sure. For grading questions and errors in the course content or any other issues, please email the instructor for assistance.

Our goal in this program is to make sure that you have the resources available to you to be successful. Please don’t be afraid to take advantage of them and ask questions whenever you want.

Resources

What You'll Learn

Web Only

This textbook was authored for the CIS 400 - Object-Oriented Design, Implementation, and Testing course at Kansas State University. This front matter is specific to that course. If you are not enrolled in the course, please disregard this section.

The following is an outline of the topics we will be covering and when.

Info

Please be aware that this schedule and topic list will likely undergo some revision during the semester.

Schedule

Week 1

  • Introduction to the Course
  • Pretest
  • Setting the Stage (The context in which object-orientation emerged)
  • Git and GitHub
  • Milestone 0

Week 2

  • Encapsulation
  • Milestone 1

Week 3

  • Classes and Objects
  • Documentation
  • Milestone 2

Week 4

  • Polymorphism
  • UML
  • Milestone 3

Week 5

  • Testing
  • Advanced C# Syntax
  • Milestone 4

Week 6

  • Exam I

Week 7

  • Windows Presentation Foundation
  • The Elements Tree
  • Milestone 5

Week 8

  • Events
  • Data Binding
  • Milestone 6

Week 9

  • Testing WPF Apps
  • Milestone 7

Week 10

  • Dependency Objects
  • MVVM
  • Milestone 8

Week 11

  • Exam II

Week 12

  • Core Web Technologies
  • ASP.NET
  • Milestone 9

Week 13

  • Web Forms
  • LINQ
  • Milestone 10

Week 14

  • Web APIs
  • Milestone 11

Week 15

  • Deployment
  • Milestone 12

Week 16

  • Final Exam

Course Textbooks

Web Only

This textbook was authored for the CIS 400 - Object-Oriented Design, Implementation, and Testing course at Kansas State University. This front matter is specific to that course. If you are not enrolled in the course, please disregard this section.

This course does not have a required print textbook. The resources presented in the modules are also organized into an online textbook that can be accessed at https://textbooks.cs.ksu.edu/cis400. You may find this a useful reference if you prefer a traditional textbook layout. Additionally, since the textbook exists outside of Canvas’ access control, you can continue to utilize it after the course ends.

Warning

Please note that the materials presented in Canvas have additional graded assignments and exercises worked into the reading order that do not appear in the online edition of the textbook. You are responsible for completing these!

CS Departmental Textbook Server

The CIS 400 course textbook is only one of several textbooks authored by your instructors and made available on the departmental server. For example, your CIS 300 textbook is also available there for you to go back and review. You can access any of these textbooks at the site https://textbooks.cs.ksu.edu

O’Riley for Higher Education

If you are looking for additional resources to support your learning, a great resource that is available to Kansas State University students is the O’Riley For Higher Education digital library offered through the Kansas State University Library. These include electronic editions of thousands of popular textbooks as well as videos and tutorials. As of this writing, a search for object-orientation returns 13,237 results and C# returns 5,984 results. In particular, I would recommend these books:

There are likewise materials for other computer science topics you may have an interest in - it is a great resource for all your CS coursework. It costs you nothing (technically, your access was paid for by your tuition and fees), so you might as well make use of it!

Course Software

Web Only

This textbook was authored for the CIS 400 - Object-Oriented Design, Implementation, and Testing course at Kansas State University. This front matter is specific to that course. If you are not enrolled in the course, please disregard this section.

For this course, we will be using a number of software packages including:

  • Microsoft Visual Studio 2022
  • Microsoft Visio 2021

These have been installed in the classroom lab, as well as all Engineering and Computer Science labs. It is strongly suggested that you install the same versions on your own development machines if you plan on working from home. Alternatively, you can remote desktop into a lab computer and use the installed software there.

Warning

Please note that the “Show whole line completions” option must be disabled in Visual Studio IntelliCode. You can disable it in Visual Studio by going to: Tools->Options, searching for “IntelliCode”, and unchecking the box that says “Show whole line completions”.

Using the whole line completions to help code the milestones is an academic honesty violation.

Remote Desktop Access

To use a remote desktop, you must first install a remote desktop client on your computer. Microsoft supplies a client for most platforms (Mac, Windows, etc.), which you can find links to and information about here.

The remote desktop server is behind a network firewall, so when accessing it from off-campus, you must be using the K-State Virtual Private Network (VPN). It has its own client that also must be installed. You can learn about K-State’s VPN and download the client on the K-State VPN Page

For remote desktop servers, you can use either those maintained by The Department of Computer Science or the College of Engineering.

Installing on Your Machine

If you would prefer to install the software on your own development machine, you can obtain no-cost copies of Microsoft Visual Studio Professional Edition and Microsoft Visio through Microsoft’s Azure Portal and signing in with your K-State eid and password.

After signing in, click the “Software” option in the left menu, and browse the available software for what you need.

The Visual Studio Community Edition is also available as a free download here. While not as full-featured as the Professional edition you can download through Azure Portal, it will be sufficient for the needs of this class.

Discord

Discord can be used through its web app at https://discord.com/webapp or you can download a native app for Windows, Linux, Mac, iOS, or Android devices.

Chapter I

Object-Orientation

Every object tells a story

Subsections of Object-Orientation

Chapter 0

Introduction

Setting the Stage

Subsections of Introduction

Introduction

Before we delve too deeply into how to reason about Object-Orientation and how to utilize it in your programming efforts, it would be useful to understand why object-orientation came to exist. This initial chapter seeks to explore the origins behind object-oriented programming.

Key Terms

Some key terms to learn in this chapter are:

  • The Software Crisis
  • GOTO statements
  • Programming Language Paradigm
  • Imperative Programming
  • Functional Programming
  • Structured Programming
  • Object-Orientation

The Growth of Computing

By this point, you should be familiar enough with the history of computers to be aware of the evolution from the massive room-filling vacuum tube implementations of ENIAC, UNIVAC, and other first-generation computers to transistor-based mainframes like the PDP-1, and the eventual introduction of the microcomputer (desktop computers that are the basis of the modern PC) in the late 1970’s. Along with a declining size, each generation of these machines also cost less:

MachineRelease YearCost at ReleaseAdjusted for Inflation
ENIAC1945$400,000$5,288,143
UNIVAC1951$159,000$1,576,527
PDP-11963$120,000$1,010,968
Commodore PET1977$795$5,282
Apple II (4K RAM model)1977$1,298$8,624
IBM PC1981$1,565$4,438
Commodore 641982$595$1,589

This increase in affordability was also coupled with an increase in computational power. Consider the ENIAC, which computed at 100,000 cycles per second. In contrast, the relatively inexpensive Commodore 64 ran at 1,000,000 cycles per second, while the more pricy IBM PC ran at 4,770,000 cycles per second.

Not surprisingly, governments, corporations, schools, and even individuals purchased computers in larger and larger quantities, and the demand for software to run on these platforms and meet these customers’ needs likewise grew. Moreover, the sophistication expected from this software also grew. Edsger Dijkstra described it in these terms:

The major cause of the software crisis is that the machines have become several orders of magnitude more powerful! To put it quite bluntly: as long as there were no machines, programming was no problem at all; when we had a few weak computers, programming became a mild problem, and now we have gigantic computers, programming has become an equally gigantic problem.

Edsger Dijkstra, The Humble Programmer (EWD340), Communications of the ACM

Coupled with this rising demand for programs was a demand for skilled software developers, as reflected in the following table of graduation rates in programming-centric degrees (the dashed line represents the growth of all bachelor degrees, not just computer-related ones):

Annual Computer-Related Bachelor Degrees Awarded in the US Annual Computer-Related Bachelor Degrees Awarded in the US

Unfortunately, this graduation rate often lagged far behind the demand for skilled graduates, and was marked by several periods of intense growth (the period from 1965 to 1985, 1995-2003, and the current surge beginning around 2010). During these surges, it was not uncommon to see students hired directly into the industry after only a course or two of learning programming (coding boot camps are a modern equivalent of this trend).

All of these trends contributed to what we now call the Software Crisis.

The Software Crisis

At the 1968 NATO Software Engineering Conference held in Garmisch Germany, the term “Software Crisis” was coined to describe the current state of the software development industry, where common problems included:

  • Projects that ran over-budget
  • Projects that ran over-time
  • Software that made inefficient use of calculations and memory
  • Software was of low quality
  • Software that failed to meet the requirements it was developed to meet
  • Projects that became unmanageable and code difficult to maintain
  • Software that never finished development

The software development industry sought to counter these problems through a variety of efforts:

  • The development of new programming languages with features intended to make it harder for programmers to make errors.
  • The development of Integrated Development Environments (IDEs) with developer-centric tools to aid in the software development process, including syntax highlighting, interactive debuggers, and profiling tools
  • The development of code repository tools like SVN and GIT
  • The development and adoption of code documentation standards
  • The development and adoption of program modeling languages like UML
  • The use of automated testing frameworks and tools to verify expected functionality
  • The adoption of software development practices that adopted ideas from other engineering disciplines

This course will seek to instill many of these ideas and approaches into your programming practice through adopting them in our everyday work. It is important to understand that unless these practices are used, the same problems that defined the software crisis continue to occur!

In fact, some software engineering experts suggest the software crisis isn’t over, pointing to recent failures like the Denver Airport Baggage System in 1995, the Ariane 5 Rocket Explosion in 1996, the German Toll Collect system cancelled in 2003, the rocky healthcare.gov launch in 2013, and the massive vulnerabilities known as the Meltdown and Spectre exploits discovered in 2018.

Language Evolution

One of the strategies that computer scientists employed to counter the software crisis was the development of new programming languages. These new languages would often 1) adopt new techniques intended to make errors harder to make while programming, and 2) remove problematic features that had existed in earlier languages.

A Fortran Example

Let’s take a look at a working (and in current use) program built using Fortran, one of the most popular programming languages at the onset of the software crisis. This software is the Environmental Policy Integrated Climate (EPIC) Model, created by researchers at Texas A&M:

Environmental Policy Integrated Climate (EPIC) model is a cropping systems model that was developed to estimate soil productivity as affected by erosion as part of the Soil and Water Resources Conservation Act analysis for 1980, which revealed a significant need for improving technology for evaluating the impacts of soil erosion on soil productivity. EPIC simulates approximately eighty crops with one crop growth model using unique parameter values for each crop. It predicts effects of management decisions on soil, water, nutrient and pesticide movements, and their combined impact on soil loss, water quality, and crop yields for areas with homogeneous soils and management. EPIC Homepage

You can download the raw source code here (click “EPIC v.1102” under “Source Code”). Open and unzip the source code, and open a file at random using your favorite code editor. See if you can determine what it does, and how it fits into the overall application.

Try this with a few other files. What do you think of the organization? Would you be comfortable adding a new feature to this program?

New Language Features

You probably found the Fortran code in the example difficult to wrap your mind around - and that’s not surprising, as more recent languages have moved away from many of the practices employed in Fortran. Additionally, our computing environment has dramatically changed since this time.

Symbol Character Limits

One clear example is symbol names for variables and procedures (functions) - notice that in the Fortran code they are typically short and cryptic: RT, HU, IEVI, HUSE, and NFALL, for example. You’ve been told since your first class that variable and function names should express clearly what the variable represents or a function does. Would rainFall, dailyHeatUnits, cropLeafAreaIndexDevelopment, CalculateWaterAndNutrientUse(), CalculateConversionOfStandingDeadCropResidueToFlatResidue() be easier to decipher? (Hint: the documentation contains some of the variable notations in a list starting on page 70, and some in-code documentation of global variables occurs in MAIN_1102.f90.).

Believe it or not, there was an actual reason for short names in these early programs. A six character name would fit into a 36-bit register, allowing for fast dictionary lookups - accordingly, early version of FORTRAN enforced a limit of six characters for variable names. However, it is easy to replace a symbol name with an automatically generated symbol during compilation, allowing for both fast lookup and human readability at a cost of some extra computation during compilation. This step is built into the compilation process of most current programming languages, allowing for arbitrary-length symbol names with no runtime performance penalty.

Paradigm Shifts

In addition to these less drastic changes, some evolutionary language changes had sweeping effects, changing the way we approach and think about how programs should be written and executed. These “big ideas” of how programming languages should work are often called paradigms. In the early days of computing, we had two common ones: imperative and functional.

At its core, imperative programming simply means the idea of writing a program as a sequence of commands, i.e. this Python script uses a sequence of commands to write to a file:

f = open("example.txt")
f.write("Hello from a file!")
f.close()

An imperative program would start executing the first line of code, and then continue executing line-by-line until the end of the file or a command to stop execution was reached. In addition to moving one line through the program code, imperative programs could jump to a specific spot in the code and continue execution from there, using a GOTO statement. We’ll revisit that aspect shorty.

In contrast, functional programming consisted primarily of functions. One function was designated as the ‘main’ function that would start the execution of the program. It would then call one or more functions, which would in turn call more functions. Thus, the entire program consisted of function definitions. Consider this Python program:

def concatenateList(str, list):
  if(len(list) == 0):
    return str
  elif(len(list) == 1):
    head = list.pop(0)
    return concatenateList(str + head, list)
  else:
    head = list.pop(0)
    return concatenateList(str + head + ", ", list)

def printToFile(filename, body):
  f = open(filename)
  f.write(body)

def printListToFile(filename, list):
  body = concatenateList("", list)
  printToFile(filename, body)

def main():
  printListToFile("list.txt", ["Dog", "Cat", "Mouse"])

main()

You probably see elements of your favorite higher-order programming language in both of these descriptions. That’s not surprising as modern languages often draw from multiple programming paradigms (after all, both the above examples were written in Python). This, too, is part of language evolution - language developers borrow good ideas as they find them.

But as languages continued to evolve and language creators sought ways to make programming easier, more reliable, and more secure to address the software crisis, new ideas emerged that were large enough to be considered new paradigms. Two of the most impactful of these new paradigms these are structured programming and object orientation. We’ll talk about each next.

Structured Programming

Another common change to programming languages was the removal of the GOTO statement, which allowed the program execution to jump to an arbitrary point in the code (much like a choose-your-own adventure book will direct you to jump to a page). The GOTO came to be considered too primitive, and too easy for a programmer to misuse 1.

While the GOTO statement is absent from most modern programming languages the actual functionality remains, abstracted into control-flow structures like conditionals, loops, and switch statements. This is the basis of structured programming, a paradigm adopted by all modern higher-order programming languages.

Each of these control-flow structures can be represented by careful use of GOTO statements (and, in fact the resulting assembly code from compiling these languages does just that). The benefit of using structured programming is it promotes “reliability, correctness, and organizational clarity” by clearly defining the circumstances and effects of code jumps 2.

You probably aren’t very familiar with GOTO statements because the structured programming paradigm has become so dominant. Before we move on, let’s see how some familiar structured programming patterns were originally implemented using GOTOs:

Conditional (if statement)

In C#, you are probably used to writing if statements with a true branch:

int x = 4;
if(x < 5) 
{
  x = x * 2;
}
Console.WriteLine("The value is:" + x);

With GOTOs, it would look something like:

int x = 4;
if(x < 5) goto TrueBranch;

AfterElse:
  Console.WriteLine("The value is:" + x);
  Environment.Exit(0);

TrueBranch:
  x = x * 2;
  goto AfterElse

Conditional (if-else statement)

Similarly, a C# if statement with an else branch:

int x = 4;
if(x < 5) 
{
  x = x * 2;
}
else 
{
  x = 7;
}
Console.WriteLine("The value is:" + x);

And using GOTOs:

int x = 4;
if(x < 5) goto TrueBranch;
goto FalseBranch;

AfterElse:
  Console.WriteLine("The value is:" + x);
  Environment.Exit(0);

TrueBranch:
  x = x * 2;
  goto AfterElse;

FalseBranch: 
  x = 7;
  goto AfterElse;

Note that with the goto, we must tell the program to stop running explicitly with Environment.Exit(0) or it will continue on to execute the labeled code (we could also place the TrueBranch and FalseBranch before the main program, and use a goto to jump to the main program).

While Loop

Loops were also originally constructed entirely from GOTOs, so the familiar while loop:

int times = 5;
while(times > 0)
{
  Console.WriteLine("Counting Down: " + times);
  times = times - 1;
}

Can be written:

int times = 5;
Test:
  if(times > 0) goto Loop;
  Environment.Exit(0);

Loop: 
  Console.WriteLine("Counting Down: " + times);
  times = times - 1;
  goto Test;

The do while and for loops are implemented similarly. As you can probably imagine, as more control flow is added to a program, using GOTOs and corresponding labels to jump to becomes very hard to follow.

Info

Interestingly, the C# language does have a goto statement (Java does not). Likely this is because C# was designed to compile to intermediate language like Visual Basic, which is an evolution of BASIC which was old enough to have a goto.

Accordingly, the above examples with the goto statements are valid C# code. You can even compile and run them. However, you should avoid using goto statements in your code.

Object-Orientation

The object-orientation paradigm was similarly developed to make programming large projects easier and less error-prone.

The term “Object Orientation” was coined by Alan Kay while he was a graduate student in the late 60’s. Alan Kay, Dan Ingalls, Adele Goldberg, and others created the first object-oriented language, Smalltalk, which became a very influential language from which many ideas were borrowed. To Alan, the essential core of object-orientation was three properties a language could possess: 1

  • Encapsulation & Information Hiding
  • Message passing
  • Dynamic binding

Let’s break down each of these ideas, and see how they helped address some of the problems we’ve identified in this chapter.

Encapsulation refers to breaking programs into smaller units that are easier to read and reason about. In an object-oriented language these units are classes and objects, and the data contained in these units is protected from being changed by code outside the unit through information hiding.

Message Passing allows us to send well-defined messages between objects. This gives us a well-defined and controlled method for accessing and potentially changing the data contained in an encapsulated unit. In an object oriented language, calling a method on an object is a form of message passing, as are events.

Dynamic Binding means we can have more than one possible way to handle messages and the appropriate one can be determined at run-time. This is the basis for polymorphism, an important idea in many object-oriented languages.

Remember these terms and pay attention to how they are implemented in the languages you are learning. They can help you understand the ideas that inspired the features of these languages.

We’ll take a deeper look at each of these in the next few chapters. But before we do, you might want to see how language popularity has fared since the onset of the software crisis, and how new languages have appeared and grown in popularity in this animated chart from Data is Beautiful:

Interestingly, the four top languages in 2019 (Python, JavaScript, Java, and C#) all adopt the object-oriented paradigm - though the exact details of how they implement it vary dramatically.


  1. Eric Elliot, “The Forgotten History of Object-Oriented Programming,” Medium, Oct. 31, 2018. ↩︎

Summary

In this chapter, we’ve discussed the environment in which object-orientation emerged. Early computers were limited in their computational power, and languages and programming techniques had to work around these limitations. Similarly, these computers were very expensive, so their purchasers were very concerned about getting the largest possible return on their investment. In the words of Niklaus Wirth:

Tricks were necessary at this time, simply because machines were built with limitations imposed by a technology in its early development stage, and because even problems that would be termed "simple" nowadays could not be handled in a straightforward way. It was the programmers' very task to push computers to their limits by whatever means available.

As computers became more powerful and less expensive, the demand for programs (and therefore programmers) grew faster than universities could train new programmers. Unskilled programmers, unwieldy programming languages, and programming approaches developed to address the problems of older technology led to what became known as the “software crisis” where many projects failed or floundered.

This led to the development of new programming techniques, languages, and paradigms to make the process of programming easier and less error-prone. Among the many new programming paradigms was structured programming paradigm, which introduced control-flow structures into programming languages to help programmers reason about the order of program execution in a clear and consistent manner.

Also developed during this time was the object-oriented paradigm, which brings together four big ideas: encapsulation & information hiding, message passing, and dynamic binding. We will be studying this paradigm, its ideas, and implementation in the C# language throughout this course.

Chapter 1

Classes and Objects

Getting Object Oriented

Subsections of Classes and Objects

Introduction

A signature aspect of object-oriented languages is (as you might expect from the name), the existence of objects within the language. In this chapter, we take a deep look at objects, exploring why they were created, what they are at both a theoretical and practical level, and how they are used.

Key Terms

Some key terms to learn in this chapter are:

  • Encapsulation

  • Information Hiding

  • Message Passing

  • State

  • Class

  • Object

  • Field

  • Method

  • Constructor

  • Parameterless Constructor

  • Property

  • Public

  • Private

  • Static

To begin, we’ll examine the term encapsulation.

Encapsulation

The first criteria that Alan Kay set for an object-oriented language was encapsulation. In computer science, the term encapsulation refers to organizing code into units. This provides a mechanism for organizing complex software.

A second related idea is information hiding, which provides mechanisms for controlling access to encapsulated data and how it can be changed.

Think back to the FORTRAN EPIC model we introduced earlier. All of the variables in that program were declared globally, and there were thousands. How easy was it to find where a variable was declared? Initialized? Used? Are you sure you found all the spots it was used?

Also, how easy was it to determine what part of the system a particular block of code belonged to? If I told you the program involved modeling hydrology (how water moves through the soils), weather, erosion, plant growth, plant residue decomposition, soil chemistry, planting, harvesting, and chemical applications, would you be able to find the code for each of those processes?

Remember from our discussion on the growth of computing the idea that as computers grew more powerful, we wanted to use them in more powerful ways? The EPIC project grew from that desire - what if we could model all the aspects influencing how well a crop grows? Then we could use that model to help us make better decisions in agriculture. Or, what if we could model all the processes involved in weather? If we could do so, we could help save lives by predicting dangerous storms! A century ago, you knew a tornado was coming when you heard its roaring winds approaching your home. Now we have warnings that conditions are favorable to produce one hours in advance. This is all thanks to using computers to model some very complex systems.

But how do we go about writing those complex systems? I don’t know about you, but I wouldn’t want to write a model the way the EPIC programmers did. And neither did most software developers at the time - so computer scientists set out to define better ways to write programs. David Parnas formalized some of the best ideas emerging from those efforts in his 1972 paper “On the Criteria To Be Used in Decomposing Systems into Modules”. 1

A data structure, its internal linkings, accessing procedures and modifying procedures are part of a single module.

Here he suggests organizing code into modules that group related variables and the procedures that operate upon them. For the EPIC module, this might mean all the code related to weather modeling would be moved into its own module. That meant that if we needed to understand how weather was being modeled, we only had to look at the weather module.

They are not shared by many modules as is conventionally done.

Here he is laying the foundations for the concept we now call scope - the idea of where a specific symbol (a variable or function name) is accessible within a program’s code. By limiting access to variables to the scope of a particular module, only code in that module can change the value. That way, we can’t accidentally change a variable declared in the weather module from the soil chemistry module (which would be a very hard error to find, as if the weather module doesn’t seem to be working, that’s what we would probably focus on trying to fix).

Programmers of the time referred to this practice as information hiding, as we ‘hid’ parts of the program from other parts of the program. Parnas and his peers pushed for not just hiding the data, but also how the data was manipulated. By hiding these implementation details, they could prevent programmers who were used to the globally accessible variables of early programming languages from looking into our code and using a variable that we might change in the future.

The sequence of instructions necessary to call a given routine and the routine itself are part of the same module.

As the actual implementation of the code is hidden from other parts of the program, a mechanism for sharing controlled access to some part of that module in order to use it needed to be made. An interface, if you will, that describes how the other parts of the program might trigger some behavior or access some value.


  1. D. L. Parnas, “On the criteria to be used in decomposing systems into modulesCommunications of the ACM, Dec. 1972. ↩︎

C# Encapsulation Examples

Let’s start by focusing on encapsulation’s benefits to organizing our code by exploring some examples of encapsulation you may already be familiar with.

Namespaces

The C# libraries are organized into discrete units called namespaces. The primary purpose of this is to separate code units that potentially use the same name, which causes name collisions where the interpreter isn’t sure which of the possibilities you mean in your program. This means you can use the same name to refer to two different things in your program, provided they are in different namespaces.

For example, there are two definitions for a Point Struct in the .NET core libraries: System.Drawing.Point and System.Windows.Point. The two have a very different internal structures (the former uses integers and the latter doubles), and we would not want to mix them up. If we needed to create an instance of both in our program, we would use their fully-quantified name to help the interpreter know which we mean:

System.Drawing.Point pointA = new System.Drawing.Point(500, 500);
System.Windows.Point pointB = new System.Windows.Point(300.0, 200.0);

The using directive allows you reference the type without quantification, i.e.:

using System.Drawing;
Point pointC = new Point(400, 400);

You can also create an alias with the using directive, providing an alternative (and usually abbreviated) name for the type:

using WinPoint = System.Windows.Point;
WinPoint pointD = new WinPoint(100.0, 100.0);

We can also declare our own namespaces, allowing us to use namespaces to organize our own code just as Microsoft has done with its .NET libraries.

Encapsulating code within a namespace helps ensure that the types defined within are only accessible with a fully qualified name, or when the using directive is employed. In either case, the intended type is clear, and knowing the namespace can help other programmers find the type’s definition.

Structs

In the discussion of namespaces, we used a struct. A C# struct is what computer scientists refer to as a compound type, a type composed from other types. This too, is a form of encapsulation, as it allows us to collect several values into a single data structure. Consider the concept of a vector from mathematics - if we wanted to store three-dimensional vectors in a program, we could do so in several ways. Perhaps the easiest would be as an array:

double[] vectorA = {3, 4, 5};

However, other than the variable name, there is no indication to other programmers that this is intended to be a three-element vector. And, if we were to accept it in a function, say a dot product:

public double DotProduct(double[] a, double[] b) {
    if(a.Length < 3 || b.Length < 3) throw new ArgumentException();
    return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
}

We would need to check that both arrays were of length three… A struct provides a much cleaner option, by allowing us to define a type that is composed of exactly three doubles:

/// <summary>
/// A 3-element vector 
/// </summary>
public struct Vector3 {
    public double x;
    public double y;
    public double z;

    public Vector3(double x, double y, double z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
}

Then, our DotProduct can take two arguments of the Vector3 struct:

public double DotProduct(Vector3 a, Vector3 b) {
    return a.x * b.x + a.y * b.y + a.z * b.z;
}

There is no longer any concern about having the wrong number of elements in our vectors - it will always be three. We also get the benefit of having unique names for these fields (in this case, x, y, and z).

Thus, a struct allows us to create structure to represent multiple values in one variable, encapsulating the related values into a single data structure. Variables, and compound data types, represent the state of a program. We’ll examine this concept in detail next.

Modules

You might think that the kind of modules that Parnas was describing don’t exist in C#, but they actually do - we just don’t call them ‘modules’. Consider how you would raise a number by a power, say 10 to the 8th power:

Math.Pow(10, 8);

The Math class in this example is actually used just like a module! We can’t see the underlying implementation of the Pow() method, it provides to us a well-defined interface (i.e. you call it with the symbol Pow and two doubles for parameters), and this method and other related math functions (Sin(), Abs(), Floor(), etc.) are encapsulated within the Math class.

We can define our own module-like classes by using the static keyword, i.e. we could group our vector math functions into a static VectorMath class:

/// <summary>
/// A library of vector math functions
/// </summary>
public static class VectorMath() {
    
    /// <summary>
    /// Computes the dot product of two vectors 
    /// </summary>
    public static double DotProduct(Vector3 a, Vector3 b) {
        return a.x * b.x + a.y * b.y + a.z * b.z;
    }

    /// <summary>
    /// Computes the magnitude of a vector
    /// </summary>
    public static double Magnitude(Vector3 a) {
        return Math.Sqrt(Math.Pow(a.x, 2) + Math.Pow(a.y, 2) + Math.Pow(a.z, 2));
    }

}
Note

To duplicate the module behavior with C#, we must declare both the class and its methods static.

Classes

But what most distinguishes C# is that it is an object-oriented language, and as such, it’s primary form of encapsulation is classes and objects. The key idea behind encapsulation in an object-oriented language is that we encapsulate both state and behavior in the class definition. Let’s explore that idea more deeply in the next section.

State and Behavior

The data stored in a program at any given moment (in the form of variables, objects, etc.) is the state of the program. Consider a variable:

int a = 5;

The state of the variable a after this line is 5. If we then run:

a = a * 3;

The state is now 15. Consider the Vector3 struct we defined in the last section:

public struct Vector3 {
    public double x;
    public double y;
    public double z;

    // constructor
    public Vector3(double x, double y, double z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
}

If we create an instance of that struct in the variable b:

Vector3 b = new Vector3(1.2, 3.7, 5.6);

The state of our variable b is $\{1.2, 3.7, 5.6\}$. If we change one of b’s fields:

b.x = 6.0;

The state of our variable b is now $\{6.0, 3.7, 5.6\}$.

We can also think about the state of the program, which would be something like: $\{a: 5, b: \{x: 6.0, y: 3.7, z: 5.6\}\}$, or a state vector like: $|5, 6.0, 3.7, 5.6|$. We can therefore think of a program as a state machine. We can in fact, draw our entire program as a state table listing all possible legal states (combinations of variable values) and the transitions between those states. Techniques like this can be used to reason about our programs and even prove them correct!

This way of reasoning about programs is the heart of Automata Theory, a subject you may choose to learn more about if you pursue graduate studies in computer science.

What causes our program to transition between states? If we look at our earlier examples, it is clear that assignment is a strong culprit. Expressions clearly have a role to play, as do control-flow structures decide which transformations take place. In fact, we can say that our program code is what drives state changes - the behavior of the program.

Thus, programs are composed of both state (the values stored in memory at a particular moment in time) and behavior (the instructions to change that state).

Now, can you imagine trying to draw the state table for a large program? Something on the order of EPIC?

On the other hand, with encapsulation we can reason about state and behavior on a much smaller scale. Consider this function working with our Vector3 struct:

/// <summary>
/// Returns the the supplied vector scaled by the provided scalar
/// </summary>
public static Vector3 Scale(Vector3 vec, double scale) {
    double x = vec.x * scale;
    double y = vec.y * scale;
    double z = vec.z * scale;
    return new Vector3(x, y, z);
}

If this method was invoked with a vector $\langle4.0, 1.0, 3.4\rangle$ and a scalar $2.0$ our state table would look something like:

stepvec.xvec.yvec.zscalexyzreturn.xreturn.yreturn.z
04.01.03.42.00.00.00.00.00.00.0
14.01.03.42.08.00.00.00.00.00.0
24.01.03.42.08.02.00.00.00.00.0
34.01.03.42.08.02.06.80.00.00.0
44.01.03.42.08.02.06.88.02.06.8

Because the parameters vec and scale, as well as the variables x, y, z, and the unnamed Vector3 we return are all defined only within the scope of the method, we can reason about them and the associated state changes independently of the rest of the program. Essentially, we have encapsulated a portion of the program state in our Vector3 struct, and encapsulated a portion of the program behavior in the static Vector3 library. This greatly simplifies both writing and debugging programs.

However, we really will only use the Vector3 library in conjunction with Vector3 structures, so it makes a certain amount of sense to define them in the same place. This is where classes and objects come into the picture, which we’ll discuss next.

Classes and Objects

The module-based encapsulation suggested by Parnas and his contemporaries grouped state and behavior together into smaller, self-contained units. Alan Kay and his co-developers took this concept a step farther. Alan Kay was heavily influenced by ideas from biology, and saw this encapsulation in similar terms to cells.

Typical Animal Cell Typical Animal Cell

Biological cells are also encapsulated - the complex structures of the cell and the functions they perform are all within a cell wall. This wall is only bridged in carefully-controlled ways, i.e. cellular pumps that move resources into the cell and waste out. While single-celled organisms do exist, far more complex forms of life are made possible by many similar cells working together.

This idea became embodied in object-orientation in the form of classes and objects. An object is like a specific cell. You can create many, very similar objects that all function identically, but each have their own individual and different state. The class is therefore a definition of that type of object’s structure and behavior. It defines the shape of the object’s state, and how that state can change. But each individual instance of the class (an object) has its own current state.

Let’s re-write our Vector3 struct as a class using this concept:

/// <summary>A class representing a 3-element vector</summary>
public class Vector3 {

    /// <summary>The X component of the vector</summary>
    public double X;
    /// <summary>The Y component of the vector</summary>
    public double Y;
    /// <summary>The Z component of the vector</summary>
    public double Z;

    /// <summary>Constructs a new vector</summary>
    /// <param name="x">The value of the vector's x component</param>
    /// <param name="y">The value of the vector's y component</param>
    /// <param name="z">The value of the vector's z component</param>
    public Vector3(double x, double y, double z) {
        X = x;
        Y = y;
        Z = z;
    }

    /// <summary>Computes the dot product of this and <paramref name="other"/> vector</summary>
    /// <param name="other">The other vector to compute with</param>
    /// <returns>The dot product</returns>
    public double DotProduct(Vector3 other) {
        return X * other.X + Y * other.Y + Z * other.Z;
    }

    /// <summary>Scales this vector by <paramref name="scalar"/></summary>
    /// <paramref name="scalar">The value to scale by</paramref>
    public void Scale(double scalar) {
        X *= scalar;
        Y *= scalar;
        Z *= scalar;
    }
}

Here we have defined:

  1. The structure of the object state - three doubles, X, Y, and Z
  2. How the object is constructed - the Vector3() constructor that takes a value for the object’s initial state
  3. Instructions for how that object state can be changed, i.e. our Scale() method

We can create as many objects from this class definition as we might want:

Vector3 one = new Vector3(1.0, 1.0, 1.0);
Vector3 up = new Vector3(0.0, 1.0, 0.0);
Vector3 a = new Vector3(5.4, -21.4, 3.11);

Conceptually, what we are doing is not that different from using a compound data type like a struct and a module of functions that work upon that struct. But practically, it means all the code for working with Vectors appears in one place. This arguably makes it much easier to find all the pertinent parts of working with vectors, and makes the resulting code better organized and easier to maintain and add features to.

Classes also provide additional benefits over structs in the form of polymorphism, which we’ll discuss in Chapter 2.

Information Hiding

Now let’s return to the concept of information hiding, and how it applies in object-oriented languages.

Unanticipated changes in state are a major source of errors in programs. Again, think back to the EPIC source code we looked at earlier. It may have seemed unusual now, but it used a common pattern from the early days of programming, where all the variables the program used were declared in one spot, and were global in scope (i.e. any part of the program could reassign any of those variables).

If we consider the program as a state machine, that means that any part of the program code could change any part of the program state. Provided those changes were intended, everything works fine. But if the wrong part of the state was changed problems would ensue.

For example, if you were to make a typo in the part of the program dealing with water run-off in a field which ends up assigning a new value to a variable that was supposed to be used for crop growth, you’ve just introduced a very subtle and difficult-to-find error. When the crop growth modeling functionality fails to work properly, we’ll probably spend serious time and effort looking for a problem in the crop growth portion of the code… but the problem doesn’t lie there at all!

Access Modifiers

There are several techniques involved in data hiding in an object-oriented language. One of these is access modifiers, which determine what parts of the program code can access a particular class, field, property, or method. Consider a class representing a student:

public class Student {
    private string _first;
    private string _last;
    private uint _wid;

    public Student(string first, string last, uint wid) {
        this._first = first;
        this._last = last;
        this._wid = wid;
    }
}

By using the access modifier private, we have indicated that our fields _first, _last, and _wid cannot be accessed (seen or assigned to) outside of the code that makes up the Student class. If we were to create a specific student:

Student willie = new Student("Willie", "Wildcat", 888888888);

We would not be able to change his name, i.e. willie._first = "Bob" would fail, because the field _first is private. In fact, we cannot even see his name, so Console.WriteLine(willie._first); would also fail.

If we want to allow a field or method to be accessible outside of the object, we must declare it public. While we can declare fields public, this violates the core principles of encapsulation, as any outside code can modify our object’s state in uncontrolled ways.

Accessor Methods

Instead, in a true object-oriented approach we would write public accessor methods, a.k.a. getters and setters (so called because they get or set the value of a field). These methods allow us to see and change field values in a controlled way. Adding accessors to our Student class might look like:

/// <summary>A class representing a K-State student</summary>
public class Student
{
    private string _first;
    private string _last;
    private uint _wid;

    /// <summary>Constructs a new student object</summary>
    /// <param name="first">The new student's first name</param>
    /// <param name="last">The new student's last name</param>
    /// <param wid="wid">The new student's Wildcat ID number</param>
    public Student(string first, string last, uint wid)
    {
        _first = first;
        _last = last;
        _wid = wid;
    }

    /// <summary>Gets the first name of the student</summary>
    /// <returns>The student's first name</returns>
    public string GetFirst()
    {
        return _first;
    }

    /// <summary>Sets the first name of the student</summary>
    public void SetFirst(string value)
    {
        if (value.Length > 0) _first = value;
    }

    /// <summary>Gets the last name of the student</summary>
    /// <returns>The student's last name</returns>
    public string GetLast()
    {
        return _last;
    }

    /// <summary>Sets the last name of the student</summary>
    /// <param name="value">The new name</summary>
    /// <remarks>The <paramref name="value"/> must be a non-empty string</remarks>
    public void SetLast(string value)
    {
        if (value.Length > 0) _last = value;
    }

    /// <summary>Gets the student's Wildcat ID Number</summary>
    /// <returns>The student's Wildcat ID Number</returns>
    public uint GetWid()
    {
        return _wid;
    }

    /// <summary>Gets the full name of the student</summary>
    /// <returns>The first and last name of the student as a string</returns>
    public string GetFullName()
    {
        return $"{_first} {_last}"
    }
}

Notice how the SetFirst() and SetLast() method check that the provided name has at least one character? We can use setters to make sure that we never allow the object state to be set to something that makes no sense.

Also, notice that the _wid field only has a getter. This effectively means once a student’s Wid is set by the constructor, it cannot be changed. This allows us to share data without allowing it to be changed outside of the class.

Finally, the GetFullName() is also a getter method, but it does not have its own private backing field. Instead it derives its value from the class state. We sometimes call this a derived getter for that reason.

C# Properties

While accessor methods provide a powerful control mechanism in object-oriented languages, they also require a lot of typing the same code syntax over and over (we often call this boilerplate). Many languages therefore introduce a mechanism for quickly defining basic accessors. In C#, we have Properties. Let’s rewrite our Student class with Properties:

public class Student {

    private string _first;
    /// <summary>The student's first name</summary>
    public string First {
        get { return _first; }
        set { if(value.Length > 0) _first = value;}
    }

    private string _last;
    /// <summary>The student's last name</summary>
    public string Last {
        get { return _last; }
        set { if(value.Length > 0) _last = value; }
    }

    private uint _wid;
    /// <summary>The student's Wildcat ID number</summary>
    public uint Wid {
        get { return this._wid; }
    }

    /// <summary>The student's full name</summary>
    public string FullName 
    {
      get 
      {
        return $"{First} {Last}"
      }
    }

    /// <summary>The student's nickname</summary>
    public string Nickname { get; set; }

    /// <summary>Constructs a new student object</summary>
    /// <param name="first">The new student's first name</param>
    /// <param name="last">The new student's last name</param>
    /// <param name="nick">The new student's nickname</param>
    /// <param wid="wid">The new student's Wildcat ID number</param>
    public Student(string first, string last, string nick, uint wid) {
        _first = first;
        _last = last;
        Nickname = nick;
        _wid = wid;
    }
}

If you compare this example to the previous one, you will note that the code contained in bodies of the get and set are identical to the corresponding getter and setter methods. Essentially, C# properties are shorthand for writing out the accessor methods. In fact, when you compile a C# program it transforms the get and set back into methods, i.e. the get in first is used to generate a method named get_First().

While properties are methods, the syntax for working with them in code is identical to that of fields, i.e. if we were to create and then print a Student’s identifying information, we’d do something like:

Student willie = new Student("Willie", "Wildcat", "WillieCat", 99999999);
Console.Write("Hello, ")
Console.WriteLine(willie.FullName);
Console.Write("Your WID is:");
Console.WriteLine(willie.Wid);

Note too that we can declare properties with only a get or a set body, and that properties can be derived from other state rather than having a private backing field.

Info

Properties are Methods

While C# properties are used like fields, i.e. Console.WriteLine(willie.Wid) or willie.First = "William", they are actually methods. As such, they do not add structure to hold state, hence the need for a backing variable.

The Nickname property in the example above is special syntax for an implicit backing field - the C# compiler creates the necessary space to hold the value. But we can only access the value stored through that property. If you need direct access to it, you must create a backing variable.

However, we don’t always need a backing variable for a Property getter if the value of a property can be calculated from the current state of the class, e.g., consider our FullName property in our Student class:

public string FullName 
    {
        get 
        {
            return $"{First} {Last}"
        }
    }

Here we’re effectively generating the value of the FullName property from the First and Last properties every time the FullName property is requested. This does cause a bit more computation, but we also know that it will always reflect the current state of the first and last names.

Auto-Property Syntax

Not all properties need to do extra logic in the get or set body. Consider our Vector3 class we discussed earlier. We used public fields to represent the X, Y, and Z components, i.e.:

public double X = 0;

If we wanted to switch to using properties, the X property would end up like this:

private double _x = 0;
public double X 
{
  get 
  {
    return _x;
  }
  set 
  {
    _x = value;
  }
}

Which seems like a lot more work for the same effect. To counter this perception and encourage programmers to use properties even in cases like this, C# also supports auto-property syntax. An auto-property is written like:

public double X {get; set;} = 0;

Note the addition of the {get; set;} - this is what tells the compiler we want a property and not a field. When compiled, this code is transformed into a full getter and setter whose bodies match the basic get and set in the example above. The compiler even creates a private backing field (but we cannot access it in our code, because it is only created at compile time). Any time you don’t need to do any additional logic in a get or set, you can use this syntax.

Note that in the example above, we set a default value of 0. You can omit setting a default value. You can also define a get-only autoproperty that always returns the default value (remember, you cannot access the compiler-generated backing field, so it can never be changed):

public double Pi {get;} = 3.14;

In practice, this is effectively a constant field, so consider carefully if it is more appropriate to use that instead:

public const PI = 3.14;

While it is possible to create a set-only auto-property, you will not be able access its value, so it is of limited use.

Expression-Bodied Members

Later versions of C# introduced a concise way of writing functions common to functional languages known as lambda syntax, which C# calls Expression-Bodied Members.

Properties can be written using this concise syntax. For example, our FullName get-only derived property in the Student written as an expression-bodied read-only property would be:

public FullName => $"{FirstName} {LastName}"

Note the use of the arrow formed by an equals and greater than symbol =>. Properties with both a getter and setter can also be written as expression-bodied properties. For example, our FirstName property could be rewritten:

public FirstName 
{
  get => _first;
  set => if(value.Length > 0) _first = value;
}

This syntax works well if your property bodies are a single expression. However, if you need multiple lines, you should use the regular property syntax instead (you can also mix and match, i.e. use an expression-bodied get with a regular set).

Different Access Levels

It is possible to declare your property as public and give a different access level to one of the accessors, i.e. if we wanted to add a GPA property to our student:

public double GPA { get; private set; } = 4.0;

In this case, we can access the value of the GPA outside of the student class, but we can only set it from code inside the class. This approach works with all ways of defining a property.

Init Property Accessor

C# 9.0 introduced a third accessor, init. This also sets the value of the property, but can only be used when the class is being initialized, and it can only be used once. This allows us to have some properties that are immutable (unable to be changed).

Our student example treats the Wid as immutable, but we can use the init keyword with an auto-property for a more concise representation:

public uint Wid {get; init;}

And in the constructor, replace setting the backing field (_wid = wid) with setting the property (Wid = wid). This approach is similar to the public property/private setter, but won’t allow the property to ever change once declared.

Programs in Memory

YouTube Video
Info

The above video and below textbook content cover the same ideas (but are not identical). Feel free to pick one or the other.

Before we move on to our next concept, it is helpful to explore how programs use memory. Remember that modern computers are stored program computers, which means the program as well as the data are stored in the computer’s memory. A Universal Turing Machine, the standard example of stored program computer, reads the program from the same paper tape that it reads its inputs to and writes its output to. In contrast, to load a program in the ENIAC, the first electronic computer in the United States, programmers had to physically rewire the computer (it was later modified to be a stored-program computer).

When a program is run, the operating system allocates part of the computer’s memory (RAM) for the program to use. This memory is divided into three parts - the static memory, the stack, and the heap.

A diagram of the memory allocated to a program A diagram of the memory allocated to a program

The program code itself is copied into the static section of that memory, along with any literals (1, "Foo"). Additionally, the space to hold any variables that are declared static is allocated here. The reason this memory space is called static is that it is allocated when the program begins, and remains allocated for as long as the program is running. It does not grow or shrink (though the value of static variables may change).

The stack is where the space for scoped variables is allocated. We often call it the stack because functionally it is used like the stack data structure. The space for global variables is allocated first, at the “bottom” of the stack. Then, every time the program enters a new scope (i.e. a new function, a new loop body, etc.) the variables declared there are allocated on the stack. When the program exits that scope (i.e. the function returns, the loop ends), the memory that had been reserved for those values is released (like the stack pop operation).

Thus, the stack grows and shrinks over the life of the program. The base of the stack is against the static memory section, and it grows towards the heap. If it grows too much, it runs out of space. This is the root cause of the nefarious stack overflow exception. The stack has run out of memory, most often because an infinite recursion or infinite loop.

Finally, the heap is where dynamically allocated memory comes from - memory the programmer specifically reserved for a purpose. In C programming, you use the calloc(), malloc(), or realloc() to allocate space manually. In Object-Oriented languages, the data for individual objects are stored here. Calling a constructor with the new keyword allocates memory on the heap, the constructor then initializes that memory, and then the address of that memory is returned. We’ll explore this process in more depth shortly.

This is also where the difference between a value type and a reference type comes into play. Value types include numeric objects like integers, floating point numbers, and also booleans. Reference types include strings and classes. When you create a variable that represents a value type, the memory to hold its value is created in the stack. When you create a variable to hold a reference type, it also has memory in the stack - but this memory holds a pointer to where the object’s actual data is allocated in the heap. Hence the term reference type, as the variable doesn’t hold the object’s data directly - instead it holds a reference to where that object exists in the heap!

This is also where null comes from - a value of null means that a reference variable is not pointing at anything.

The objects in the heap are not limited to a scope like the variables stored in the stack. Some may exist for the entire running time of the program. Others may be released almost immediately. Accordingly, as this memory is released, it leaves “holes” that can be re-used for other objects (provided they fit). Many modern programming languages use a garbage collector to monitor how fragmented the heap is becoming, and will occasionally reorganize the data in the heap to make more contiguous space available.

Objects in Memory

We often talk about the class as a blueprint for an object. This is because classes define what properties and methods an object should have, in the form of the class definition. An object is created from this blueprint by invoking the class’ constructor. Consider this class representing a planet:

/// <summary>
/// A class representing a planet
// </summary>
public class Planet {

    /// <summary>
    /// The planet's mass in Earth Mass units (~5.9722 x 10^24kg)
    /// </summary>
    private double mass;
    public double Mass 
    {
        get { return mass; }
    }

    /// <summary>
    /// The planet's radius in Earth Radius units (~6.738 x 10^6m)
    /// </summary>
    private double radius;
    public double Radius 
    {
        get { return radius; }
    }

    /// <summary>
    /// Constructs a new planet
    /// <param name="mass">The planet's mass</param>
    /// <param name="radius">The planet's radius</param>
    public Planet(double mass, double radius) 
    {
        this.mass = mass;
        this.radius = radius;
    }

}

It describes a planet as having a mass and a radius. But a class does more than just labeling the properties and fields and providing methods to mutate the state they contain. It also specifies how memory needs to be allocated to hold those values as the program runs. In memory, we would need to hold both the mass and radius values. These are stored side-by-side, as a series of bits that are on or off. You probably remember from CIS 115 that a double is stored as a sign bit, mantissa and exponent. This is also the case here - a C# double requires 64 bits to hold these three parts, and we can represent it with a memory diagram:

The Planet memory diagram The Planet memory diagram

We can create a specific planet by invoking its constructor, i.e.:

new Planet(1, 1);

This allocates (sets aside) the memory to hold the planet, and populates the mass and radius with the supplied values. We can represent this with a memory diagram:

The constructed planet’s memory diagram The constructed planet’s memory diagram

With memory diagrams, we typically write the values of variables in their human-readable form. Technically the values we are storing are in binary, and would each be 0000000000010000000000000000000000000000000000000000000000000001, so our overall object would be the bits: 00000000000100000000000000000000000000000000000000000000000000010000000000010000000000000000000000000000000000000000000000000001.

And this is exactly how it is stored in memory! The nice boxes we drew in our memory diagram are a tool for us to reason about the memory, not something that actually exists in memory. Instead, the compiler determines the starting point for each double by looking at the structure defined in our class, i.e. the first field defined is mass, so it will be the first 64 bits of the object in memory. The second field is radius, so it starts 65 bits into the object and consists of the next (and final) 64 bits.

If we assign the created Planet object to a variable, we allocate memory for that variable:

Planet earth = new Planet(1, 1);

Unlike our double and other primitive values, this allocated memory holds a reference (a starting address of the memory where the object was allocated). We indicate this with a box and arrow connecting the variable and object in our memory diagram:

The memory diagram for the earth variable The memory diagram for the earth variable

A reference is either 32 bits (on a computer with a 32-bit CPU) or 64 bits (on a computer with a 64-bit CPU), and essentially is an offset from the memory address $0$ indicating where the object will be located in memory (in the computer’s RAM). You’ll see this in far more detail in CIS 450 - Computer Architecture and Operations, but the important idea for now is the variable stores where the object is located in memory not the object’s data itself. This is also why if we define a class variable but don’t assign it an object, i.e.:

Planet mars;

The memory diagram for the mars variable The memory diagram for the mars variable

The value of this variable will be null. It’s because it doesn’t point anywhere!

Returning to our Earth example, earth is an instance of the class Planet. We can create other instances, i.e.

Planet mars = new Planet(0.107, 0.53);

The memory diagram for the initialized mars variable The memory diagram for the initialized mars variable

We can even create a Planet instance to represent one of the exoplanets discovered by NASA’s TESS:

Planet hd21749b = new Planet(23.20, 2.836);

The memory diagram for three planets The memory diagram for three planets

Let’s think more deeply about the idea of a class as a blueprint. A blueprint for what, exactly? For one thing, it serves to describe the state of the object, and helps us label that state. If we were to check our variable mars’ radius, we do so based on the property Radius defined in our class:

mars.Radius

This would follow the mars reference to the Planet object it represents, and access the second group of 64 bits stored there, interpreting them as a double (basically it adds 64 to the reference address and then reads the next 64 bits)

Info

Incidentally, this is why we start counting at 0 in computer science. The mass bits start at the start of our Planet object, referenced by mars i.e. if mars holds the reference address $5234$, then the bits of mass also begin at $5234$, or $5234+0$. And the radius bits start at $5234 + 64$.

State and memory are clearly related - the current state is what data is stored in memory. It is possible to take that memory’s current state, write it to persistent storage (like the hard drive), and then read it back out at a later point in time and restore the program to exactly the state we left it with. This is actually what Windows does when you put it into hibernation mode.

The process of writing out the state is known as serialization, and it’s a topic we’ll revisit later.

Info

The Static Modifier and Memory

You might have wondered how the static modifier plays into objects. Essentially, the static keyword indicates the field or method it modifies exists in only one memory location. I.e. a static field references the same memory location for all objects that possess it. Hence, if we had a simple class like:

public class Simple {
    public static int A;
    public int B;

    public Simple(int a, int b) {
        this.A = a;
        this.B = b;
    }
}

And created a couple of instances:

Simple first = new Simple(10, 12);
Simple second = new Simple(8, 5);

The value of first.A would be 8 - because first.A and second.A reference the same memory location, and second.A was set after first.A. If we changed it again:

first.A = 3;

Then both first.A and second.A would have the value 3, as they share the same memory. first.B would still be 12, and second.B would be 5.

Another way to think about static is that it means the field or method we are modifying belongs to the class and not the individual object. Hence, each object shares a static variable, because it belongs to their class. Used on a method, static indicates that the method belongs to the class definition, not the object instance. Hence, we must invoke it from the class, not an object instance: i.e. Math.Pow(), not Math m = new Math(); m.Pow();.

Finally, when used with a class, static indicates we can’t create objects from the class - the class definition exists on its own. Hence, the Math m = new Math(); is actually an error, as the Math class is declared static.

C# Object Initialization

With our broader understanding of objects in memory, let’s re-examine something you’ve been working with already, how the values in that memory are initialized (set to their initial values). In C#, there are four primary ways a value is initialized:

  1. By zeroing the memory
  2. By setting a default value
  3. By the constructor
  4. With Initialization syntax

This also happens to be the order in which these operations occur - i.e. the default value can be overridden by code in the constructor. Only after all of these steps are completed is the initialized object returned from the constructor.

Zeroing the Memory

This step is actually done for you - it is a feature of the C# language. Remember, allocated memory is simply a series of bits. Those bits have likely been used previously to represent something else, so they will already be set to 0s or 1s. Once you treat it as a variable, it will have a specific meaning. Consider this statement:

int foo;

That statement allocates the space to hold the value of foo. But what is that value? In many older languages, it would be whatever is specified by how the bits were set previously - i.e. it could be any integer within the available range. And each time you ran the program, it would probably be a different value! This is why it is always a good idea to assign a value to a variable immediately after you declare it.

The creators of C# wanted to avoid this potential problem, so in C# any memory that is allocated by a variable declaration is also zeroed (all bits are set to 0). Exactly what this means depends on the variable’s type. Essentially, for numerics (integers, floating points, etc) the value would be 0, for booleans it would be false. And for reference types, the value would be null.

Default Values

A second way a field’s value can be set is by assigning a default value after it is declared. Thus, if we have a private backing _count in our CardDeck class, we could set it to have a default value of 52:

public class CardDeck
{
  private int _count = 52;

  public int Count 
  {
    get 
    {
      return _count;
    }
    set 
    {
      _count = value;
    }
  }
}

This ensures that _count starts with a value of 52, instead of 0.

We can also set a default value when using auto-property syntax:

public class CardDeck
{
  public int Count {get; set;} = 52;
}
Info

It is important to understand that the default value is assigned as the memory is allocated, which means the object doesn’t exist yet. Basically, we cannot use methods, fields, or properties of the class to set a default value. For example:

public class CardDeck
{
  public int Count {get; set;} = 52;
  public int PricePerCard {get;} = 5m / Count; 
}

Won’t compile, because we don’t have access to the Count property when setting the default for PricePerCard.

Constructors

This brings us to the constructor, the standard way for an object-oriented program to initialize the state of the object as it is created. In C#, the constructor always has the same name as the class it constructs and has no return type. For example, if we defined a class Square, we might type:

public class Square {
    public float length;

    public Square(float length) {
        this.length = length;
    }

    public float Area() {
        return length * length;
    }
}

Note that unlike the regular method, Area(), our constructor Square() does not have a return type. In the constructor, we set the length field of the newly constructed object to the value supplied as the parameter length. Note too that we use the this keyword to distinguish between the field length and the parameter length. Since both have the same name, the C# compiler assumes we mean the parameter, unless we use this.length to indicate the field that belongs to this - i.e. this object.

Parameterless Constructors

A parameterless constructor is one that does not have any parameters. For example:

public class Ball {
    private int x;
    private int y;

    public Ball() {
        x = 50;
        y = 10;
    }
}

Notice how no parameters are defined for Ball() - the parentheses are empty.

If we don’t provide a constructor for a class the C# compiler automatically creates a parameterless constructor for us, i.e. the class Bat:

public class Bat {
    private bool wood = true;
}

Can be created by invoking new Bat(), even though we did not define a constructor for the class. If we define any constructors, parameterless or otherwise, then the C# compiler will not create an automatic parameterless constructor.

Initializer Syntax

Finally, C# introduces some special syntax for setting initial values after the constructor code has run, but before initialization is completed - Object initializers. For example, if we have a class representing a rectangle:

public class Rectangle 
{
  public int Width {get; set;}
  public int Height {get; set;}
}

We could initialize it with:

Rectangle r = new Rectangle() {
  Width = 20,
  Height = 10
};

The resulting rectangle would have its width and height set to 20 and 10 respectively before it was assigned to the variable r.

Info

In addition to the get and set accessor, C# has an init operator that works like the set operator but can only be used during object initialization.

Message Passing

The second criteria Alan Kay set for object-oriented languages was message passing. Message passing is a way to request that a unit of code engage in a behavior, i.e. changing its state, or sharing some aspect of its state.

Consider the real-world analogue of a letter sent via the postal service. Such a message consists of: an address the message needs to be sent to, a return address, the message itself (the letter), and any data that needs to accompany the letter (the enclosures). A specific letter might be a wedding invitation. The message includes the details of the wedding (the host, the location, the time), an enclosure might be a refrigerator magnet with these details duplicated. The recipient should (per custom) send a response to the host addressed to the return address letting them know if they will be attending.

In an object-oriented language, message passing primarily takes the form of methods. Let’s revisit our example Vector3 class:

public class Vector3 {
    public double X {get; set;}
    public double Y {get; set;}
    public double Z {get; set;}

    /// <summary> 
    /// Creates a new Vector3 object
    /// </summary>
    public Vector3(double x, double y, double z) {
        this.X = x;
        this.Y = y;
        this.Z = z;
    }

    /// <summary>
    /// Computes the dot product of this vector and another one 
    /// </summary>
    /// <param name="other">The other vector</param>
    public double DotProduct(Vector3 other) {
        return this.X * other.X + this.Y * other.Y + this.Z * other.Z;
    }
}

And let’s use its DotProduct() method:

Vector3 a = new Vector3(1, 1, 2);
Vector3 b = new Vector3(4, 2, 1);
double c = a.DotProduct(b);

Consider the invocation of a.DotProduct(b) above. The method name, DotProduct provides the details of what the message is intended to accomplish (the letter). Invoking it on a specific variable, i.e. a, tells us who the message is being sent to (the recipient address). The return type indicates what we need to send back to the recipient (the invoking code), and the parameters provide any data needed by the class to address the task (the enclosures).

Let’s define a new method for our Vector3 class that emphasizes the role message passing plays in mutating object state:

public class Vector3 {
    public double X {get; set;}
    public double Y {get; set;}
    public double Z {get; set;}

    /// <summary> 
    /// Creates a new Vector3 object
    /// </summary>
    public Vector3(double x, double y, double z) {
        this.X = x;
        this.Y = y;
        this.Z = z;
    }

    public void Normalize() {
        var magnitude = Math.Sqrt(Math.pow(this.X, 2) + Math.Pow(this.Y, 2) + Math.Pow(this.Z, 2));
        this.X /= magnitude;
        this.Y /= magnitude;
        this.Z /= magnitude;
    }
}

We can now invoke the Normalize() method on a Vector3 to mutate its state, shortening the magnitude of the vector to length 1.

Vector3 f = new Vector3(9.0, 3.0, 2.0);
f.Normalize();

Note how here, f is the object receiving the message Normalize. There is no additional data needed, so there are no parameters being passed in. Our earlier DotProduct() method took a second vector as its argument, and used that vector’s values to mutate its state.

Message passing therefore acts like those special molecular pumps and other gate mechanisms of a cell that control what crosses the cell wall. The methods defined on a class determine how outside code can interact with the object. An extra benefit of this approach is that a method becomes an abstraction for the behavior of the code, and the associated state changes it embodies. As a programmer using the method, we don’t need to know the exact implementation of that behavior - just what data we need to provide, and what it should return or how it will alter the program state. This makes it far easier to reason about our program, and also means we can change the internal details of a class (perhaps to make it run faster) without impacting the other aspects of the program.

This is also the reason we want to use getters and setters (or properties in C#) instead of public fields in an object-oriented language. Getters, setters, and C# properties are all methods, and therefore are a form of message passing, and they ensure that outside code is not modifying the state of the object (rather, the outside code is requesting the object to change its state). It is a fine distinction, but one that can be very important.

Info

You probably have noticed that in many programming languages we speak of functions, but in C# and other object-oriented languages, we’ll often speak of methods. You might be wondering just what is the difference?

Both are forms of message passing, and share many of the same characteristics. Broadly speaking though, methods are functions defined as part of an object. Therefore, their bodies can access the state of the object. In fact, that’s what the this keyword in C# means - it refers to this object, i.e. the instance of the class that the method is currently executing for. For non-object-oriented languages, there is no concept of this (or self as it appears in some other languages).

Summary

In this chapter, we looked at how Object-Orientation adopted the concept of encapsulation to combine related state and behavior within a single unit of code, known as a class. We also discussed the three key features found in the implementation of classes and objects in Object-Oriented languages:

  1. Encapsulation of state and behavior within an object, defined by its class
  2. Information hiding applied to variables defined within that class to prevent unwanted mutations of object state
  3. Message passing to allow controlled mutations of object state in well-defined ways

We explored how objects are instances of a class created through invoking a constructor method, and how each object has its own independent state but shares behavior definitions with other objects constructed from the same class. We discussed several different ways of looking at and reasoning about objects - as a state machine, and as structured data stored in memory. We saw how the constructor creates the memory to hold the object state and initializes its values. We saw how access modifiers and accessor methods can be used to limit and control access to the internal state of an object through message passing.

Finally, we explored how all of these concepts are implemented in the C# language.

Chapter 2

Polymorphism

It’s a shapeshifter!

Subsections of Polymorphism

Introduction

The term polymorphism means many forms. In computer science, it refers to the ability of a single symbol (i.e. a function or class name) to represent multiple types. Some form of polymorphism can be found in nearly all programming languages.

While encapsulation of state and behavior into objects is the most central theoretical idea of object-oriented languages, polymorphism - specifically in the form of inheritance is a close second. In this chapter we’ll look at how polymorphism is commonly implemented in object-oriented languages.

Key Terms

Some key terms to learn in this chapter are:

  • Polymorphism
  • Type
  • Type Checking
  • Casting
  • Implicit Casting
  • Explicit Casting
  • Interface
  • Inheritance
  • Superclass
  • Subclass
  • Abstract Classes

C# Keywords:

  • Interface
  • protected
  • abstract
  • virtual
  • override
  • sealed
  • as
  • is
  • dynamic

Types

Before we can discuss polymorphism in detail, we must first understand the concept of types. In computer science, a type is a way of categorizing a variable by its storage strategy, i.e., how it is represented in the computer’s memory.

You’ve already used types extensively in your programming up to this point. Consider the declaration:

int number = 5;

The variable number is declared to have the type int. This lets the .NET interpreter know that the value of number will be stored using a specific scheme. This scheme will use 32 bits and contain the number in Two’s complement binary form. This form, and the number of bytes, allows us to represent numbers in the range -2,147,483,648 to 2,147,483,647. If we need to store larger values, we might instead use a long which uses 64 bits of storage. Or, if we only need positive numbers, we might instead use a uint, which uses 32 bits and stores the number in regular base 2 (binary) form.

This is why languages like C# provide multiple integral and float types. Each provides a different representation, representing a tradeoff between memory required to store the variable and the range or precision that variable can represent.

In addition to integral and float types, most programming languages include types for booleans, characters, arrays, and often strings. C# is no exception - you can read about its built-in value types in the documentation.

User-Defined Types

In addition to built-in types, most programming languages support user-defined types, that is, new types defined by the programmer. For example, if we were to define a C# enum:

public enum Grade {
  A,
  B,
  C,
  D,
  F
}

Defines the type Grade. We can then create variables with that type:

Grade courseGrade = Grade.A;

Similarly, structs provide a way of creating user-defined compound data types.

Classes are Types

In an object-oriented programming language, a Class also defines a new type. As we discussed in the previous chapter, the Class defines the structure for the state (what is represented) and memory (how it is represented) for objects implementing that type. Consider the C# class Student:

public class Student {
  // backing variables
  private float creditPoints = 0;
  private uint creditHours = 0;

  /// <summary>
  /// Gets and sets first name.
  /// </summary>
  public string First { get; set; }

  /// <summary>
  /// Gets and sets last name.
  /// </summary>
  public string Last { get; set; }

  /// <summary>
  /// Gets the student's GPA
  /// </summary>
  public float GPA {
    get {
      return creditPoints / creditHours;
    }
  }

  /// <summary>
  /// Adds a final grade for a course to the
  // student's GPA.
  /// </summary>
  /// <param name="grade">The student's final letter grade in the course</param>
  /// <param name="hours">The course's credit hours</param>
  public void AddCourseGrade(Grade grade, uint hours) {
    this.creditHours += hours;
    switch(grade) {
      case Grade.A:
        this.creditPoints += 4.0 * hours;
        break;
      case Grade.B:
        this.creditPoints += 3.0 * hours;
        break;
      case Grade.C:
        this.creditPoints += 2.0 * hours;
        break;
      case Grade.D:
        this.creditPoints += 1.0 * hours;
        break;
      case Grade.F:
        this.creditPoints += 0.0 * hours;
        break;
    }
  }
}

If we want to create a new student, we would create an instance of the class Student which is an object of type Student:

Student willie = new Student("Willie", "Wildcat");

Hence, the type of an object is the class it is an instance of. This is a staple across all object-oriented languages.

Static vs. Dynamic Typed Languages

A final note on types. You may hear languages being referred to as statically or dynamically typed. A statically typed language is one where the type is set by the code itself, either explicitly:

int foo = 5;

or implicitly (where the compiler determines the type based on the assigned value):

var bar = 6;

In a statically typed language, a variable cannot be assigned a value of a different type, i.e.:

foo = 8.3;

Will fail with an error, as a float is a different type than an int. Similarly, because bar has an implied type of int, this code will fail:

bar = 4.3;

However, we can cast the value to a new type (changing how it is represented), i.e.:

foo = (int)8.9;

For this to work, the language must know how to perform the cast. The cast may also lose some information - in the above example, the resulting value of foo is 8 (the fractional part is discarded).

In contrast, in a dynamically typed language the type of the variable changes when a value of a different type is assigned to it. For example, in JavaScript, this expression is legal:

var a = 5;
a = "foo";

and the type of a changes from int (at the first assignment) to string (at the second assignment).

C#, Java, C, C++, and Kotlin are all statically typed languages, while Python, JavaScript, and Ruby are dynamically typed languages.

Interfaces

If we think back to the concept of message passing in object-oriented languages, it can be useful to think of the collection of public methods available in a class as an interface, i.e., a list of messages you can dispatch to an object created from that class. When you were first learning a language (and probably even now), you find yourself referring to these kinds of lists, either in the language documentation, or via Intellisense in Visual Studio.

The Java API The Java API

Visual Studio Intellisense Visual Studio Intellisense

Essentially, programmers use these ‘interfaces’ to determine what methods can be invoked on an object. In other words, which messages can be passed to the object. This ‘interface’ (note the lowercase i) is determined by the class definition, specifically what methods it contains.

In dynamically typed programming languages, like Python, JavaScript, and Ruby, if two classes accept the same message, you can treat them interchangeably, i.e. the Kangaroo class and Car class both define a jump() method, you could populate a list with both, and call the jump() method on each:

var jumpables = [new Kangaroo(), new Car(), new Kangaroo()];
for(int i = 0; i < jumpables.length; i++) {
    jumpables[i].jump();
}

This is sometimes called duck typing, from the sense that “if it walks like a duck, and quacks like a duck, it might as well be a duck.”

However, for statically typed languages we must explicitly indicate that two types both possess the same message definition, by making the interface explicit. We do this by declaring an interface. I.e., the interface for classes that possess a parameter-less jump method might be:

/// <summary>
/// An interface indicating an object's ability to jump
/// </summary>
public interface IJumpable {
    
    /// <summary>
    /// A method that causes the object to jump
    /// </summary>
    void Jump();
}

In C#, it is common practice to preface interface names with the character I. The interface declaration defines an ‘interface’ - the shape of the messages that can be passed to an object implementing the interface - in the form of a method signature. Note that this signature does not include a body, but instead ends in a semicolon (;). An interface simply indicates the message to be sent, not the behavior it will cause! We can specify as many methods in an interface declaration as we want.

Also note that the method signatures in an interface declaration do not have access modifiers. This is because the whole purpose of defining an interface is to signify methods that can be used by other code. In other words, public access is implied by including the method signature in the interface declaration.

This interface can then be implemented by other classes by listing it after the class name, after a colon :. Any Class declaration implementing the interface must define public methods whose signatures match those were specified by the interface:

/// <summary>A class representing a kangaroo</summary>
public class Kangaroo : IJumpable 
{
    /// <summary>Causes the Kangaroo to jump into the air</summary>
    public void Jump() {
        // TODO: Implement jumping...
    }
}

/// <summary>A class representing an automobile</summary>
public class Car : IJumpable 
{
    /// <summary>Helps a stalled car to start by providing electricity from another car's battery</summary>
    public void Jump() {
        // TODO: Implement jumping a car...
    }

    /// <summary>Starts the car</summary>
    public void Start() {
        // TODO: Implement starting a car...
    }
}

We can then treat these two disparate classes as though they shared the same type, defined by the IJumpable interface:

List<IJumpable> jumpables = new List<IJumpable>() {new Kangaroo(), new Car(), new Kangaroo()};
for(int i = 0; i < jumpables.Count; i++)
{
    jumpables[i].Jump();
}

Note that while we are treating the Kangaroo and Car instances as IJumpable instances, we can only invoke the methods defined in the IJumpable interface, even if these objects have other methods. Essentially, the interface represents a new type that can be shared amongst disparate objects in a statically-typed language. The interface definition serves to assure the static type checker that the objects implementing it can be treated as this new type - i.e. the Interface provides a mechanism for implementing polymorphism.

We often describe the relationship between the interface and the class that implements it as a is-a relationship, i.e. a Kangaroo is an IJumpable (i.e. a Kangaroo is a thing that can jump). We further distinguish this from a related polymorphic mechanism, inheritance, by the strength of the relationship. We consider the relationship between interfaces and classes implementing them to be weak is-a connections. For example, other than the shared interface, a Kangaroo and a Car don’t have much to do with one another.

A C# class can implement as many interfaces as we want, they just need to be separated by commas, i.e.:

public class Frog : IJumpable, ICroakable, ICatchFlies
{
    // TODO: Implement frog class...
}

Object Inheritance

In an object-oriented language, inheritance is a mechanism for deriving part of a class definition from another existing class definition. This allows the programmer to “share” code between classes, reducing the amount of code that must be written.

Consider a Student class:

/// <summary>
/// A class representing a student
/// </summary>
public class Student {

  // private backing variables
  private double hours;
  private double points;

  /// <summary>
  /// Gets the students' GPA
  /// </summary>
  public double GPA {
    get {
      return points / hours;
    }
  }

  /// <summary>
  /// Gets or sets the first name
  /// </summary>
  public string First { get; set; }

  /// <summary>
  /// Gets or sets the last name
  /// </summary>
  public string Last { get; set; }

  /// <summary>
  /// Constructs a new instance of Student
  /// </summary>
  /// <param name="first">The student's first name </param>
  /// <param name="last">The student's last name</param>
  public Student(string first, string last) {
    this.First = first;
    this.Last = last;
  }

  /// <summary>
  /// Adds a new course grade to the student's record.
  /// </summary>
  /// <param name="creditHours">The number of credit hours in the course </param>
  /// <param name="finalGrade">The final grade earned in the course</param>
  ///
  public void AddCourseGrade(uint creditHours, Grade finalGrade) {
    this.hours += creditHours;
    switch(finalGrade) {
      case Grade.A:
        this.points += 4 * creditHours;
        break;
      case Grade.B:
        this.points += 3 * creditHours;
        break;
      case Grade.C:
        this.points += 2 * creditHours;
        break;
      case Grade.D:
        this.points += 1 * creditHours;
        break;
    }
  }
}

This would work well for representing a student. But what if we are representing multiple kinds of students, like undergraduate and graduate students? We’d need separate classes for each, but both would still have names and calculate their GPA the same way. So it would be handy if we could say “an undergraduate is a student, and has all the properties and methods a student has” and “a graduate student is a student, and has all the properties and methods a student has.” This is exactly what inheritance does for us, and we often describe it as a is a relationship. We distinguish this from the interface mechanism we looked at earlier by saying it is a strong is a relationship, as an Undergraduate student is, for all purposes, also a Student.

Let’s define an undergraduate student class:

/// <summary>
/// A class representing an undergraduate student
/// </summary>
public class UndergraduateStudent : Student {

  /// <summary>
  /// Constructs a new instance of UndergraduateStudent
  /// </summary>
  /// <param name="first">The student's first name </param>
  /// <param name="last">The student's last name</param>
  public UndergraduateStudent(string first, string last) : base(first, last) {
  }
}

In C#, the : indicates inheritance - so public class UndergraduateStudent : Student indicates that UndergraduateStudent inherits from (is a) Student. Thus, it has properties First, Last, and GPA that are inherited from Student. Similarly, it inherits the AddCourseGrade() method.

In fact, the only method we need to define in our UndergraduateStudent class is the constructor - and we only need to define this because the base class has a defined constructor taking two parameters, first and last names. This Student constructor must be invoked by the UndergraduateStudent constructor - that’s what the :base(first, last) portion does - it invokes the Student constructor with the first and last parameters passed into the UndergraduateStudent constructor.

Inheritance, State, and Behavior

Let’s define a GraduateStudent class as well. This will look much like an UndergraduateStudent, but all graduates have a bachelor’s degree:

/// <summary>
/// A class representing a graduate student
/// </summary>
public class GraduateStudent : Student {

  /// <summary>
  /// Gets the student's bachelor degree
  /// </summary>
  public string BachelorDegree {
    get; private set;
  }

  /// <summary>
  /// Constructs a new instance of GraduateStudent
  /// </summary>
  /// <param name="first">The student's first name </param>
  /// <param name="last">The student's last name</param>
  /// <param name="degree">The student's bachelor degree</param>
  public GraduateStudent(string first, string last, string degree) : base(first, last) {
    BachelorDegree = degree;
  }
}

Here we add a property for BachelorDegree. Since it’s setter is marked as private, it can only be written to by the class, as is done in the constructor. To the outside world, it is treated as read-only.

Thus, the GraduateStudent has all the state and behavior encapsulated in Student, plus the additional state of the bachelor’s degree title.

The protected Keyword

What you might not expect is that any fields declared private in the base class are inaccessible in the derived class. Thus, the private fields points and hours cannot be used in a method defined in GraduateStudent. This is again part of the encapsulation and data hiding ideals - we’ve encapsulated and hidden those variables within the base class, and any code outside that assembly, even in a derived class, is not allowed to mess with it.

However, we often will want to allow access to such variables in a derived class. C# uses the access modifier protected to allow for this access in derived classes, but not the wider world.

Inheritance and Memory

What happens when we construct an instance of GraduateStudent? First, we invoke the constructor of the GraduateStudent class:

GraduateStudent bobby = new GraduateStudent("Bobby", "TwoSocks", "Economics");

This constructor then invokes the constructor of the base class, Student, with the arguments "Bobby" and "TwoSocks". Thus, we allocate space to hold the state of a student, and populate it with the values set by the constructor. Finally, execution returns to the derived class of GraduateStudent, which allocates the additional memory for the reference to the BachelorDegree property. Thus, the memory space of the GraduateStudent contains an instance of the Student, somewhat like nesting dolls.

Because of this, we can treat a GraduateStudent object as a Student object. For example, we can store it in a list of type Student, along with UndergraduateStudent objects:

List<Student> students = new List<Student>();
students.Add(bobby);
students.Add(new UndergraduateStudent("Mary", "Contrary"));

Because of their relationship through inheritance, both GraduateStudent class instances and UndergraduateStudent class instances are considered to be of type Student, as well as their supertypes.

Nested Inheritance

We can go as deep as we like with inheritance - each base type can be a superclass of another base type, and has all the state and behavior of all the inherited base classes.

This said, having too many levels of inheritance can make it difficult to reason about an object. In practice, a good guideline is to limit nested inheritance to two or three levels of depth.

Abstract Classes

If we have a base class that only exists to inherit from (like our Student class in the example), we can mark it as abstract with the abstract keyword. An abstract class cannot be instantiated (that is, we cannot create an instance of it using the new keyword). It can still define fields and methods, but you can’t construct it. If we were to re-write our Student class as an abstract class:

/// <summary>
/// A class representing a student
/// </summary>
public abstract class Student {

  // private backing variables
  private double hours;
  private double points;

  /// <summary>
  /// Gets the students' GPA
  /// </summary>
  public double GPA {
    get {
      return points / hours;
    }
  }

  /// <summary>
  /// Gets or sets the first name
  /// </summary>
  public string First { get; set; }

  /// <summary>
  /// Gets or sets the last name
  /// </summary>
  public string Last { get; set; }

  /// <summary>
  /// Constructs a new instance of Student
  /// </summary>
  /// <param name="first">The student's first name </param>
  /// <param name="last">The student's last name</param>
  public Student(string first, string last) {
    this.First = first;
    this.Last = last;
  }

  /// <summary>
  /// Adds a new course grade to the student's record.
  /// </summary>
  /// <param name="creditHours">The number of credit hours in the course </param>
  /// <param name="finalGrade">The final grade earned in the course</param>
  ///
  public void AddCourseGrade(uint creditHours, Grade finalGrade) {
    this.hours += creditHours;
    switch(finalGrade) {
      case Grade.A:
        this.points += 4 * creditHours;
        break;
      case Grade.B:
        this.points += 3 * creditHours;
        break;
      case Grade.C:
        this.points += 2 * creditHours;
        break;
      case Grade.D:
        this.points += 1 * creditHours;
        break;
    }
  }
}

Now with Student as an abstract class, attempting to create a Student instance i.e. Student mark = new Student("Mark", "Guy") would fail with an exception. However, we can still create instances of the derived classes GraduateStudent and UndergraduateStudent, and treat them as Student instances. It is best practice to make a class abstract if it serves only as a base class for derived classes and will never be created directly.

Sealed Classes

Conversely, C# also offers the sealed keyword, which can be used to indicate that a class should not be inheritable. For example:

/// <summary>
/// A class that cannot be inherited from
/// </summary>
public sealed class DoNotDerive {

}

Derived classes can also be sealed. I.e., we could seal our UndergraduateStudent class to prevent further derivation:

/// <summary>
/// A sealed version  of the class representing an undergraduate student
/// </summary>
public sealed class UndergraduateStudent : Student {

  /// <summary>
  /// Constructs a new instance of UndergraduateStudent
  /// </summary>
  /// <param name="first">The student's first name </param>
  /// <param name="last">The student's last name</param>
  public UndergraduateStudent(string first, string last) : base(first, last) {
  }
}

Many of the library classes provided with the C# installation are sealed. This helps prevent developers from making changes to well-known classes that would make their code harder to maintain. It is good practice to seal classes that you expect will never be inherited from.

Interfaces and Inheritance

A class can use both inheritance and interfaces. In C#, a class can only inherit one base class, and it should always be the first after the colon (:). Following that we can have as many interfaces as we want, all separated from each other and the base class by commas (,):

public class UndergraduateStudent : Student, ITeachable, IEmailable 
{
  // TODO: Implement student class 
}

Casting

You have probably used casting to convert numeric values from one type to another, i.e.:

int a = 5;
double b = a;

And

int c = (int)b;

What you are actually doing when you cast is transforming a value from one type to another. In the first case, you are taking the value of a (5), and converting it to the equivalent double (5.0). If you consider the internal representation of an integer (a 2’s complement binary number) to a double (an IEEE 754 standard representation), we are actually applying a conversion algorithm to the binary representations.

We call the first operation an implicit cast, as we don’t expressly tell the compiler to perform the cast. In contrast, the second assignment is an explicit cast, as we signify the cast by wrapping the type we are casting to in parenthesis before the variable we are casting. We have to perform an explicit cast in the second case, as the conversion has the possibility of losing some precision (i.e. if we cast 7.2 to an integer, it would be truncated to 7). In any case where the conversion may lose precision or possibly throw an error, an explicit cast is required.

Custom Casting Conversions

We can actually extend the C# language to add additional conversions to provide additional casting operations. Consider if we had Rectangle and Square structs:

/// <summary>A struct representing a rectangle</summary>
public struct Rectangle {
    
    /// <summary>The length of the short side of the rectangle</summary>
    public int ShortSideLength;
    
    /// <summary>The length of the long side of the rectangle</summary>
    public int LongSideLength;
    
    /// <summary>Constructs a new rectangle</summary>
    /// <param name="shortSideLength">The length of the shorter sides of the rectangle</param>
    /// <param name="longSideLength">The length of the longer sides of the rectangle</param>
    public Rectangle(int shortSideLength, int longSideLength){
        ShortSideLength = shortSideLength;
        LongSideLength = longSideLength;
    }
}

/// <summary>A struct representing a square</summary>
public struct Square {

    /// <summary> The length of the square's sides</summary>
    public int SideLength;

    /// <summary>Constructs a new square</summary>
    /// <param name="sideLength">The length of the square's sides</param>
    public Square(int sideLength){
        SideLength = sideLength;
    }
}

Since we know that a square is a special case of a rectangle (where all sides are the same length), we might define an implicit casting operator to convert it into a Rectangle (this would be placed inside the Square struct definition):

    /// <summary>Casts the <paramref name="square"/> into a Rectangle</summary>
    /// <param name="square">The square to cast</param>
    public static implicit operator Rectangle(Square square) 
    {
        return new Rectangle(square.SideLength, square.SideLength);
    }

Similarly, we might create a cast operator to convert a rectangle to a square. But as this can only happen when the sides of the rectangle are all the same size, it would need to be an explicit cast operator , and throw an exception when that condition is not met (this method is placed in the Rectangle struct definition):

    /// <summary>Casts the <paramref name="rectangle"/> into a Square</summary>
    /// <param name="rectangle">The rectangle to cast</param>
    /// <exception cref="System.InvalidCastOperation">The rectangle sides must be equal to cast to a square</exception>
    public static explicit operator Square(Rectangle rectangle){
        if(rectangle.LongSideLength != rectangle.ShortSideLength) throw new InvalidCastException("The sides of a square must be of equal lengths");
        return new Square(rectangle.LongSideLength);
    }

Casting and Inheritance

Casting becomes a bit more involved when we consider inheritance. As you saw in the previous discussion of inheritance, we can treat derived classes as the base class, i.e. the code:

Student sam = new UndergraduateStudent("Sam", "Malone");

Is actually implicitly casting the undergraduate student “Sam Malone” into a student class. Because an UndergraduateStudent is a Student, this cast can be implicit. Moreover, we don’t need to define a casting operator - we can always implicitly cast a class to one of its ancestor classes, it’s built into the inheritance mechanism of C#.

Going the other way requires an explicit cast as there is a chance that the Student we are casting isn’t an undergraduate, i.e.:

UndergraduateStudent u = (UndergraduateStudent)sam;

If we tried to cast sam into a graduate student:

GraduateStudent g = (GraduateStudent)sam;

The program would throw an InvalidCastException when run.

Casting and Interfaces

Casting interacts similarly with interfaces. A class can be implicitly cast to an interface it implements:

IJumpable roo = new Kangaroo();

But must be explicitly cast to convert it back into the class that implemented it:

Kangaroo k = (Kangaroo)roo;

And if that cast is illegal, we’ll throw an InvalidCastException:

Car c = (Car)roo;

The as Operator

When we are casting reference and nullable types, we have an additional casting option - the as casting operator.

The as operator performs the cast, or evaluates to null if the cast fails (instead of throwing an InvalidCastException), i.e.:

UndergraduateStudent u = sam as UndergraduateStudent; // evaluates to an UndergraduateStudent 
GraduateStudent g = sam as GraduateStudent; // evaluates to null
Kangaroo k = roo as Kangaroo; // evaluates to a Kangaroo 
Car c = roo as Kangaroo; // evaluates to null

The is Operator

Rather than performing a cast and catching the exception (or performing a null check when using the as operator), it is often useful to know if a cast is possible. This can be checked for with the is operator. It evaluates to a boolean, true if the cast is possible, false if not:

sam is UndergraduateStudent; // evaluates to true
sam is GraduateStudent; // evaluates to false
roo is Kangaroo; // evaluates to true
roo is Car; // evaluates to false
Warning

The is operator does not work with user-defined casting operators, i.e. when used with the Rectangle/Square cast we defined above:

Square s = new Square(10);
bool test = s is Rectangle;

The value of test will be false, even though we have a user-defined implicit cast that works.

The is operator is commonly used to determine if a cast will succeed before performing it, i.e.:

if(sam is UndergraduateStudent) 
{
    Undergraduate samAsUGrad = sam as UndergraduateStudent;
    // TODO: Do something undergraduate-ey with samAsUGrad
}

This pattern was so commonly employed, it led to the addition of the is type pattern matching expression in C# version 7.0:

if(sam is UndergraduateStudent samAsUGrad) 
{
    // TODO: Do something undergraduate-y with samAsUGrad
}

If the cast is possible, it is performed and the result assigned to the provided variable name (in this case, samAsUGrad). This is another example of syntactic sugar.

Message Dispatching

The term dispatch refers to how a language decides which polymorphic operation (a method or function) a message should trigger.

Consider polymorphic functions in C# (aka Method Overloading, where multiple methods use the same name but have different parameters) like this one for calculating the rounded sum of an array of numbers:

int RoundedSum(int[] a) {
  int sum = 0;
  foreach(int i in a) {
    sum += i;
  }
  return sum;
}

int RoundedSum(float[] a) {
    double sum = 0;
    foreach(int i in a) {
        sum += i;
    }
    return (int)Math.Round(sum);
}

How does the interpreter know which version to invoke at runtime? It should not be a surprise that it is determined by the arguments - if an integer array is passed, the first is invoked, if a float array is passed, the second.

Object-Oriented Polymorphism

However, inheritance can cause some challenges in selecting the appropriate polymorphic form. Consider the following fruit implementations that feature a Blend() method:

/// <summary>
/// A base class representing fruit
/// </summary>
public class Fruit
{
    /// <summary>
    /// Blends the fruit
    /// </summary>
    /// <returns>The result of blending</returns>
    public string Blend() {
      return "A pulpy mess, I guess";
    }
}

/// <summary>
/// A class representing a banana
/// </summary>
public class Banana : Fruit
{
    /// <summary>
    /// Blends the banana
    /// </summary>
    /// <returns>The result of blending the banana</returns>
    public string Blend()
    {
        return "yellow mush";
    }
}

/// <summary>
/// A class representing a Strawberry
/// </summary>
public class Strawberry : Fruit
{
    /// <summary>
    /// Blends the strawberry
    /// </summary>
    /// <returns>The result of blending a strawberry</returns>
    public string Blend()
    {
        return "Gooey Red Sweetness";
    }
}

Let’s add fruit instances to a list, and invoke their Blend() methods:

List<Fruit> toBlend = new List<Fruit>();
toBlend.Add(new Banana());
toBlend.Add(new Strawberry());

foreach(Fruit item in toBlend) {
  Console.WriteLine(item.Blend());
}

You might expect this code to produce the lines:

yellow mush
Gooey Red Sweetness

As these are the return values for the Blend() methods for the Banana and Strawberry classes, respectively. However, we will get:

A pulpy mess, I guess?
A pulpy mess, I guess?

Which is the return value for the Fruit base class Blend() implementation. The line forEach(Fruit item in toBlend) explicitly tells the interpreter to treat the item as a Fruit instance, so of the two available methods (the base or super class implementation), the Fruit base class one is selected.

C# 4.0 introduced a new keyword, dynamic to allow variables like item to be dynamically typed at runtime. Hence, changing the loop to this:

forEach(dynamic item in toBlend) {
  Console.WriteLine(item.Blend());
}

Will give us the first set of results we discussed.

Method Overriding

Of course, part of the issue in the above example is that we actually have two implementations for Blend() available to each fruit. If we wanted all bananas to use the Banana class’s Blend() method, even when the banana was being treated as a Fruit, we need to override the base method instead of creating a new one that hides it (in fact, in Visual Studio we should get a warning that our new method hides the base implementation, and be prompted to add the new keyword if that was our intent).

To override a base class method, we first must mark it as abstract or virtual. The first keyword, abstract, indicates that the method does not have an implementation (a body). The second, virtual, indicates that the base class does provide an implementation. We should use abstract when each derived class will define its own implementation, and virtual when some derived classes will want to use a common base implementation. Then, we must mark the method in the derived class with the override keyword.

Considering our Fruit class, since we’re providing a unique implementation of Blend() in each derived class, the abstract keyword is more appropriate:

/// <summary>
/// A base class representing fruit
/// </summary>
public abstract class Fruit : IBlendable
{
    /// <summary>
    /// Blends the fruit
    /// </summary>
    /// <returns>The result of blending</returns>
    public abstract string Blend();
}

As you can see above, the Blend() method does not have a body, only the method signature.

Also, note that if we use an abstract method in a class, the class itself must also be declared abstract. The reason should be clear - an abstract method cannot be called, so we should not create an object that only has the abstract method. The virtual keyword can be used in both abstract and regular classes.

Now we can override the Blend() method in Banana class:

/// <summary>
/// A class representing a banana
/// </summary>
public class Banana : Fruit
{
    /// <summary>
    /// Blends the banana
    /// </summary>
    /// <returns>The result of blending the banana</returns>
    public override string Blend()
    {
        return "yellow mush";
    }
}

Now, even if we go back to our non-dynamic loop that treats our fruit as Fruit instances, we’ll get the result of the Banana class’s Blend() method.

We can override any method marked abstract, virtual, or override (this last will only occur in a derived class whose base class is also derived, as it is overriding an already-overridden method).

Sealed Methods

We can also apply the sealed keyword to overridden methods, which prevents them from being overridden further. Let’s apply this to the Strawberry class:

/// <summary>
/// A class representing a Strawberry
/// </summary>
public class Strawberry : Fruit
{
    /// <summary>
    /// Blends the strawberry
    /// </summary>
    /// <returns>The result of blending a strawberry</returns>
    public sealed override string Blend()
    {
        return "Gooey Red Sweetness";
    }
}

Now, any class inheriting from Strawberry will not be allowed to override the Blend() method.

C# Collections

Collections in C# are a great example of polymorphism in action. Many collections utilize generics to allow the collection to hold an arbitrary type. For example, the List<T> can be used to hold strings, integers, or even specific objects:

List<string> strings = new List<string>();
List<int> ints = new List<int>();
List<Person> persons = new List<Person>();

We can also use an interface as the type, as we did with the IJumpable interface as we discussed in the generics section, i.e.:

List<IJumpable> jumpables = new List<IJumpable>();
jumpables.Add(new Kangaroo());
jumpables.Add(new Car());
jumpables.Add(new Kangaroo());

Collection Interfaces

The C# language and system libraries also define a number of interfaces that apply to custom collections. Implementing these interfaces allows different kinds of data structures to be utilized in a standardized way.

The IEnumerable<T> Interface

The first of these is the IEnumerable<T> interface, which requires the collection to implement one method:

  • public IEnumerator<T> GetEnumerator()

Implementing this interface allows the collection to be used in a foreach loop.

The ICollection<T> Interface

C# Collections also typically implement the ICollection<T> interface, which extends the IEnumerable<T> interface and adds additional methods:

  • public void Add<T>(T item) adds item to the collection
  • public void Clear() empties the collection
  • public bool Contains(T item) returns true if item is in the collection, false if not.
  • public void CopyTo(T[] array, int arrayIndex) copies the collection contents into array, starting at arrayIndex.
  • public bool Remove(T item) removes item from the collection, returning true if item was removed, false otherwise

Additionally, the collection must implement the following properties:

  • int Count the number of items in the collection
  • bool IsReadOnly the collection is read-only (can’t be added to or removed from)

The IList<T> Interface

Finally, collections that have an implied order and are intended to be accessed by a specific index should probably implement the IList<T> interface, which extends ICollection<T> and IEnumerable<T>. This interface adds these additional methods:

  • public int IndexOf(T item) returns the index of item in the list, or -1 if not found
  • public void Insert(int index, T item) Inserts item into the list at position index
  • public void RemoveAt(int index) Removes the item from the list at position index

The interface also adds the property:

  • Item[int index] which gets or sets the item at index.

Collection Implementation Strategies

When writing a C# collection, there are three general strategies you can follow to ensure you implement the corresponding interfaces:

  1. Write the entire class by scratch
  2. Implement the interface methods as a pass-through to a system library collection
  3. Inherit from a system library collection

Writing collections from scratch was the strategy you utilized in CIS 300 - Data Structures and Algorithms. While this strategy gives you the most control, it is also the most time-consuming.

The pass-through strategy involves creating a system library collection, such as a List<T>, as a private field in your collection class. Then, when you implement the necessary interface methods, you simply pass through the call to the private collection. I.e.:

public class PassThroughList<T> : IList<T>
{
  private List<T> _list = new List<T>;

  public IEnumerator<T> GetEnumerator() 
  {
    return _list.GetEnumerator();
  } 

  // TODO: Implement remaining methods and properties...
}

Using this approach, you can add whatever additional logic your collection needs into your pass-through methods without needing to re-implement the basic collection functionality.

Using inheritance gives your derived class all of the methods of the base class, so if you extend a class that already implements the collection interfaces, you’ve already got all the methods!

public class InheritedList<T> : List<T>
{
  // All IList<T>, ICollection<T>, and IEnumerable<T> methods 
  // from List<T> are already defined on InheritedList<T>
}

However, most system collection class methods are not declared as virtual, so you cannot override them to add custom functionality.

Summary

In this chapter, we explored the concept of types and discussed how variables are specific types that can be explicitly or implicitly declared. We saw how in a statically-typed language (like C#), variables are not allowed to change types (though they can do so in a dynamically-typed language). We also discussed how casting can convert a value stored in a variable into a different type. Implicit casts can happen automatically, but explicit casts must be indicated by the programmer using a cast operator, as the cast could result in loss of precision or the throwing of an exception.

We explored how class declarations and interface declarations create new types. We saw how polymorphic mechanisms like interface implementation and inheritance allow objects to be treated as (and cast to) different types. We also introduced the as and is casting operators, which can be used to cast or test the ability to cast, respectively. We saw that if the as cast operator fails, it evaluates to null instead of throwing an exception. We also saw the is type pattern expression, which simplifies a casting test and casting operation into a single expression.

Next, we looked at how C# collections leverage the use of interfaces, inheritance, and generic types to quickly and easily make custom collection objects that interact with the C# language in well-defined ways.

Finally, we explored how messages are dispatched when polymorphism is involved. We saw that the method invoked depends on what Type we are currently treating the object as. We saw how the C# modifiers protected, abstract, virtual, override, and sealed interacted with this message dispatch process. We also saw how the dynamic type could delay determining an object’s type until runtime.

Chapter 3

Documentation

Coding for Humans

Subsections of Documentation

Introduction

As part of the strategy for tackling the challenges of the software crisis, good programming practice came to include writing clear documentation to support both the end-users who will utilize your programs, as well as other programmers (and yourself) in understanding what that code is doing so that it is easy to maintain and improve.

Key Terms

Some key terms to learn in this chapter are:

  • User documentation
  • Developer documentation
  • Markdown
  • XML
  • Autodoc tools
  • Intellisense

Key Skills

The key skill to learn in this chapter is how to use C# XML code comments to document the C# code you write.

Documentation

Documentation refers to the written materials that accompany program code. Documentation plays multiple, and often critical roles. Broadly speaking, we split documentation into two categories based on the intended audience:

  • User Documentation is meant for the end-users of the software
  • Developer Documentation is meant for the developers of the software

As you might expect, the goals for these two styles of documentation are very different. User documentation instructs the user on how to use the software. Developer documentation helps orient the developer so that they can effectively create, maintain, and expand the software.

Historically, documentation was printed separately from the software. This was largely due to the limited memory available on most systems. For example, the EPIC software we discussed had two publications associated with it: a User Manual, which explains how to use it, and Model Documentation which presents the mathematic models that programmers adapted to create the software. There are a few very obvious downsides to printed manuals: they take substantial resources to produce and update, and they are easily misplaced.

User Documentation

As memory became more accessible, it became commonplace to provide digital documentation to the users. For example, with Unix (and Linux) systems, it became commonplace to distribute digital documentation alongside the software it documented. This documentation came to be known as man pages based on the man command (short for manual) that would open the documentation for reading. For example, to learn more about the linux search tool grep, you would type the command:

$ man grep 

Which would open the documentation distributed with the grep tool. Man pages are written in a specific format; you can read more about it here.

While a staple of the Unix/Linux filesystem, there was no equivalent to man pages in the DOS ecosystem (the foundations of Windows) until Powershell was introduced, which has the Get-Help tool. You can read more about it here.

However, once software began to be written with graphical user interfaces (GUIs), it became commonplace to incorporate the user documentation directly into the GUI, usually under a “Help” menu. This served a similar purpose to man pages of ensuring user documentation was always available with the software. Of course, one of the core goals of software design is to make the software so intuitive that users don’t need to reference the documentation. It is equally clear that developers often fall short of that mark, as there is a thriving market for books to teach certain software.

Example Software Books Example Software Books

Not to mention the thousands of YouTube channels devoted to teaching specific programs!

Developer Documentation

Developer documentation underwent a similar transformation. Early developer documentation was often printed and placed in a three-ring binder, as Neal Stephenson describes in his novel Snow Crash: 1

Fisheye has taken what appears to be an instruction manual from the heavy black suitcase. It is a miniature three-ring binder with pages of laser-printed text. The binder is just a cheap unmarked one bought from a stationery store. In these respects, it is perfectly familiar to Him: it bears the earmarks of a high-tech product that is still under development. All technical devices require documentation of a sort, but this stuff can only be written by the techies who are doing the actual product development, and they absolutely hate it, always put the dox question off to the very last minute. Then they type up some material on a word processor, run it off on the laser printer, send the departmental secretary out for a cheap binder, and that's that.

Shortly after the time this novel was written, the internet became available to the general public, and the tools it spawned would change how software was documented forever. Increasingly, web-based tools are used to create and distribute developer documentation. Wikis, bug trackers, and autodocumentation tools quickly replaced the use of lengthy, and infrequently updated word processor files.


  1. Neal Stephenson, “Snow Crash.” Bantam Books, 1992. ↩︎

Documentation Formats

Developer documentation often faces a challenge not present in other kinds of documents - the need to be able to display snippets of code. Ideally, we want code to be formatted in a way that preserves indentation. We also don’t want code snippets to be subject to spelling- and grammar-checks, especially auto-correct versions of these algorithms, as they will alter the snippets. Ideally, we might also apply syntax highlighting to these snippets. Accordingly, a number of textual formats have been developed to support writing text with embedded program code, and these are regularly used to present developer documentation. Let’s take a look at several of the most common.

HTML

Since its inception, HTML has been uniquely suited for developer documentation. It requires nothing more than a browser to view - a tool that nearly every computer is equipped with (in fact, most have two or three installed). And the <code> element provides a way of styling code snippets to appear differently from the embedded text, and <pre> can be used to preserve the snippet’s formatting. Thus:

<p>This algorithm reverses the contents of the array, <code>nums</code></p>
<pre>
<code>
for(int i = 0; i < nums.Length/2; i++) {
    int tmp = nums[i];
    nums[i] = nums[nums.Length - 1 - i];
    nums[nums.Length - 1 - i] = tmp;
}
</code>
</pre>

Will render in a browser as:

This algorithm reverses the contents of the array, nums


for(int i = 0; i < nums.Length/2; i++) {
    int tmp = nums[i];
    nums[i] = nums[nums.Length - 1 - i];
    nums[nums.Length - 1 - i] = tmp;
}

JavaScript and CSS libraries like highlight.js, prism, and others can provide syntax highlighting functionality without much extra work.

Of course, one of the strongest benefits of HTML is the ability to create hyperlinks between pages. This can be invaluable in documenting software, where the documentation about a particular method could include links to documentation about the classes being supplied as parameters, or being returned from the method. This allows developers to quickly navigate and find the information they need as they work with your code.

Markdown

However, there is a significant amount of boilerplate involved in writing a webpage (i.e. each page needs a minimum of elements not specific to the documentation to set up the structure of the page). The extensive use of HTML elements also makes it more time-consuming to write and harder for people to read in its raw form. Markdown is a markup language developed to counter these issues. Markdown is written as plain text, with a few special formatting annotations, which indicate how it should be transformed to HTML. Some of the most common annotations are:

  • Starting a line with hash (#) indicates it should be a <h1> element, two hashes (##) indicates a <h2>, and so on…
  • Wrapping a statement with underscores (_) or asterisks (*) indicates it should be wrapped in a <i> element
  • Wrapping a statement with double underscores (__) or double asterisks (**) indicates it should be wrapped in a <b> element
  • Links can be written as [link text](url), which is transformed to <a href="url">link text</a>
  • Images can be written as ![alt text](url), which is transformed to <img alt="alt text" src="url"/>

Code snippets are indicated with backtick marks (`). Inline code is written surrounded with single backtick marks, i.e. `int a = 1` and in the generated HTML is wrapped in a <code> element. Code blocks are wrapped in triple backtick marks, and in the generated HTML are enclosed in both <pre> and <code> elements. Thus, to generate the above HTML example, we would use:


This algorithm reverses the contents of the array, `nums`
```
for(int i = 0; i < nums.Count/2; i++) {
    int tmp = nums[i];
    nums[i] = nums[nums.Count - 1 - i];
    nums[nums.Count - 1 - i] = tmp;
}
```

Most markdown compilers also support specifying the language (for language-specific syntax highlighting) by following the first three backticks with the language name, i.e.:


```csharp
List = new List;
```

Nearly every programming language features at least one open-source library for converting Markdown to HTML. Microsoft even includes a C# one in the Windows Community Toolkit. In addition to being faster to write than HTML, and avoiding the necessity to write boilerplate code, Markdown offers some security benefits. Because it generates only a limited set of HTML elements, which specifically excludes some most commonly employed in web-based exploits (like using <script> elements for script injection attacks), it is often safer to allow users to contribute markdown-based content than HTML-based content. Note: this protection is dependent on the settings provided to your HTML generator - most markdown converters can be configured to allow or escape HTML elements in the markdown text

In fact, this book was written using Markdown, and then converted to HTML using the Hugo framework, a static website generator built using the Go programming language.

Additionally, chat servers like RocketChat and Discord support using markdown in posts!

GitHub even incorporates a markdown compiler into its repository displays. If your file ends in a .md extension, GitHub will evaluate it as Markdown and display it as HTML when you navigate your repo. If your repository contains a README.md file at the top level of your project, it will also be displayed as the front page of your repository. GitHub uses an expanded list of annotations known as GitHub-flavored markdown that adds support for tables, task item lists, strikethroughs, and others.

Info

It is best practice to include a README.md file at the top level of a project. This document provides an overview of the project, as well as helpful instructions on how it is to be used and where to go for more information. For open-source projects, you should also include a LICENSE file that contains the terms of the license the software is released under.

XML

Extensible Markup Language (XML) is a close relative of HTML - they share the same ancestor, Standard Generalized Markup Language (SGML). It allows developers to develop their own custom markup languages based on the XML approach, i.e. the use of elements expressed via tags and attributes. XML-based languages are usually used as a data serialization format. For example, this snippet represents a serialized fictional student:

<student>
    <firstName>Willie</firstName>
    <lastName>Wildcat</lastName>
    <wid>8888888</wid>
    <degreeProgram>BCS</degreeProgram>
</student>

While XML is most known for representing data, it is one of Microsoft’s go-to tools. For example, they have used it as the basis of Extensible Application Markup Language (XAML), which is used in Windows Presentation Foundation as well as cross-platform Xamrin development. So it shouldn’t be a surprise that Microsoft also adopted it for their autodocumentation code commenting strategy. We’ll take a look at this next.

Autodocs

One of the biggest innovations in documenting software was the development of autodocumentation tools. These were programs that would read source code files, and combine information parsed from the code itself and information contained in code comments to generate documentation in an easy-to-distribute form (often HTML). One of the earliest examples of this approach came from the programming language Java, whose API specification was generated from the language source files using JavaDoc.

This approach meant that the language of the documentation was embedded within the source code itself, making it far easier to update the documentation as the source code was refactored. Then, every time a release of the software was built (in this case, the Java language), the documentation could be regenerated from the updated comments and source code. This made it far more likely developer documentation would be kept up-to-date.

Microsoft adopted a similar strategy for the .NET languages, known as XML comments. This approach was based on embedding XML tags into comments above classes, methods, fields, properties, structs, enums, and other code objects. These comments are set off with a triple forward slash (///) to indicate the intent of being used for autodoc generation. Comments using double slashes (//) and slash-asterisk notation (/* */) are ignored in this autodoc scheme.

For example, to document an Enum, we would write:

/// <summary>
/// An enumeration of fruits used in pies 
/// </summary>
public enum Fruit {
    Cherry,
    Apple,
    Blueberry,
    Peach
}

At a bare minimum, comments should include a <summary> element containing a description of the code structure being described.

Let’s turn our attention to documenting a class:

public class Vector2 {

    public float X {get; set;}

    public float Y {get; set;}

    public Vector2(float x, float y) {
        X = x;
        Y = y;
    }

    public void Scale(float scalar) {
        X *= scalar;
        Y *= scalar;
    }

    public float DotProduct(Vector2 other) {
        return this.X * other.X + this.Y * other.Y;
    }

    public float Normalize() {
        float magnitude = Math.Sqrt(Math.Pow(this.X, 2), Math.Pow(this.Y, 2));
        if(magnitude == 0) throw new DivideByZeroException();
        X /= magnitude;
        Y /= magnitude;
    }
}

We would want to add a <summary> element just above the class declaration, i.e.:

/// <summary>
/// A class representing a two-element vector composed of floats 
/// </summary>

Properties should be described using the <summary> element, i.e.:

/// <summary>
/// The x component of the vector 
/// </summary>

And methods should use <summary>, plus <param> elements to describe parameters. It has an attribute of name that should be set to match the parameter it describes:

/// <summary>
/// Constructs a new two-element vector 
/// </summary>
/// <param name="x">The X component of the new vector</param>
/// <param name="y">The Y component of the new vector</param>

The <paramref> can be used to reference a parameter in the <summary>:

/// <summary>
/// Scales the Vector2 by the provided <paramref name="scalar"/> 
/// </summary>
/// <param name="scalar">The value to scale the vector by</param>

If a method returns a value, this should be indicated with the <returns> element:

/// <summary>
/// Computes the dot product of this and an <paramref name="other"> vector
/// </summary>
/// <param name="other">The vector to compute a dot product with</param>
/// <returns>The dot product</returns>

And, if a method might throw an exception, this should be also indicated with the <exception> element, which uses the cref attribute to indicate the specific exception:

/// <summary>
/// Normalizes the vector
/// </summary>
/// <remarks>
/// This changes the length of the vector to one unit.  The direction remains unchanged 
/// </remarks>
/// <exception cref="System.DivideByZeroException">
/// Thrown when the length of the vector is 0.
/// </exception>

Note too, the use of the <remarks> element in the above example to add supplemental information. The <example> element can also be used to provide examples of using the class, method, or other code construct. There are more elements available, like <see> and <seealso> that generate links to other documentation, <para>, and <list> which are used to format text, and so on.

Of especial interest are the <code> and <c> elements, which format code blocks and inline code, respectively.

See the official documentation for a complete list and discussion.

Thus, our completely documented class would be:

/// <summary>
/// A class representing a two-element vector composed of floats 
/// </summary>
public class Vector2 {

    /// <summary>
    /// The x component of the vector 
    /// </summary>
    public float X {get; set;}

    /// <summary>
    /// The y component of the vector 
    /// </summary>
    public float Y {get; set;}

    /// <summary>
    /// Constructs a new two-element vector 
    /// </summary>
    /// <param name="x">The X component of the new vector</param>
    /// <param name="y">The Y component of the new vector</param>
    public Vector2(float x, float y) {
        X = x;
        Y = y;
    }

    /// <summary>
    /// Scales the Vector2 by the provided <paramref name="scalar"/> 
    /// </summary>
    /// <param name="scalar">The value to scale the vector by</param>
    public void Scale(float scalar) {
        X *= scalar;
        Y *= scalar;
    }

    /// <summary>
    /// Computes the dot product of this and an <paramref name="other"> vector
    /// </summary>
    /// <param name="other">The vector to compute a dot product with</param>
    /// <returns>The dot product</returns>
    public float DotProduct(Vector2 other) {
        return this.X * other.X + this.Y * other.Y;
    }

    /// <summary>
    /// Normalizes the vector
    /// </summary>
    /// <remarks>
    /// This changes the length of the vector to one unit.  The direction remains unchanged 
    /// </remarks>
    /// <exception cref="System.DivideByZeroException">
    /// Thrown when the length of the vector is 0.
    /// </exception>
    public float Normalize() {
        float magnitude = Math.Sqrt(Math.Pow(this.X, 2), Math.Pow(this.Y, 2));
        if(magnitude == 0) throw new DivideByZeroException();
        X /= magnitude;
        Y /= magnitude;
    }
}

With the exception of the <remarks>, the XML documentation elements used in the above code should be considered the minimum for best practices. That is, every Class, Struct, and Enum should have a <summary>. Every property should have a <summary>. And every method should have a <summary>, a <param> for every parameter, a <returns> if it returns a value (this can be omitted for void) and an <exception> for every exception it might throw.

There are multiple autodoc programs that generate documentation from XML comments embedded in C# code, including open-source Sandcastle Help File Builder and the simple Docu, as well as multiple commercial products.

However, the perhaps more important consumer of XML comments is Visual Studio, which uses these comments to power its Intellisense features, displaying text from the comments as tooltips as you edit code. This intellisense data is automatically built into DLLs built from Visual Studio, making it available in projects that utilize compiled DLLs as well.

Summary

In this chapter, we examined the need for software documentation aimed at both end-users and developers (user documentation and developer documentation respectively). We also examined some formats this documentation can be presented in: HTML, Markdown, and XML. We also discussed autodocumentation tools, which generate developer documentation from specially-formatted comments in our code files.

We examined the C# approach to autodocumentation, using Microsoft’s XML code comments formatting strategy. We explored how this data is used by Visual Studio to power its Intellisense features, and provide useful information to programmers as they work with constructs like classes, properties, and methods. For this reason, as well as the ability to produce HTML-based documentation using an autodocumentation tool, it is best practice to use XML code comments in all your C# programs.

Chapter 4

Testing

Is it Working Yet?

Subsections of Testing

Introduction

A critical part of the software development process is ensuring the software works! We mentioned earlier that it is possible to logically prove that software works by constructing a state transition table for the program, but once a program reaches a certain size this strategy becomes less feasible. Similarly, it is possible to model a program mathematically and construct a theorem that proves it will perform as intended. But in practice, most software is validated through some form of testing. This chapter will discuss the process of testing object-oriented systems.

Key Terms

Some key terms to learn in this chapter are:

  • Informal Testing
  • Formal Testing
  • Test Plan
  • Test Framework
  • Automated Testing
  • Assertions
  • Unit tests
  • Testing Code Coverage
  • Regression Testing

Key Skills

The key skill to learn in this chapter is how to write C# unit test code using xUnit and the Visual Studio Test Explorer.

Manual Testing

As you’ve developed programs, you’ve probably run them, supplied input, and observed if what happened was what you wanted. This process is known as informal testing. It’s informal, because you don’t have a set procedure you follow, i.e. what specific inputs to use, and what results to expect. Formal testing adds that structure. In a formal test, you would have a written procedure to follow, which specifies exactly what inputs to supply, and what results should be expected. This written procedure is known as a test plan.

Historically, the test plan was often developed at the same time as the design for the software (but before the actual programming). The programmers would then build the software to match the design, and the completed software and the test plan would be passed onto a testing team that would follow the step-by-step testing procedures laid out in the testing plan. When a test failed, they would make a detailed record of the failure, and the software would be sent back to the programmers to fix.

This model of software development has often been referred to as the ‘waterfall model’ as each task depends on the one before it:

The Waterfall Model of Software Development The Waterfall Model of Software Development

Unfortunately, as this model is often implemented, the programmers responsible for writing the software are reassigned to other projects as the software moves into the testing phase. Rather than employ valuable programmers as testers, most companies will hire less expensive workers to carry out the testing. So either a skeleton crew of programmers is left to fix any errors that are found during the tests, or these are passed back to programmers already deeply involved in a new project.

The costs involved in fixing software errors also grow larger the longer the error exists in the software. The table below comes from a NASA report of software error costs throughout the project life cycle: 1

Comparison of System Cost Factors Excluding Operations Comparison of System Cost Factors Excluding Operations

It is clear from the graph and the paper that the cost to fix a software error grows exponentially if the fix is delayed. You probably have instances in your own experience that also speak to this - have you ever had a bug in a program you didn’t realize was there until your project was nearly complete? How hard was it to fix, compared to an error you found and fixed right away?

It was realizations like these, along with growing computing power that led to the development of automated testing, which we’ll discuss next.


  1. Jonette M. Stecklein, Jim Dabney, Brandon Dick, Bill Haskins, Randy Lovell, and Gregory Maroney. “Error Cost Escalation Through the Project Life Cycle”, NASA, June 19, 2014. ↩︎

Automated Testing

Automated testing is the practice of using a program to test another program. Much as a compiler is a program that translates a program from a higher-order language into a lower-level form, a test program executes a test plan against the program being tested. And much like you must supply the program to be compiled, for automated testing you must supply the tests that need to be executed. In many ways the process of writing automated tests is like writing a manual test plan - you are writing instructions of what to try, and what the results should be. The difference is with a manual test plan, you are writing these instructions for a human. With an automated test plan, you are writing them for a program.

Automated tests are typically categorized as unit, integration, and system tests:

  • Unit tests focus on a single unit of code, and test it in isolation from other parts of the code. In object-oriented programs where code is grouped into objects, these are the units that are tested. Thus, for each class you would have a corresponding file of unit tests.
  • Integration tests focus on the interaction of units working together, and with infrastructure external to the program (i.e. databases, other programs, etc).
  • System tests look at the entire program’s behavior.

The complexity of writing tests scales with each of these categories. Emphasis is usually put on writing unit tests, especially as the classes they test are written. By testing these classes early, errors can be located and fixed quickly.

Writing Tests

Writing tests is in many ways just as challenging and creative an endeavor as writing programs. Tests usually consist of invoking some portion of program code, and then using assertions to determine that the actual results match the expected results. The results of these assertions are typically reported on a per-test basis, which makes it easy to see where your program is not behaving as expected.

Consider a class that is a software control system for a kitchen stove. It might have properties for four burners, which correspond to what heat output they are currently set to. Let’s assume this is as an integer between 0 (off) and 5 (high). When we first construct this class, we’d probably expect them all to be off! A test to verify that expectation would be:

public class StoveTests {

    [Fact]
    public void BurnersShouldBeOffAtInitialization() {
        Stove stove = new Stove();
        Assert.Equal(0, stove.BurnerOne);
        Assert.Equal(0, stove.BurnerTwo);
        Assert.Equal(0, stove.BurnerThree);
        Assert.Equal(0, stove.BurnerFour);
    }
}

Here we’ve written the test using the C# xUnit test framework, which is being adopted by Microsoft as their preferred framework, replacing the nUnit test framework (there are many other C# test frameworks, but these two are the most used).

Notice that the test is simply a method, defined in a class. This is very common for test frameworks, which tend to be written using the same programming language the programs they test are written in (which makes it easier for one programmer to write both the code unit and the code to test it). Above the class appears an attribute - [Fact]. Attributes are a way of supplying metadata within C# code. This metadata can be used by the compiler and other programs to determine how it works with your code. In this case, it indicates to the xUnit test runner that this method is a test.

Inside the method, we create an instance of stove, and then use the Assert.Equal<T>(T expected, T actual) method to determine that the actual and expected values match. If they do, the assertion is marked as passing, and the test runner will display this pass. If it fails, the test runner will report the failure, along with details to help find and fix the problem (what value was expected, what it actually was, and which test contained the assertion).

The xUnit framework provides for two kinds of tests, Facts, which are written as functions that have no parameters, and Theories, which do have parameters. The values for these parameters are supplied with another attribute, typically [InlineData]. For example, we might test that when we set a burner to a setting within the valid 0-5 range, it is set to that value:

[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
[InlineData(4)]
[InlineData(5)]
public void ShouldBeAbleToSetBurnerOneToValidRange(int setting) {
    Stove stove = new Stove();
    stove.BurnerOne = setting;
    Assert.Equal(setting, stove.BurnerOne);
}

The values in the parentheses of the InlineData are the values supplied to the parameter list of the theory method. Thus, this test is actually six tests; each test makes sure that one of the settings is working. We could have done all six as separate assignments and assertions within a single fact, but using a theory means that if only one of these settings doesn’t work, we will see that one test fail while the others pass. This level of specificity can be very helpful in finding errors.

So far our tests cover the expected behavior of our stove. But where tests really prove their worth is with the edge cases - those things we as programmers don’t anticipate. For example, what happens if we try setting our range to a setting above 5? Should it simply clamp at 5? Should it not change from its current setting? Or should it shut itself off entirely because its user is clearly a pyromaniac bent on burning down their house? If the specification for our program doesn’t say, it is up to us to decide. Let’s say we expect it to be clamped at 5:

[Theory]
[InlineData(6)]
[InlineData(18)]
[InlineData(1000000)]
public void BurnerOneShouldNotExceedASettingOfFive(int setting) {
    Stove stove = new Stove();
    stove.BurnerOne = setting;
    Assert.Equal(5, stove.BurnerOne);
}

Note that we don’t need to exhaustively test all numbers above 5 - it is sufficient to provide a representative sample, ideally the first value past 5 (6), and a few others. Also, now that we have defined our expected behavior, we should make sure the documentation of our BurnerOne property matches it:

/// <summary>
/// The setting of burner one 
/// </summary>
/// <value>
/// An integer between 0 (off) and 5 (high)
/// </value>
/// <remarks>
/// If a value higher than 5 is attempted, the burner will be set to 5
/// </remarks>
public int BurnerOne {get; set;}

This way, other programmers (and ourselves, if we visit this code years later) will know what the expected behavior is. We’d also want to test the other edge cases: i.e. when the burner is set to a negative number.

Warning

Recognizing and testing for edge cases is a critical aspect of test writing. But it is also a difficult skill to develop, as we have a tendency to focus on expected values and expected use-cases for our software. But most serious errors occur when values outside these expectations are introduced. Also, remember special values, like double.PositiveInfinity, double.NegativeInfinity, and double.NaN.

xUnit Assertions

Like most testing frameworks, the xUnit framework provides a host of specialized assertions.

Boolean Assertions

For example, xUnit provides two boolean assertions:

  • Assert.True(bool actual), asserts that the value supplied to the actual parameter is true.
  • Assert.False(bool actual), asserts that the value supplied to the actual parameter is false.

While it may be tempting to use Assert.True() for all tests, i.e. Assert.True(stove.BurnerOne == 0), it is better practice to use the specialized assertion that best matches the situation, in this case Assert.Equal<T>(T expected, T actual) as a failing test will supply more details.

Equality Assertions

The Assert.Equal<T>(T expected, T actual) is the workhorse of the assertion library. Notice it is a template method, so it can be used with any type that is comparable (which is pretty much everything possible in C#). It also has an override, Assert.Equal<T>(T expected, T actual, int precision) which allows you to specify the precision for floating-point numbers. Remember that floating point error can cause two calculated values to be slightly different than one another; specifying a precision allows you to say just how close to the expected an actual value needs to be to be considered ’equal’ for the purposes of the test.

Like most assertions, it is paired with an opposite, Assert.NotEqual<T>(T expected, T actual), which also has an override for supplying precision.

Numeric Assertions

With numeric values, it can be handy to determine if the value falls within a range:

  • Assert.InRange<T>(T actual, T low, T high) asserts actual falls between low and high (inclusive), and
  • Assert.NotInRange<T>(T actual, T low, T high) asserts actual does not fall between low and high (inclusive)

Reference Assertions

There are special assertions to deal with null references:

  • Assert.Null(object object) asserts the supplied object is null, and
  • Assert.NotNull(object object) asserts the supplied object is not null

In addition, two objects may be considered equal, but may or may not be the same object (i.e. not referencing the same memory). This can be asserted with:

  • Assert.Same(object expected, object actual) asserts the expected and actual object references are to the same object, while
  • Assert.NotSame(object expected, object actual) asserts the expected and actual object references are not the same object

Type Assertions

At times, you may want to assure it is possible to cast an object to a specific type. This can be done with:

  • Assert.IsAssignableFrom<T>(object obj) Where T is the type to cast into.

At other times, you may want to assert that the object is exactly the type you expect (.e. T is not an interface or base class of obj). That can be done with:

  • Assert.IsType<T>(object obj)

Collection Assertions

There are a host of assertions for working with collections:

  • Assert.Empty(IEnumerable collection) asserts that the collection is empty, while
  • Assert.NotEmpty(IEnumerable collection) asserts that it is not empty
  • Assert.Contains<T>(T expected, IEnumerable<T> collection) asserts that the expected item is found in the collection, while
  • Assert.DoesNotContain<T>(T expected, IEnumerable<T> collection) asserts the expected item is not found in the collection

In addition to the simple equality check form of Assert.Contains<T>() and Assert.DoesNotContain<T>(), there is a version that takes a filter expression (an expression that evaluates to true or false indicating that an item was found) written as a lambda expression. For example, to determine if a list of Fruit contains an Orange we could use:

List<Fruit> fruits = new List<Fruit>() {
    new Orange(),
    new Apple(),
    new Grape(),
    new Banana() {Overripe = true}
};
Assert.Contains(fruits, item => item is Orange);

The expression item is Orange is run on each item in fruits until it evaluates to true or we run out of fruit to check. We can also supply curly braces with a return statement if we need to perform more complex logic:

Assert.Contains(fruits, item => {
    if(item is Banana banana) {
        if(banana.Overripe) return true;
    }
    return false;
});

Here we only return true for overripe bananas. Using Assert.Contains() with a filter expression can be useful for checking that expected items are in a collection. To check that the collection also does not contain unexpected items, we can test the length of the collection against the expected number of values, i.e.:

Assert.True(fruits.Count == 4, $"Expected 4 items but found {fruits.Count}");

Here we use the Assert.True() overload that allows a custom message when the test fails.

Finally, Assert.Collection<T>(IEnumerable<T> collection, Action<T>[] inspectors) can apply specific inspectors against each item in a collection. Using the same fruits list as above:

Assert.Collection(fruits, 
    item => Assert.IsType<Orange>(item),
    item => Assert.IsType<Apple>(item),
    item => Assert.IsType<Grape>(item),
    item => {
        Assert.IsType<Banana>(item);
        Assert.True(((Banana)item).Overripe);
    }
);

Here we use an Action delegate to map each item in the collection to an assertion. These actions are written using [lambda expressions], which are conceptually functions.

The number of actions should correspond to the expected size of the collection, and the items supplied to the actions must be in the same order as they appear in the collection. Thus, the Assert.Collection() is a good choice when the collection is expected to always be in the same order, while the Assert.Contains() approach allows for variation in the ordering.

Exception Assertions

Error assertions also use Action delegate, in this case to execute code that is expected to throw an exception, i.e. we could test for System.DivideByZeroException with:

[Fact]
public void DivisionByZeroShouldThrowException() {
    Assert.Throws(System.DivideByZeroException, () => {
        var tmp = 10.0/0.0;
    });
}

Note how we place the code that is expected to throw the exception inside the body of the Action? This allows the assertion to wrap it in a try/catch internally. The exception-related assertions are:

  • Assert.Throws(System.Exception expectedException, Action testCode) asserts the supplied expectedException is thrown when testCode is executed
  • Assert.Throws<T>(Action testCode) where T : System.Exception the templated version of the above
  • Assert.ThrowsAny<T>(Action testCode) where T: System.Exception asserts that any exception will be thrown by the testCode when executed

There are also similar assertions for exceptions being thrown in asynchronous code. These operate nearly identically, except instead of supplying an Action, we supply a Task:

  • Assert.ThrowsAsync<T>(Task testCode) where T : System.Exception asserts the supplied exception type T is thrown when testCode is executed
  • Assert.ThrowsAnyAsync<T>(Task testCode) where T: System.Exception is the asynchronous version of the previous assertion, asserts the supplied exception type T will be thrown some point after testCode is executed.

Events Assertions

Asserting that events will be thrown also involves Action delegate, and is a bit more involved as it requires three. The first delegate is for attaching the assertion-supplied event handler to the listener, the second for detaching it, and the third is for triggering the event with the actual code involved.

For example, assume we have a class, Emailer, with a method SendEmail(string address, string body) that should have an event handler EmailSent whose event args are EmailSentEventArgs. We could test that this class was actually raising this event with:

[Fact]
public void EmailerShouldRaiseEmailSentWhenSendingEmails()
{
    string address = "test@test.com";
    string body = "this is a test";
    Emailer emailer = new Emailer();
    Assert.Raises<EmailSentEventArgs>(
        listener => emailer += listener, // This action attaches the listener
        listener => emailer -= listener, // This action detaches the listener 
        () => {
            emailer.SendEmail(address, body);
        }
    )
}

The various event assertions are:

  • Assert.Raises<T>(Action attach, Action detach, Action testCode)
  • Assert.RaisesAny<T>(Action attach, Action detach, Action testCode)

There are also similar assertions for events being raised by asynchronous code. These operate nearly identically, except instead of supplying an Action, we supply a Task:

  • Assert.RaisesAsync<T>(Action attach, Action detach, Task testCode)
  • Assert.RaisesAnyAsync<T>(Action attach, Action detach, Task testCode)

For examples of these assertions, see section 2.3.10

Info

XUnit does not directly support old-style events - those with a named event handler like CollectionChangedEventHandler, only those that use the templated form: EventHandler<CustomEventArgs> (with the exception of the PropertyChanged event, discussed below). For strategies to handle the older-style events, see section 2.3.11

Property Change Assertions

Because C# has deeply integrated the idea of ‘Property Change’ notifications as part of its GUI frameworks (which we’ll cover in a later chapter), it makes sense to have a special assertion to deal with this notification. Hence, the Assert.PropertyChanged(INotifyPropertyChanged @object, string propertyName, Action testCode). Using it is simple - supply the object that implements the INotifyPropertyChanged interface as the first argument, the name of the property that will be changing as the second, and the Action delegate that will trigger the change as the third.

For example, if we had a Profile object with a StatusMessage property that we knew should trigger a notification when it changes, we could write our test as:

[Fact]
public void ProfileShouldNotifyOfStatusMessageChanges() {
    Profile testProfile = new Profile();
    Assert.PropertyChanged(testProfile, "StatusMessage", () => testProfile.StatusMessage = "Hard at work");
}

There is also a similar assertion for testing if a property is changed in asynchronous code. This operates nearly identically, except instead of supplying an Action, we supply a Task:

  • Assert.PropertyChangedAsync(INotifyPropertyChanged @object, string propertyName, Task testCode)

Mock Objects

One of the most important ideas behind unit testing is the idea that you are testing an object in isolation from other objects (This is in direct contrast to integration testing, where you are interested in how objects are working together).

But how do we test a class that has a strong dependency on another class? Let’s consider the case of an Automated Teller Machine (ATM). If we designed its control system using an object-oriented language, one natural architecture would be to have classes representing the cash dispenser, card reader, keyboard, display, and user’s bank accounts. Then we might coordinate each of these into a central object, representing the entire ATM.

Unit testing most of these classes would be straightforward, but how do we unit test the ATM class? It would have dependencies on each of the other classes. If we used normal instances of those, we’d have no idea if the test was failing due to the ATM class or its dependency. This is where mock objects come into play.

We start by replacing each of the dependencies with an interface using the same method signatures, and we pass the dependencies through the ATM constructor. We make sure our existing classes implement the interface, and pass them into the ATM when we create it. Thus, this step doesn’t change much about how our program operates - we’re still using the same classes to do the same things.

But in our unit tests for the ATM class, we can create new classes that implement the interfaces and pass them into the ATM instance we are testing. These are our mock classes, because they “fill in” for the real classes. Typically, a mock class is much simpler than a real class, and exposes information we might need in our test. For example, our Display class might include a DisplayText method, so have it implement an IDisplay interface that lists DisplayText. Then our MockDisplay class might look like:

internal class MockDisplay :IDisplay
{
  public string LastTextDisplayed {get; set;}

  public void DisplayText(string text) 
  {
    LastTextDisplayed = text;
  }
}

Note that our mock class implements the required method, DisplayText, but in a very different way than a real display would - it just holds onto the string and makes it accessible with a public property. That way, we could check its value in a test:

[fact]
public void ShouldDisplayGreetingOnStartup()
{
  MockDisplay md = new MockDisplay();
  MockKeyboard mk = new MockKeyboard();
  MockCardReader mcr= new MockCardReader();
  MockCashDispenser mcd = new MockCashDispenser();
  Atm atm = new Atm(md, mk, mcr, mcd);
  Assert.Equal("Hello ATM!", md.LastTextDisplayed);
}

Given our knowledge of C#, the only way md.LastTextDisplayed would be the string specified was if the ATM class asked it to display the message when it was constructed. Thus, we know it will do the same with the real DisplayScreen class. And if we have also thoroughly unit tested the DisplayScreen class, then we have a strong basis for believing our system is built correctly.

This approach also allows us to test things that would normally be very difficult to do - for example, we can write a method to have a MockCardReader trigger a CardInserted event:

internal class MockCardReader : ICardReader 
{
  public event EventHandler<CardInsertedEventArgs> CardInserted;

  public void TriggerCardInserted()
  {
    CardInserted.Invoke(this, new CardInsertedEventArgs());
  }
}

Which allows us to check that the ATM prompts a user for a PIN once a card is inserted:

[Fact]
public void ShouldPromptForPinOnCardInsert()
{
  MockDisplay md = new MockDisplay();
  MockKeyboard mk = new MockKeyboard();
  MockCardReader mcr= new MockCardReader();
  MockCashDispenser mcd = new MockCashDispenser();
  Atm atm = new Atm(md, mk, mcr, mcd);
  mcr.TriggerCardInserted();
  Assert.Equal("Please enter your PIN:", md.LastTextDisplayed);
}

Using mock objects like this can greatly simplify the test-writing process, and improve the quality and robustness of your unit tests.

Running Tests

Tests are usually run with a test runner, a program that will execute the test code against the code to be tested. The exact mechanism involved depends on the testing framework.

The xUnit framework is offered as a set of Nuget packages:

  • The xunit package contains the library code defining the Assertion class as well as the [Fact] and [Test] attributes.
  • The xunit.runner.visualstudio package contains the actual test runner

As with other aspects of the .NET framework, the tools can be used at either the command line, or through Visual Studio integrations. The xunit documentation describes the command line approach thoroughly, so we won’t belabor it here. But be aware, if you want to do development in a Linux or Unix environment, you must use the command line, as there is no version of Visual Studio available for those platforms (there is however, a version available for the Mac OS).

When building tests with Visual Studio, you will typically begin by adding an xUnit Test Project to your existing solution. Using the wizard will automatically incorporate the necessary Nuget packages into the project. However, you will need to add the project to be tested to the Dependencies list of the test project to give it access to the assembly to be tested. You do this by right-clicking the ‘Dependencies’ entry under the Test Project in Visual Studio, choosing “Add Project Reference”, and in the dialog that pops up, checking the checkbox next to the name of the project you are testing:

Adding the project reference Adding the project reference

To explore and run your tests, you can open the Test Explorer from the “Test” menu. If no tests appear, you may need to build the test project. This can be done by right-clicking the test project in the Solution Explorer and selecting “Build”, or by clicking the “Run All” button in the Test Explorer. The “Run All” button will run every test in the suite. Alternatively, you can run individual tests by clicking on them, and clicking the “Run” button.

Run and Run All buttons Run and Run All buttons

As tests complete, they will report their status - pass or fail - indicated by a green checkmark or red x next to the test name, as well as the time it took to run the test. There will also be a summary available with details about any failures that can be accessed by clicking the test name.

Test Detail Summary Test Detail Summary

Occasionally, your tests may not seem to finish, but get stuck running. If this happens, check the output panel, switching it from “build” to “tests”. Most likely your test process crashed because of an error in your test code, and the output reporting that error will be reported there.

Test Output in the Output Panel Test Output in the Output Panel

It is a good idea to run tests you’ve written previously as you add to or refactor your code. This practice is known as regression testing, and can help you identify errors your changes introduce that break what had previously been working code. This is also one of the strongest arguments for writing test code rather than performing ad-hoc testing; automated tests are easy to repeat.

Summary

In this chapter we learned about testing, both manually using test plans and automatically using a testing framework. We saw how the cost of fixing errors rises exponentially with how long they go undiscovered. We discussed how writing automated tests during the programming phase can help uncover these errors earlier, and how regression testing can help us find new errors introduced while adding to our programs.

We learned how to use xUnit and Visual Studio’s Test Explorer to write and run tests on .NET programs. We explored a good chunk of xUnit’s assertion library. We saw how to get Visual Studio to analyze our tests for code coverage, discussed this metric’s value to evaluate our tests. We also explored mutation testing, and saw how it can help improve our tests.

As you move forward as a software developer, you’ll want to incorporate testing into your code-writing efforts.

Test Code Coverage

The term test code coverage refers to how much of your program’s code is executed as your tests run. It is a useful metric for evaluating the depth of your test, if not necessarily the quality. Basically, if your code is not executed in the test framework, it is not tested in any way. If it is executed, then at least some tests are looking at it. So aiming for a high code coverage is a good starting point for writing tests.

Much like Visual Studio provides a Test Explorer for running tests, it provides support for analyzing test coverage. We can access this from the “Test” menu, where we select the “Analyze Code Coverage for All Tests”.

Code coverage command in the Test Menu Code coverage command in the Test Menu

This will build and run all our tests, and as they run it will collect data about how many blocks of code are or are not executed. The results appear in the Code Coverage Results panel:

Code Coverage results panel Code Coverage results panel

Be aware that there will always be some blocks that are not picked up in this analysis, so it is typical to shoot for a high percentage.

While test code coverage is a good starting point for evaluating your tests, it is simply a measure of quantity, not quality. It is easily possible for you to have all of your code covered by tests, but still miss errors. You need to carefully consider the edge cases - those unexpected and unanticipated ways your code might end up being used.

Mutation Testing

At this point you may be asking how to determine if your tests are good. Mutation testing is one way of evaluating the quality of your tests. Effectively, mutation testing is a strategy that mutates your program, and then runs your tests. If the test fails against the mutated code, this suggests your test is good.

As a simplistic example, take this extremely simple class:

public void Doll
{
  public string Name {get;} = "Molly";
}

A mutation might change it to:

public void Doll
{
  public string Name {get;} = "Mollycoddle";
}

We would expect that the test TheDollsNameIsAlwaysMolly would fail due to this mutation. If it doesn’t, we probably need to revisit our test. Here is an example of a test that would both normally pass, and pass with this mutation. See if you can spot the problem:

[Fact]
public void TheDollsNameIsAlwaysMolly()
{
  Doll doll = new Doll();
  Assert.Contains(doll.Name, "Molly");
}

Mutation testing is done by a special testing tool that uses reflection to understand and alter the classes being tested in your unit tests. In C#, we use Stryker.NET.

As with code coverage, mutation testing can’t provide all the answers. But it does help ensure that our programs and the tests we write of them are more robust.

Chapter 5

UML

The Standard Model of Object-Orientation

Subsections of UML

Introduction

As software systems became more complex, it became harder to talk and reason about them. Unified Modeling Language (UML) attempted to correct for this by providing a visual, diagrammatic approach to communicate the structure and function of a program. If a picture is worth a thousand words, a UML diagram might be worth a thousand lines of code…

Key Terms

Some key terms to learn in this chapter are:

  • Unified Modeling Language
  • Class Diagrams
  • Typed Elements
  • Constraints
  • Stereotypes
  • Attributes
  • Operations
  • Association
  • Generalization
  • Realization
  • Composition
  • Aggregation

Key Skills

The key skill to learn in this chapter is how to draw UML class diagrams.

UML

Unified Modeling Language (UML) was introduced to create a standardized way of visualizing a software system design. It was developed by Grady Booch, Ivar Jacobson, and James Rumbah at Rational Software in the mid-nineties. It was adopted as a standard by the Object Management Group in 1997, and also by the International Organization for Standardization (ISO) as an approved ISO standard in 2005.

The UML standard actually provides many different kinds of diagrams for describing a software system - both structure and behavior:

  • Class Diagram A class diagram visualizes the structure of the classes in the software, and the relationships between these classes.
  • Component Diagram A component diagram visualizes how the software system is broken into components, and how communication between those components is achieved.
  • Activity Diagram An activity diagram represents workflows in a step-by-step process for actions. It is used to model data flow in a software system.
  • Use-Case Diagram A use-case diagram identifies the kinds of users a software system will have, and how they work with the software.
  • Sequence Diagram A sequence diagram shows object interactions arranged in chronological sequences.
  • Communication Diagram A communication diagram models the interactions between objects in terms of sequences of messages.

The full UML specification is 754 pages long, so there is a lot of information packed into it. For the purposes of this class, we’re focusing on a single kind of diagram - the class diagram.

Boxes

UML class diagrams are largely composed of boxes - basically a rectangular border containing text. UML class diagrams use boxes to represent units of code - i.e. classes, structs, and enumerations. These boxes are broken into compartments. For example, an Enum is broken into two compartments:

A UML Enum representation A UML Enum representation

Stereotypes

UML is intended to be language-agnostic. But we often find ourselves in situations where we want to convey language-specific ideas, and the UML specification leaves room for this with stereotypes. Stereotypes consist of text enclosed in double less than and greater than symbols. In the example above, we indicate the box represents an enumeration with the $ \texttt{<<enum>>}$ stereotype.

Typed Elements

A second basic building block for UML diagrams is a typed element. Typed elements (as you might expect from the name) have a type. Fields and parameters are typed elements, as are method parameters and return values.

The pattern for defining a typed element is:

$$ \texttt{[visibility] element : type [constraint]} $$

The optional $\texttt{[visibility]}$ indicates the visibility of the element, the $\texttt{element}$ is the name of the typed element, and the $\texttt{type}$ is its type, and the $\texttt{[constraint]}$ is an optional constraint.

Visibility

In UML visibility (what we would call access level in C#) is indicated with symbols, i.e.:

  • $\texttt{+}$ indicates public
  • $\texttt{-}$ indicates private
  • $\texttt{#}$ indicates protected

I.e. the field:

protected int Size;

Would be expressed:

$$ \texttt{# Size : int} $$

Constraints

A typed element can include a constraint indicating some restriction for the element. The constraints are contained in a pair of curly braces after the typed element, and follow the pattern:

$$ \texttt{ {element: boolean expression} } $$

For example:

$$ \texttt{- age: int {age: >= 0}} $$

Indicates the private variable age must be greater than or equal to 0.

Classes

In a UML class diagram, individual classes are represented with a box divided into three compartments, each of which is for displaying specific information:

Class Diagram example Class Diagram example

The first compartment identifies the class - it contains the name of the class. The second compartment holds the attributes of the class (in C#, these are the fields and properties). And the third compartment holds the operations of the class (in C#, these are the methods).

In the diagram above, we can see the Fruit class modeled on the right side.

Attributes

The attributes in UML represent the state of an object. For C#, this would correspond to the fields and properties of the class.

We indicate fields with a typed element, i.e. in the example above, the blended field is represented with:

$$ \texttt{-blended:bool} $$

Indicating it should be declared private with the type bool.

For properties, we add a stereotype containing either get, set, or both. I.e. if we were to expose the private field bool with a public accessor, we would add a line to our class diagram with:

$$ \texttt{+Blended:bool<<get,set>>} $$
Info

In C#, properties are technically methods. But we use the same syntax to utilize them as we do fields, and they serve the same role - to expose aspects of the class state. So for the purposes of this class we’ll classify them as attributes.

Operators

The operators in UML represent the behavior of the object, i.e. the methods we can invoke upon it. These are declared using the pattern:

$$ \texttt{visibility name([parameter list])[:return type]} $$

The $\texttt{[visibility]}$ uses the same symbols as typed elements, with the same correspondences. The $\texttt{name}$ is the name of the method, and the $\texttt{[parameter list]}$ is a comma-separated list of typed elements, corresponding to the parameters. The $\texttt{[:return type]}$ indicates the return type for the method (it can be omitted for void).

Thus, in the example above, the protected method Blend has no parameters and returns a string. Similarly, the method:

public int Add(int a, int b)
{
    return a + b;
}

Would be expressed:

$$ \texttt{+Add(a:int, b:int):int} $$

Static and Abstract

In UML, we indicate a class is static by underlining its name in the first compartment of the class diagram. We can similarly indicate static operators and methods by underlining the entire line referring to them.

To indicate a class is abstract, we italicize its name. Abstract methods are also indicated by italicizing the entire line referring to them.

Associations

Class diagrams also express the associations between classes by drawing lines between the boxes representing them.

UML Association UML Association

There are two basic types of associations we model with UML: has-a and is-a associations. We break these into two further categories, based on the strength of the association, which is either strong or weak. These associations are:

Association NameAssociation Type
Realizationweak is-a
Generalizationstrong is-a
Aggregationweak has-a
Compositionstrong has-a

Is-A Associations

Is-a associations indicate a relationship where one class is a instance of another class. Thus, these associations represent polymorphism, where a class can be treated as another class, i.e. it has both its own, and the associated classes’ types.

Realization (Weak is-a)

Realization refers to making an interface “real” by implementing the methods it defines. For C#, this corresponds to a class that is implementing an Interface. We call this a is-a relationship, because the class is treated as being the type of the Interface. It is also a weak relationship as the same interface can be implemented by otherwise unrelated classes. In UML, realization is indicated by a dashed arrow in the direction of implementation:

Realization in UML Realization in UML

Generalization

Generalization refers to extracting the shared parts from different classes to make a general base class of what they have in common. For C# this corresponds to inheritance. We call this a strong is-a relationship, because the class has all the same state and behavior as the base class. In UML, generalization is indicated by a solid arrow in the direction of inheritance:

Generalization in UML Generalization in UML

Also notice that we show that Fruit and its Blend() method are abstract by italicizing them.

Has-A Associations

Has-a associations indicates that a class holds one or more references to instances of another class. In C#, this corresponds to having a variable or collection with the type of the associated class. This is true for both kinds of has-a associations. The difference between the two is how strong the association is.

Aggregation

Aggregation refers to collecting references to other classes. As the aggregating class has references to the other classes, we call this a has-a relationship. It is considered weak because the aggregated classes are only collected by the aggregating class, and can exist on their own. It is indicated in UML by a solid line from the aggregating class to the one it aggregates, with an open diamond “fletching” on the opposite side of the arrow (the arrowhead is optional).

Aggregation in UML Aggregation in UML

Composition

Composition refers to assembling a class from other classes, “composing” it. As the composed class has references to the other classes, we call this a has-a relationship. However, the composing class typically creates the instances of the classes composing it, and they are likewise destroyed when the composing class is destroyed. For this reason, we call it a strong relationship. It is indicated in UML by a solid line from the composing class to those it is composed of, with a solid diamond “fletching” on the opposite side of the arrow (the arrowhead is optional).

Composition in UML Composition in UML

Info

Aggregation and composition are commonly confused, especially given they both are defined by holding a variable or collection of another class type. An analogy I like to use to help students reason about the difference is this:

Aggregation is like a shopping cart. When you go shopping, you place groceries into the shopping cart, and it holds them as you push it around the store. Thus, a ShoppingCart class might have a List<Grocery> named Contents, and you would add the items to it. When you reach the checkout, you would then take the items back out. The individual Grocery objects existed before they were aggregated by the ShoppingCart, and also after they were removed from it.

In contrast, Composition is like an organism. Say we create a class representing a Dog. It might be composed of classes like Tongue, Ear, Leg, and Tail. We would probably construct these in the Dog class’s constructor, and when we dispose of the Dog object, we wouldn’t expect these component classes to stick around.

Multiplicity

With aggregation and composition, we may also place numbers on either end of the association, indicating the number of objects involved. We call these numbers the multiplicity of the association.

Composition in UML Composition in UML

For example, the Frog class in the composition example has two instances of front and rear legs, so we indicate that each Frog instance (by a 1 on the Frog side of the association) has exactly two (by the 2 on the leg side of the association) legs. The tongue has a 1 to 1 multiplicity as each frog has one tongue.

Aggregation in UML Aggregation in UML

Multiplicities can also be represented as a range (indicated by the start and end of the range separated by ..). We see this in the ShoppingCart example above, where the count of GroceryItems in the cart ranges from 0 to infinity (infinity is indicated by an asterisk *).

Generalization and realization are always one-to-one multiplicities, so multiplicities are typically omitted for these associations.

Visio

One of the many tools we can use to create UML diagrams is Microsoft Visio. For Kansas State University Computer Science students, this can be downloaded through your Azure Student Portal.

Visio is a vector graphics editor for creating flowcharts and diagrams. it comes preloaded with a UML class diagram template, which can be selected when creating a new file:

UML Class Diagram Template UML Class Diagram Template

Class diagrams are built by dragging shapes from the shape toolbox onto the drawing surface. Notice that the shapes include classes, interfaces, enumerations, and all the associations we have discussed. Once in the drawing surface, these can be resized and edited.

Dragging Shapes Dragging Shapes

Right-clicking on an association will open a context menu, allowing you to turn on multiplicities. These can be edited by double-clicking on them. Unneeded multiplicities can be deleted.

Setting Multiplicities Setting Multiplicities

To export a Visio project in PDF or other form, choose the “Export” option from the file menu.

Summary

In this section, we learned about UML class diagrams, a language-agnostic approach to visualizing the structure of an object-oriented software system. We saw how individual classes are represented by boxes divided into three compartments; the first for the identity of the class, the second for its attributes, and the third for its operators. We learned that italics are used to indicate abstract classes and operators, and underlining static classes, attributes, and operators.

We also saw how associations between classes can be represented by arrows with specific characteristics, and examined four of these in detail: aggregation, composition, generalization, and realization. We also learned how multiplicities can show the number of instances involved in these associations.

Finally, we saw how C# classes, interfaces, and enumerations are modeled using UML. We saw how the stereotype can be used to indicate language-specific features like C# properties. We also looked at creating UML Class diagrams using Microsoft Visio.

Chapter 6

Advanced C#

For a Sharper Language

Subsections of Advanced C#

Introduction

Throughout the earlier chapters, we’ve focused on the theoretical aspects of Object-Orientation, and discussed how those are embodied in the C# language. Before we close this section though, it would be a good idea to recognize that C# is not just an object-oriented language, but actually draws upon many ideas and syntax approaches that are not very object-oriented at all!

In this chapter, we’ll examine many aspects of C# that fall outside of the object-oriented mold. Understanding how and why these constructs have entered C# will help make you a better .NET programmer, and hopefully alleviate any confusion you might have.

Key Terms

Some key terms to learn in this chapter are:

  • Production Languages
  • The static keyword
  • Generics
  • Nullables
  • Anonymous Types
  • Lambda Syntax
  • Pattern Matching

Production Languages

It is important to understand that C# is a production language - i.e. one intended to be used to create real-world software. To support this goal, the developers of the C# language have made many efforts to make C# code easier to write, read, and reason about. Each new version of C# has added additional syntax and features to make the language more powerful and easier to use. In some cases, these are entirely new things the language couldn’t do previously, and in others they are syntactic sugar - a kind of abbreviation of an existing syntax. Consider the following if statement:

if(someTestFlag) 
{
    DoThing();
}
else 
{ 
    DoOtherThing();
}

As the branches only execute a single expression each, this can be abbreviated as:

if(someTestFlag) DoThing();
else DoOtherThing();

Similarly, Visual Studio has evolved side-by-side with the language. For example, you have probably come to like Intellisense - Visual Studio’s ability to offer descriptions of classes and methods as you type them, as well as code completion, where it offers to complete the statement you have been typing with a likely target. As we mentioned in our section on learning programming, these powerful features can be great for a professional, but can interfere with a novice programmer’s learning.

Let’s take a look at some of the features of C# that we haven’t examined in detail yet.

The static Keyword

To start, let’s revisit one more keyword that causes a lot of confusion for new programmers, static. We mentioned it briefly when talking about encapsulation and modules, and said we could mimic a module in C# with a static class. We offered this example:

/// <summary>
/// A library of vector math functions
/// </summary>
public static class VectorMath
{
    
    /// <summary>
    /// Computes the dot product of two vectors 
    /// </summary>
    public static double DotProduct(Vector3 a, Vector3 b) {
        return a.x * b.x + a.y * b.y + a.z * b.z;
    }

    /// <summary>
    /// Computes the magnitude of a vector
    /// </summary>
    public static double Magnitude(Vector3 a) {
        return Math.Sqrt(Math.Pow(a.x, 2) + Math.Pow(a.y, 2) + Math.Pow(a.z, 2));
    }

}

You’ve probably worked with the C# Math class before, which is declared the same way - as a static class containing static methods. For example, to compute 8 cubed, you might have used:

Math.Pow(8, 3);

Notice how we didn’t construct an object from the Math class? In C# we cannot construct static classes - they simply exist as a container for static fields and methods. If you’re thinking that doesn’t sound very object-oriented, you’re absolutely right. The static keyword allows for some very non-object-oriented behavior more in line with imperative languages like C. Bringing the idea of static classes into C# let programmers with an imperative background use similar techniques to what they were used to, which is why static classes have been a part of C# from the beginning.

Static Methods in Regular Classes

You can also create static methods within a non-static class. For example, we could refactor our Vector3 class to add a static DotProduct() within it:

public struct Vector3 {
    public double X {get; set;}
    public double Y {get; set;}
    public double Z {get; set;}

    /// <summary> 
    /// Creates a new Vector3 object
    /// </summary>
    public Vector3(double x, double y, double z) {
        this.X = x;
        this.Y = y;
        this.Z = z;
    }

    /// <summary>
    /// Computes the dot product of this vector and another one 
    /// </summary>
    /// <param name="other">The other vector</param>
    public double DotProduct(Vector3 other) {
        return this.X * other.X + this.Y * other.Y + this.Z * other.Z;
    }

    /// <summary>
    /// Computes the dot product of two vectors 
    /// </summary>
    /// <param name="a">The first vector<param>
    /// <param name="b">The second vector</param>
    public static DotProduct(Vector3 a, Vector3 b)
    {
        return a.DotProduct(b);
    }
}

This method would be invoked like any other static method, i.e.:

Vector3 a = new Vector3(1,3,4);
Vector3 b = new Vector3(4,3,1);
Vector3.DotProduct(a, b);

You can see we’re doing the same thing as the instance method DotProduct(Vector3 other), but in a library-like way.

Static Fields Within Regular Classes

We can also declare fields as static, which has a meaning slightly different than static methods. Specifically, the field is shared amongst all instances of the class. Consider the following class:

public class Tribble
{
    private static int count = 1;

    public Tribble() 
    {
        count *= 2;
    }

    public int TotalTribbles
    {
        get 
        {
            return count;
        }
    }
}

If we create a single Tribble, and then ask how many total Tribbles there are:

var t = new Tribble();
t.TotalTribbles; // expect this to be 2

We would expect the value to be 2, as count was initialized to 1 and then multiplied by 2 in the Tribble constructor. But if we construct two Tribbles:

var t = new Tribble();
var u = new Tribble();
t.TotalTribbles; // will be 4
u.TotalTribbles; // will be 4

This is because all instances of Tribble share the count field. So it is initialized to 1, multiplied by 2 when tribble a was constructed, and multiplied by 2 again when tribble b was constructed. Hence $1 * 2 * 2 = 4$. Every additional Tribble we construct will double the total population (which is the trouble with Tribbles).

Why Call This Static?

Which brings us to a point of confusion for most students, why call this static? After all, doesn’t the word static indicate unchanging?

The answer lies in how memory is allocated in a program. Sometimes we know in advance how much memory we need to hold a variable, i.e. a double in C# requires 64 bits of memory. We call these types value types in C#, as the value is stored directly in memory where our variable is allocated. Other types, i.e. a List<T>, we may not know exactly how much memory will be required. We call these reference types. Instead of the variable holding a binary value, it holds a binary address to another location in memory where the list data is stored (hence, it is a reference).

When your program runs, it gets assigned a big chunk of memory from the operating system. Your program is loaded into the first part of this memory, and the remaining memory is used to hold variable values as the program runs. If you imagine that memory as a long shelf, we put the program instructions and any literal values to the far left of this shelf. Then, as the program runs and we need to create space for variables, we either put them on the left side or right side of the remaining shelf space. Value types, which we know will only exist for the duration of their scope (i.e. the method they are defined in) go to the left, and once we’ve ended that scope we remove them. Similarly, the references we create (holding the address of memory of reference types) go on the left. The data of the reference types however, go on the right, because we don’t know when we’ll be done with them.

We call the kind of memory allocation that happens on the left static, as we know it should exist as long as the variable is in scope. Hence, the static keyword. In lower-level languages like C, we have to manually allocate space for our reference types (hence, not static). C# is a memory managed language in that we don’t need to manually allocate and deallocate space for reference types, but we do allocate space every time we use the new keyword, and the garbage collector frees any space it decides we’re done with (because we no longer have references pointing at it). So pointers do exist in C#, they are just “under the hood”.

By the way, the left side of the shelf we call the Stack, and the right the Heap. This is the source of the name for a Stack Overflow Exception - it means your program used up all the available space in the Stack, but still needs more. This is why it typically happens with infinite loops or recursion - they keep allocating variables on the stack until they run out of space.

Memory allocation and pointers is covered in detail in CIS 308 - C Language Lab, and you’ll learn more about how programs run and the heap and stack in CIS 450 - Computer Architecture and Operations.

Operator Overloading

C# allows you to override most of the language’s operators to provide class-specific functionality. The user-defined casts we discussed earlier are one example of this.

Perhaps the most obvious of these are the arithmetic operators, i.e. +, -, \, *. Consider our Vector3 class we defined earlier. If we wanted to overload the + operator to allow for vector addition, we could add it to the class definition:

/// <summary>
/// A class representing a 3-element vector
/// </summary>
public class Vector3 
{
    /// <summary>The x-coordinate</summary>
    public double X { get; set;}

    /// <summary>The y-coordinate</summary>
    public double Y { get; set;}

    /// <summary>The z-coordinate</summary>
    public double Z { get; set;}

    /// <summary>
    /// Constructs a new vector
    /// </summary>
    public Vector3(double x, double y, double z)
    {
        X = x;
        Y = y;
        Z = z;
    }

    /// Adds two vectors using vector addition
    public static Vector3 operator +(Vector3 v1, Vector3 v2)
    {
        return new Vector3(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
    }
}

Note that we have to make the method static, and include the operator keyword, along with the symbol of the operation. This vector addition we are performing here is also a binary operation (meaning it takes two parameters). We can also define unary operations, like negation:

/// Negates a vector
public static Vector3 operator -(Vector3 v)
{
    return new Vector3(-v.X, -v.Y, -v.Z);
}

The full list of overloadable operators is found in the C# documentation

Generics

Generics expand the type system of C# by allowing classes and structs to be defined with a generic type parameter, which will be instantiated when it is used in code. This avoids the necessity of writing similar specialized classes that each work with a different data type. You’ve used examples of this extensively in your CIS 300 - Data Structures course.

For example, the generic List<T> can be used to create a list of any type. If we want a list of integers, we declare it using List<int>, and if we want a list of booleans we declare it using List<bool>. Both use the same generic list class.

You can declare your own generics as well. Say you need a binary tree, but want to be able to support different types. We can declare a generic BinaryTreeNode<T> class:

/// <summary>
/// A class representing a node in a binary tree 
/// <summary>
/// <typeparam name="T">The type to hold in the tree</typeparam>
public class BinaryTreeNode<T> 
{
    /// <summary> 
    /// The value held in this node of the tree 
    /// </summary>
    public T Value { get; set; }

    /// <summary> 
    /// The left branch of this node
    /// </summary>
    public BinaryTreeNode<T> Left { get; set; }

    /// <summary> 
    /// The right branch of this node
    /// </summary>
    public BinaryTreeNode<T> Right { get; set; }
}

Note the use of <typeparam> in the XML comments. You should always document your generic type parameters when using them.

Nullables

Returning to the distinction between value and reference types, a value type stores its value directly in the variable, while a reference type stores an address to another location in memory that has been allocated to hold the value. This is why reference types can be null - this indicates they aren’t pointing at anything. In contrast, value types cannot be null - they always contain a value. However, there are times it would be convenient to have a value type be allowed to be null.

For these circumstances, we can use the Nullable generic type, which allows the variable to represent the same values as before, plus null. It does this by wrapping the value in a simple structure that stores the value in its Value property, and also has a boolean property for HasValue. More importantly, it supports explicit casting into the template type, so we can still use it in expressions, i.e.:

Nullable<int> a = 5;
int b = 6;
int c = (int)a + b;
// This evaluates to 11.

However, if the value is null, we’ll get an InvalidOperationException with the message “Nullable object must have a value”.

There is also syntactic sugar for declaring nullable types. We can follow the type with a question mark (?), i.e.:

int? a = 5;

Which works the same as Nullable<int> a = 5;, but is less typing.

Anonymous Types

Another new addition to C# is anonymous types. These are read-only objects whose type is created by the compiler rather than being defined in code. They are created using syntax very similar to object initializer syntax.

For example, the line:

var name = new { First="Jack", Last="Sprat" };

Creates an anonymous object with properties First and Last and assigns it to the variable name. Note we have to use var, because the object does not have a defined type. Anonymous types are primarily used with LINQ, which we’ll cover in the future.

Lambda Syntax

The next topic we’ll cover is lambda syntax. You may remember from CIS 115 the Turing Machine, which was Alan Turing’s theoretical computer he used to prove a lot of theoretical computer science ideas. Another mathematician of the day, Alan Church, created his own equivalent of the Turing machine expressed as a formal logic system, Lambda calculus. Broadly speaking, the two approaches do the same thing, but are expressed very differently - the Turing machine is an (imaginary) hardware-based system, while Lambda Calculus is a formal symbolic system grounded in mathematical logic. Computer scientists develop familiarity with both conceptions, and some of the most important work in our field is the result of putting them together.

But they do represent two different perspectives, which influenced different programming language paradigms. The Turing machine you worked with in CIS 115 is very similar to assembly language, and the imperative programming paradigm draws strongly upon this approach. In contrast, the logical and functional programming paradigms were more influenced by Lambda calculus. This difference in perspective also appears in how functions are commonly written in these different paradigms. An imperative language tends to define functions something like:

Add(param1, param2)
{
    return param1 + param2;
}

While a functional language might express the same idea as:

(param1, param2) => param1 + param2

This “arrow” or “lambda” syntax has since been adopted as an alternative way of writing functions in many modern languages, including C#. In C#, it is primarily used as syntactic sugar, to replace what would otherwise be a lot of typing to express a simple idea.

Consider the case where we want to search a List<string> AnimalList for a string containing the substring "kitten". The List.Find() takes a predicate - a static method that can be invoked to find an item in the list. We have to define a static method, i.e.:

private static bool FindKittenSubstring(string fullString)
{
    return fullString.Contains("kitten");
}

From this method, we create a predicate:

Predicate<string> findKittenPredicate = FindKittenSubstring;

Then we can pass that predicate into our Find:

bool containsKitten = AnimalList.Find(findKittenPredicate);

This is quite a lot of work to express a simple idea. C# introduced lambda syntax as a way to streamline it. The same operation using lambda syntax is:

bool containsKitten = AnimalList.Find((fullString) => fullString.Contains("kitten"));

Much cleaner to write. The C# compiler is converting this lambda expression into a predicate as it compiles, but we no longer have to write it! You’ll see this syntax in your xUnit tests as well as when we cover LINQ. It has also been adapted to simplify writing getters and setters. Consider this case:

public class Person 
{
    public string LastName { get; set; }
    
    public string FirstName { get; set; }

    public string FullName 
    { 
        get 
        {
            return FirstName + " " + LastName;

        }
    }
}

We could instead express this as:

public class Person 
{
    public string LastName { get; set; }

    public string FirstName { get; set; }

    public string FullName => FirstName + " " + LastName;
}

In fact, all methods that return the result of a single expression can be written this way:

public class VectorMath
{
    public double Add(Vector a, Vector b) => new Vector(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
}

Pattern Matching

Pattern matching is another idea common to functional languages that has gradually crept into C#. Pattern matching refers to extracting information from structured data by matching the shape of that data.

We’ve already seen the pattern-matching is operator in our discussion of casting. This allows us to extract the cast version of a variable and assign it to a new one:

if(oldVariable is SpecificType newVariable)
{
    // within this block newVariable is (SpecificType)oldVariable
}

The switch statement is also an example of pattern matching. The traditional version only matched constant values, i.e.:

switch(choice)
{
    case "1":
        // Do something
        break;
    case "2":
        // Do something else
        break;
    case "3":
        // Do a third thing
        break;
    default:
        // Do a default action
        break;
}

However, in C# version 7.0, this has been expanded to also match patterns. For example, given a Square, Circle, and Rectangle class that all extend a Shape class, we can write a method to find the area using a switch:

public static double ComputeCircumference(Shape shape)
{
    switch(shape)
    {
        case Square s:
            return 4 * s.Side;
        case Circle c:
            return c.Radius * 2 * Math.PI;
        case Rectangle r:
            return 2 * r.Length + 2 * r.Height;
        default:
            throw new ArgumentException(
                message: "shape is not a recognized shape",
                paramName: nameof(shape)
            );
    }
}

Note that here we match the type of the shape and cast it to that type making it available in the provided variable, i.e. case Square s: matches if shape can be cast to a Square, and s is the result of that cast operation.

This is further expanded upon with the use of when clauses, i.e. we could add a special case for a circle or square with a circumference of 0:

public static double ComputeCircumference(Shape shape)
{
    switch(shape)
    {
        case Square s when s.Side == 0:
        case Circle c when c.Radius == 0:
            return 0;
        case Square s:
            return 4 * s.Side;
        case Circle c:
            return c.Radius * 2 * Math.PI;
        case Rectangle r:
            return 2 * r.Length + 2 * r.Height;
        default:
            throw new ArgumentException(
                message: "shape is not a recognized shape",
                paramName: nameof(shape)
            );
    }
}

The when applies conditions to the match that only allow a match when the corresponding condition is true.

Info

C# 8.0, which is currently in preview, has expanded greatly upon pattern matching, adding exciting new features, such as the switch expression, tuples, and deconstruction operator.

Summary

In this chapter we looked at some of the features of C# that aren’t directly related to object-orientation, including many drawn from imperative or functional paradigms. Some have been with the language since the beginning, such as the static keyword, while others have recently been added, like pattern matching.

Each addition has greatly expanded the power and usability of C# - consider generics, whose introduction brought entirely new (and much more performant) library collections like List<T>, Dictionary<T>, and HashSet<T>. Others have lead to simpler and cleaner code, like the use of Lambda expressions. Perhaps most important is the realization that programming languages often continue to evolve beyond their original conceptions.

Chapter II

Desktop Development

Objects Go to Work

Subsections of Desktop Development

Chapter 1

Windows Presentation Foundation

Some clever statement…

Subsections of Windows Presentation Foundation

Introduction

Windows Presentation Foundation (WPF) is a open-source system for rendering Windows application user interfaces. It was released as part of the .NET framework in 2006. In many ways, it is intended to be a successor to Windows Forms. This chapter will examine the basics of working with WPF in detail.

Key Terms

Some key terms to learn in this chapter are:

  • Graphical User Interface (GUI)
  • Windows Presentation Foundation (WPF)
  • Extensible Application Markup Language (XAML)
  • Codebehind
  • Layouts
  • Controls
  • Component-Based Design
  • Composition

Key Skills

The key skill to learn in this chapter is how to use C# and XAML to develop WPF user interfaces that adapt to device screen dimensions.

WPF Features

Windows Presentation Foundation is a library and toolkit for creating Graphical User Interfaces - a user interface that is presented as a combination of interactive graphical and text elements commonly including buttons, menus, and various flavors of editors and inputs. GUIs represent a major step forward in usability from earlier programs that were interacted with by typing commands into a text-based terminal (the EPIC software we looked at in the beginning of this textbook is an example of this earlier form of user interface).

You might be wondering why Microsoft introduced WPF when it already had support for creating GUIs in its earlier Windows Forms product. In part, this decision was driven by the evolution of computing technology.

Screen Resolution and Aspect Ratio

No doubt you are used to having a wide variety of screen resolutions available across a plethora of devices. But this was not always the case. Computer monitors once came in very specific, standardized resolutions, and only gradually were these replaced by newer, higher-resolution monitors. The table below summarizes this time, indicating the approximate period each resolution dominated the market.

StandardSizePeak Years
VGA640x4801987-1990
SVGA800x6001990-2003
XGA1024x7682007-2015

Windows Forms was introduced in the early 2000’s, at a time where the most popular screen resolution in the United States was transitioning from SVGA to XGA, and screen resolutions (especially for business computers running Windows) had remained remarkably consistent for long periods. Moreover, these resolutions were all using the 4:3 aspect ratio (the ratio of width to height of the screen). Hence, the developers of Windows forms did not consider the need to support vastly different screen resolutions and aspect ratios. Contrast that with trends since that time:

Screen Resolutions in US from 2009-2020 Screen Resolutions in US from 2009-2020

There is no longer a clearly dominating resolution, nor even an aspect ratio! Thus, it has become increasingly important for Windows applications to adapt to different screen resolutions. Windows Forms does not do this easily - each element in a Windows Forms application has a statically defined width and height, as well as its position in the window. Altering these values in response to different screen resolution requires significant calculations to resize and reposition the elements, and the code to perform these calculations must be written by the programmer.

In contrast, WPF adopts a multi-pass layout mechanism similar to that of a web browser, where it calculates the necessary space for each element within the GUI, then adapts the layout based on the actual space. With careful design, the need for writing code to position and size elements is eliminated, and the resulting GUIs adapt well to the wide range of available screen sizes.

Direct3D and Hardware Graphics Acceleration

Another major technology shift was the widespread adoption of hardware-accelerated 3D graphics. In the 1990’s this technology was limited to computers built specifically for playing video games, 3D modeling, video composition, or other graphics-intensive tasks. But by 2006, this hardware had become so widely accepted that with Windows Vista, Microsoft redesigned the Windows kernel to leverage this technology to take on the task of rendering windows applications.

WPF leveraged this decision and offloads much of the rendering work to the graphics hardware. This meant that WPF controls could be vector-based, instead of the raster-based approach adopted by Windows Forms. Vector-based rendering means the image to be drawn on-screen is created from instructions as needed, rather than copied from a bitmap. This allows controls to look as sharp when scaled to different screen resolutions or enhanced by software intended to support low-vision users. Raster graphics scaled the same way will look pixelated and jagged.

Leveraging the power of hardware accelerated graphics also allowed for the use of more powerful animations and transitions, as well as freeing up the CPU for other tasks. It also simplifies the use of 3D graphics in windows applications. WPF also leveraged this power to provide a rich storyboarding and animation system as well as inbuilt support for multimedia. In contrast, Windows Forms applications are completely rendered using the CPU and offer only limited support for animations and multimedia resources.

Customizable Styling and Template System

One additional shift is that Windows forms leverage controls built around graphical representations provided directly by the hosting Windows operating system. This helped keep windows applications looking and feeling like the operating system they were deployed on, but limits the customizability of the controls. A commonly attempted feature - placing an image on a button - becomes an onerous task within Windows Forms. Attempting to customize controls often required the programmer to take over the rendering work entirely, providing the commands to render the raw shapes of the control directly onto the control’s canvas. Unsurprisingly, an entire secondary market for pre-developed custom controls emerged to help counter this issue.

In contrast, WPF separated control rendering from windows subsystems, and implemented a declarative style of defining user interfaces using Extensible Application Markup Language (XAML). This provides the programmer complete control over how controls are rendered, and multiple mechanisms of increasing complexity to accomplish this. Style properties can be set on individual controls, or bundled into “stylesheets” and applied en-masse. Further, each control’s default style is determined by a template that can be replaced with a custom implementation, completely altering the appearance of a control.

This is just the tip of the iceberg - WPF also features a new and robust approach to data binding that will be subject of its own chapter, and allows for UI to be completely separated from logic, allowing for more thorough unit testing of application code.

XAML

Windows Presentation Foundation builds upon Extensible Application Markup Language (XAML), an extension of the XML language we’ve discussed previously. Just like XML, it consists of elements defined by opening and closing tags.

For example, a button is represented by:

<Button></Button>

Which, because it has no children, could also be expressed with a self-closing tag:

<Button/>

In addition, elements can have attributes, i.e we could add a height, width, and content to our button:

<Button Height="30" Width="120" Content="Click Me!"/>

XAML also offers an expanded property syntax that is an alternative to attributes. For example, we could re-write the above button as:

<Button>
  <Button.Height>30</Button.Height>
  <Button.Width>120</Button.Width>
  <Button.Content>Click Me!</Button.Content>
</Button>

Note how we repeat the tag name (Button) and append the attribute name (Height, Width, and Content to it with a period between the two). This differentiates the expanded property from nested elements, i.e. in this XAML code:

<Grid>
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="200"/>
    <ColumnDefinition Width="200"/>
    <ColumnDefinition Width="200"/>
  </Grid.ColumnDefinitions>
  <Grid.RowDefinitions>
    <RowDefinition/>
    <RowDefinition/>
  </Grid.RowDefinitions>
  <Button Height="30" Width="120" Content="Click Me!"/>
</Grid>

<Grid.ColumnDefinitions> and <Grid.RowDefinitions> are attributes of the <Grid>, while <Button Height="30" Width="120" Content="Click Me!"/> is a child element of the <Grid> element.

Because XAML is an extension of XML, we can add comments the same way, by enclosing the comment within a <!-- and -->:

<!-- I am a comment -->

XAML Defines Objects

What makes XAML different from vanilla XML is that it defines objects. The XAML used for Windows Presentation Foundation is drawn from the

namespace. This namespace defines exactly what elements exist in this flavor of XAML, and they correspond to specific classes defined in the WPF namespaces.

For example, the <Button> class corresponds to the WPF Button class. This class has a Content property which defines the text or other content displayed on the button. Additionally, it has a Width and Height property. Thus the XAML:

<Button Height="30" Width="120" Content="Click Me!"/>

Effectively says construct an instance of the Button class with its Height property set to 30, its Width property set to 120, and its Content property set to the string "Click Me!". Were we to write the corresponding C# code, it would look like:

var button = new Button();
button.Height = 30;
button.Width = 120;
button.Content = "Click Me!";

This is why XAML stands for Extensible Application Markup Language - it’s effectively another way of writing programs! You can find the documentation for all the controls declared in the xaml/presentation namespace on docs.microsoft.com.

XAML and Partial Classes

In addition to being used to define objects, XAML can also be used to define part of a class. Consider this MainWindow XAML code, which is generated by the Visual Studio WPF Project Template:

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
    </Grid>
</Window>

And its corresponding C# file:

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

Notice the use of the partial modifier in the C# code? This indicates that the MainWindow class is only partially defined in this file (MainWindow.xaml.cs) - the rest of the definition exists in another file. That’s the previously referenced XAML file (MainWindow.xaml). Notice in the XAML that the <Window> element has the attribute x:Class="WpfApp1.MainWindow"? That indicates that it defines part of the class MainWindow defined in the WpfApp1 namespace - it’s the other part of our file!

When we compile this project, the XAML is actually transformed into a temporary C# file as part of the build process, and that C# file is joined with the other C# file. This temporary file defines the InitializeComponent() method, which would look something like this:

void InitializeComponent() 
{
    this.Title = "MainWindow";
    this.Height = 450;
    this.Width = 800;

    var grid = new Grid();
    this.Content = grid;
}

Notice how it sets the properties corresponding to the attributes defined on the <MainWindow> element? Further, it assigns the child of that element (a <Grid> element) as the Content property of the Window. Nested XAML elements are typically assigned to either Content or Children properties, depending on if the element in question is a container element or not (container elements can have multiple children, all other elements are limited to a single child).

Any structure defined in our XAML is set up during this InitializeComponent() call. This means you should never remove the InitializeComponent(); invocation from a WPF class, or your XAML-defined content will not be added. Similarly, you should not manipulate that structure until the InitializeComponent(); method has been invoked, or the structure will not exist!

This strategy of splitting GUI code into two files is known in Microsoft parlance as codebehind, and it allows the GUI’s visual aspect to be created independently of the code that provides its logic. This approach has been a staple of both Windows Forms and Windows Presentation Foundation. This separation also allows for graphic designers to create the GUI look-and-feel without ever needing to write a line of code. There is a companion application to Visual Studio called Blend that can be used to write the XAML files for a project without needing the full weight and useability of Visual Studio.

Tip

Occasionally, Visual Studio will encounter a problem while building the temporary file from the XAML definition, and the resulting temporary file may become corrupted. When this happens, your changes to the XAML are no longer incorporated into the program when you compile, because the process can’t overwrite the corrupted temporary file. Instead, the corrupted temporary file is used - resulting in weird and unexpected behavior. If this happens to you, just run the “Build > Clean” menu option on the project to delete all the temporary files.

Info

Partial classes weren’t a WPF-specific innovation - Windows Forms used them first. In Windows Forms, when you create a form, Visual Studio actually creates two C# files. One of these is the one intended for you to edit, and the other is used by the drag-and-drop editor, which fills it with auto-generated C# code. If you ever make the mistake of editing this file, it will cause all kinds of problems for the drag-and-drop editor (and you)! In contrast, the drag-and-drop editor for WPF actually modifies the same XAML file you do - allowing you to use the editor, manually edit the XAML, or any combination of the two.

Layouts

Windows Presentation Foundation provides a number of container elements that fulfill the specialized purpose of layouts. Unlike most WPF controls, they can have multiple children, which they organize on-screen. And unlike Windows Forms, these layouts adjust to the available space.

Let’s examine each of five layouts in turn:

The Grid

The default layout is the Grid, which lays out its children elements in a grid pattern. A <Grid> is composed of columns and rows, the number and characteristics of which are defined by the grid’s ColumnDefinitions and RowDefinitions properties. These consist of a collection of ColumnDefinition and <RowDefinition/> elements. Each <ColumnDefinition/> is typically given a Width property value, while each <RowDefinition/> is given a Height property value.

Thus, you might expect the code:

<Grid>
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="200"/>
    <ColumnDefinition Width="200"/>
    <ColumnDefinition Width="200"/>
  </Grid.ColumnDefinitions>
  <Grid.RowDefinitions>
    <RowDefinition Height="100"/>
    <RowDefinition Height="100"/>
  </Grid.RowDefinitions>
  <Button Height="30" Width="120" Content="Click Me!"/>
</Grid>

Creates a grid with three columns, each 200 logical units wide, and two rows, each 100 logical units high. However, it will actually create a grid like this:

The resulting Grid The resulting Grid

Remember, all WPF containers will fill the available space - so the grid stretches the last column and row to fill the remaining space. Also, any element declared as a child of the grid (in this case, our button), will be placed in the first grid cell - [0,0] (counted from the top-left corner).

When declaring measurements in WPF, integer values correspond to logical units, which are 1/96th of an inch. We can also use relative values, by following a measurement with a *. This indicates the ratio of remaining space a column or row should take up after the elements with an exact size are positioned. I.e. a column with a width of 2* will be twice as wide as one with a width of 1*.

Thus, to create a 3x3 grid centered in the available space to represent a game of Tic-Tac-Toe we might use:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="1*"/>
        <ColumnDefinition Width="100"/>
        <ColumnDefinition Width="100"/>
        <ColumnDefinition Width="100"/>
        <ColumnDefinition Width="1*"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="1*"/>
        <RowDefinition Height="100"/>
        <RowDefinition Height="100"/>
        <RowDefinition Height="100"/>
        <RowDefinition Height="1*"/>
    </Grid.RowDefinitions>
    <TextBlock Grid.Column="1" Grid.Row="1" FontSize="100" VerticalAlignment="Center" HorizontalAlignment="Center">X</TextBlock>
    <TextBlock Grid.Column="1" Grid.Row="2" FontSize="100" VerticalAlignment="Center" HorizontalAlignment="Center">O</TextBlock>
    <TextBlock Grid.Column="2" Grid.Row="1" FontSize="100" VerticalAlignment="Center" HorizontalAlignment="Center">X</TextBlock>        
</Grid>

Which would create:

The resulting Tic-Tac-Toe grid The resulting Tic-Tac-Toe grid

Note too that we use the properties Grid.Column and Grid.Row in the <TextBlock> elements to assign them to cells in the grid. The row and column indices start at 0 in the upper-left corner of the grid, and increase to the right and down.

The StackPanel

The StackPanel arranges content into a single row or column (defaults to vertical). For example, this XAML:

<StackPanel>
    <Button>Banana</Button>
    <Button>Orange</Button>
    <Button>Mango</Button>
    <Button>Strawberry</Button>
    <Button>Blackberry</Button>
    <Button>Peach</Button>
    <Button>Watermelon</Button>
</StackPanel>

Creates this layout:

The StackPanel Example The StackPanel Example

The StackPanel can be set to a horizontal orientation by setting its Orientation property to Horizontal:

<StackPanel Orientation="Horizontal">
    <Button>Banana</Button>
    <Button>Orange</Button>
    <Button>Mango</Button>
    <Button>Strawberry</Button>
    <Button>Blackberry</Button>
    <Button>Peach</Button>
    <Button>Watermelon</Button>
</StackPanel>

The WrapPanel

The WrapPanel layout is like the <StackPanel>, with the additional caveat that if there is not enough space for its contents, it will wrap to an additional line. For example, this XAML code:

<WrapPanel>
    <Button>Banana</Button>
    <Button>Orange</Button>
    <Button>Mango</Button>
    <Button>Strawberry</Button>
    <Button>Blackberry</Button>
    <Button>Peach</Button>
    <Button>Watermelon</Button>
</WrapPanel>

Produces this layout when there is ample room:

The WrapPanel Example The WrapPanel Example

And this one when things get tighter:

The Squeezed WrapPanel Example The Squeezed WrapPanel Example

The DockPanel

The DockPanel layout should be familiar to you - it’s what Visual Studio uses. Its content items can be ‘docked’ to one of the sides, as defined by the Dock enum: Bottom, Top, Left, or Right by setting the DockPanel.Dock property on that item. The last item specified will also fill the central space. If more than one child is specified for a particular side, it will be stacked with that side.

Thus, this XAML:

<DockPanel>
    <Button DockPanel.Dock="Top">Top</Button>
    <Button DockPanel.Dock="Left">Left</Button>
    <Button DockPanel.Dock="Right">Right</Button>
    <Button DockPanel.Dock="Bottom">Bottom</Button>
    <Button>Center</Button>
</DockPanel>

Generates this layout:

The DockPanel Example The DockPanel Example

The Canvas

Finally, the Canvas lays its content out strictly by their position within the <Canvas>, much like Windows Forms. This approach provides precise placement and size control, at the expense of the ability to automatically adjust to other screen resolutions. For example, the code:

<Canvas>
    <Button Canvas.Top="40" Canvas.Right="40">Do Something</Button>
    <TextBlock Canvas.Left="200" Canvas.Bottom="80">Other thing</TextBlock>
    <Canvas Canvas.Top="30" Canvas.Left="300" Width="300" Height="300" Background="SaddleBrown"/>
</Canvas>

Creates this layout:

The Canvas Example The Canvas Example

If there is a chance the <Canvas> might be resized, it is a good idea to anchor all elements in the canvas relative to the same corner (i.e. top right) so that they all are moved the same amount.

Controls

In addition to the layout controls, WPF provides a number of useful (and often familiar) controls that we can use to compose our applications. Let’s take a look at some of the most commonly used.

Border

A Border is a control that draws a border around its contents. The properties specific to a border include BorderBrush (which sets the color of the border, see the discussion of brushes on the next page), BorderThickness the number of units thick the border should be drawn, CornerRadius, which adds rounded corners, and Padding which adds space between the border and its contents.

<Border BorderBrush="Green" BorderThickness="5" CornerRadius="5" Padding="10">
    <Button>Do Something</Button>
</Border>

A Border Example A Border Example

Button

A Button is a control that draws a button. This button can be interacted with by the mouse, and clicking on it triggers any Click event handlers that have been attached to it. Unlike Windows Forms buttons, it can contain any other WPF control, including images and layouts. Thus, a button featuring an image might be created with:

<Button Click="TriggerBroadcast">
    <StackPanel Orientation="Horizontal">
        <Image Source="dish.jpg" Width="100"/>
        <TextBlock FontSize="25" VerticalAlignment="Center">Broadcast</TextBlock>
    </StackPanel>
</Button>

A Button Example A Button Example

The event handler for the button needs to be declared in the corresponding .xaml.cs file, and will take two parameters, an object and RoutedEventArgs:

/// <summary>
/// An event handler that triggers a broadcast
/// </summary>
/// <param name="sender">The object sending this message</param>
/// <param name="args">The event data</param>
void TriggerBroadcast(object sender, RoutedEventArgs args) {
    // TODO: Send Broadcast
}

We’ll be discussing events in more detail soon.

CheckBox

A CheckBox provides a checkable box corresponding to a boolean value. The IsChecked property reflects the checked or unchecked state of the checkbox. A checkbox also exposes Checked and Unchecked event handlers.

<CheckBox IsChecked="True">
    The sky is blue
</CheckBox>  

A CheckBox Example A CheckBox Example

ComboBox

A ComboBox provides a drop-down selection list. The selected value can be accessed through the SelectedItem property, and the IsEditable boolean property determines if the combo box can be typed into, or simply selected from. It exposes a SelectionChanged event. The items in the ComboBox can be set declaratively:

<ComboBox>
    <ComboBoxItem>Apple</ComboBoxItem>
    <ComboBoxItem>Orange</ComboBoxItem>
    <ComboBoxItem>Peach</ComboBoxItem>
    <ComboBoxItem>Pear</ComboBoxItem> 
</ComboBox>

A ComboBox Example A ComboBox Example

Note that the ComboBox dropdown doesn’t work in the editor - it only operates while the application is running.

Alternatively, you can expose the ComboBox in the codebehind .xaml.cs file by giving it a Name property.

<ComboBox Name="FruitSelection" Text="Fruits" SelectedValue="Apple">
</ComboBox>

Then, after the combo box has been initialized, use the ItemsSource to specify a collection declared in the corresponding .xaml.cs file.

/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
    public UserControl1()
    {
        InitializeComponent();
        FruitSelection.ItemsSource = new List<string>
        {
            "Apple",
            "Orange",
            "Peach",
            "Pear"
        };
    }
}

We could also leverage data binding to bind the item collection dynamically. We’ll discuss this approach later.

Image

The Image control displays an image. The image to display is determined by the Source property. If the image is not exactly the same size as the <Image> control, the Stretch property determines how to handle this case. Its possible values are:

  • "None" (the default) - the image is displayed at its original size
  • "Fill" - the image is resized to the element’s size. This will result in stretching if the aspect ratios are not the same
  • "Uniform" - the image is resized to fit into the element. If the aspect ratios are different, there will be blank areas in the element (letterboxing)
  • "UniformToFill" - the image is resized to fill the element. If the aspect ratios are different, part of the image will be cropped out

The stretch values effects are captured by this graphic:

The applied Stretch property The applied Stretch property

The stretching behavior can be further customized by the StretchDirection property.

Images can also be used for Background or Foreground properties, as discussed on the next page.

Label

A Label displays text on a form, and can be as simple as:

<Label>First Name:</Label>

A Label Example A Label Example

What distinguishes it from other text controls is that it can also be associated with a specific control specified by the Target parameter, whose value should be bound to the name of the control. It can then provide an access key (aka a mnemonic) that will transfer focus to that control when the corresponding key is pressed. The access key is indicated by preceding the corresponding character in the text with an underscore:

<StackPanel>
    <Label Target="{Binding ElementName=firstNameTextBox}">
        <AccessText>_First Name:</AccessText>
    </Label>
    <TextBox Name="firstNameTextBox"/>
</StackPanel>

A Label with Access Key Example A Label with Access Key Example

Now when the program is running, the user can press ALT + F to shift focus to the textbox, so they can begin typing (Note the character “F” is underlined in the GUI). Good use of access keys means users can navigate forms completely with the keyboard.

ListBox

A ListBox displays a list of items that can be selected. The SelectionMode property can be set to either "Single" or "Multiple", and the "SelectedItems" read-only property provides those selected items. The ItemsSource property can be set declaratively using <ListBoxItem> contents. It also exposes a SelectionChanged event handler:

<ListBox>
    <ListBoxItem>Apple</ListBoxItem>
    <ListBoxItem>Orange</ListBoxItem>
    <ListBoxItem>Peach</ListBoxItem>
    <ListBoxItem>Pear</ListBoxItem> 
</ListBox>

A Label with Access Key Example A Label with Access Key Example

RadioButton

A group of RadioButton elements is used to present multiple options where only one can be selected. To group radio buttons, specify a shared GroupName property. Like other buttons, radio buttons have a Click event handler, and also a Checked and Unchecked event handler:

<StackPanel>
    <RadioButton GroupName="Fruit">Apple</RadioButton>
    <RadioButton GroupName="Fruit">Orange</RadioButton>
    <RadioButton GroupName="Fruit">Peach</RadioButton>
    <RadioButton GroupName="Fruit">Pear</RadioButton> 
</StackPanel>

A Radio Button Example A Radio Button Example

TextBlock

A TextBlock can be used to display arbitrary text. It also makes available a TextChanged event that is triggered when its text changes.

<TextBlock>Hi, I have something important to say.  I'm a text block.</TextBlock>

A TextBlock Example A TextBlock Example

TextBox

And a TextBox is an editable text box. It’s text can be accessed through the Text property:

<TextBox Text="And I'm a textbox!"/>

A TextBlock Example A TextBlock Example

ToggleButton

Finally, a ToggleButton is a button that is either turned on or off. This can be determined from its IsChecked property. It also has event handlers for Checked and Unchecked events:

<ToggleButton>On or Off</ToggleButton>

Off looks like:

A ToggleButton Example A ToggleButton Example

And on looks like:

A ToggleButton Example A ToggleButton Example

Other Controls

This is just a sampling of some of the most used controls. You can also reference the System.Windows.Controls namespace documentation, or the TutorialsPoint WPF Controls reference.

Control Properties

All WPF controls (including the layout controls we’ve already seen) derive from common base classes, i.e. UIElement and FrameworkElement, which means they all inherit common properties. Some of the most commonly used are described here.

Size & Placement Modifying Properties

Perhaps the most important of the control properties are those that control sizing and placement. Let’s take a look at the most important of these.

Size

WPF controls use three properties to determine the height of the element. These are MinHeight, Height, and MaxHeight. They are doubles expressed in device-independent units (measuring 1/96 of an inch). The rendering algorithm treats Height as a suggestion, but limits the calculated height to fall in the range between MinHeight and MaxHeight. The height determined by the algorithm can be accessed from the ActualHeight read-only property. Similar values exist for width: MinWidth, Width, MaxWidth, and ActualWidth.

PropertyDefault ValueDescription
MinHeight0.0The minimum element height
HeightNaNThe suggested element height
MaxHeightPositiveInfinityThe maximum element height
MinWidth0.0The minimum element width
WidthNaNThe suggested element width
MaxWidthPositiveInfinityThe maximum element width

Margins

In addition to the size of the element, we can set margins around the element, adding empty space between this and other elements. The Margin property is actually of type Thickness, a structure with four properties: left, top, right, and bottom. We can set the Margin property in several ways using XAML.

To set all margins to be the same size, we just supply a single value:

<Button Margin="3">Do something</Button>

To set different values for the horizontal and vertical margins, use two comma-separated values (horizontal comes first):

<Button Margin="10, 20">Do Something</Button> 

And finally, they can all be set separately as a comma-separated list (the order is left, top, right, and then bottom).

<Button Margin="10, 20, 30, 50">Do Something</Button>

Alignment

You can also align the elements within the space allocated for them using the VerticalAlignment and HorizontalAlignment properties. Similarly, you can align the contents of an element with the VerticalContentAlignment and HorizontalContentAlignment properties.

For most controls, these are "Stretch" by default, which means the control or its contents will expand to fill the available space. Additional values include "Bottom", "Center", and "Top" for vertical, and "Left", "Center", and "Right" for horizontal. These options do not fill the available space - the control is sized in that dimension based on its suggested size.

HorizontalAlignment
OptionDescription
StretchControl fills the available horizontal space
LeftControl is aligned along the left of the available space
CenterControl is centered in the available horizontal space
RightControl is aligned along the right of the available space
VerticalAlignment
OptionDescription
StretchControl fills the available vertical space
TopControl is aligned along the top side of the available space
CenterControl is centered in the available vertical space
BottomControl is aligned along the bottom side of the available space

Text and Font Properties

As most controls prominently feature text, it is important to discuss the properties that effect how this text is presented.

Font Family

The FontFamily property sets the font used by the control. This font needs to be installed on the machine. You can supply a single font, i.e.:

<TextBlock FontFamily="Arial">

Or a list of font families to supply fallback options if the requested font is not available:

<TextBlock FontFamily="Arial, Century Gothic">

Font Size

The FontSize property determines the size of the font used in the control.

Font Style

The FontStyle property sets the style of the font used. This can include "Normal", "Italic", or "Oblique". Italic is typically defined in the font itself (and created by the font creator), while Oblique is created from the normal font by applying a mathematical rendering transformation, and can be used for fonts that do not have a defined italic style.

Font Styles Font Styles

Font Weight

The FontWeight refers to how thick a stroke is used to draw the font. It can be set to values like "Light", "Normal", "Bold", or "Ultra Bold". A list of all available options can be found here.

Text Alignment

The TextAlignment property defines how the text is aligned within its element. Possible values are "Left" (the default), "Center", "Justify", and "Right", and behave just like these options in your favorite text editor.

There is no corresponding vertical alignment option - instead use VerticalContentAlignment discussed above.

Appearance & Interactability Modifying Properties

There are often times in working with a GUI where you might want to disable or even hide a control. WPF controls provide several properties that affect the rendering and interaction of controls.

IsEnabled

The IsEnabled property is a boolean that indicates if this control is currently enabled. It defaults to true. Exactly what ’enabled’ means for a control is specific to that kind of control, but usually means the control cannot be interacted with. For example, a button with IsEnabled=false cannot be clicked on, and will be rendered grayed out, i.e.:

Enabled vs. Disabled Buttons Enabled vs. Disabled Buttons

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <Button IsEnabled="False" Margin="10">I'm Disabled</Button>
    <Button Grid.Column="1" Margin="10">I'm Enabled</Button>
</Grid>

Opacity

A similar effect can be obtained by changing an element’s Opacity property, a double that ranges from 0.0 (completely transparent) to 1.0 (completely solid). Below you can see two <TextBlock> elements, with the one on the left set to an opacity of 0.40:

Opacity Example Opacity Example

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <TextBlock Opacity="0.4" Foreground="Purple" VerticalAlignment="Center" HorizontalAlignment="Center">
      I'm semi-translucent!
    </TextBlock>
    <TextBlock Grid.Column="1" Foreground="Purple" VerticalAlignment="Center" HorizontalAlignment="Center">
      I'm solid!
    </TextBlock>
</Grid>

Alerting an elements’ opacity does not have any effect on its functionality, i.e. a completely transparent button can still be clicked.

Visibility

Finally, the Visible property alters how the element is considered in the WPF rendering algorithm. It has three possible values: "Visible", "Hidden", and "Collapsed". The default value is "Visible", and the element renders normally, as “Button One” does in the example below:

Visible Visibility Example Visible Visibility Example

<StackPanel>
  <Button Visibility="Visible" Margin="10">Button One</Button>
  <Button Margin="10">Button Two</Button>
</StackPanel>

The "Hidden" value will hide the element, but preserve its place in the layout. A hidden element cannot be interacted with, so this is similar to setting the Opacity to 0 and IsEnabled to false:

Hidden Visibility Example Hidden Visibility Example

<StackPanel>
  <Button Visibility="Hidden" Margin="10">Button One</Button>
  <Button Margin="10">Button Two</Button>
</StackPanel>

Finally, the "Collapsed" value will leave the element out of the layout calculations, as though it were not a part of the control at all. A hidden element cannot be interacted with. Note that in the example below, “Button Two” has been rendered in the space previously occupied by “Button One”:

Collapsed Visibility Example Collapsed Visibility Example

<StackPanel>
  <Button Visibility="Collapsed" Margin="10">Button One</Button>
  <Button Margin="10">Button Two</Button>
</StackPanel>

Backgrounds and Foregrounds

You may have noticed the previous examples that colors can be accomplished through the Background and Foreground properties - where the Background determines the color of the element, and Foreground determines the color of text and other foreground elements. While this is true, it is also just the beginning of what is possible. Both of these properties have the type Brush, which deserves a deeper look.

Simply put, a brush determines how to paint graphical objects. This can be as simple as painting a solid color, or as complex as painting an image. The effect used is determined by the type of brush - the Brush class itself serving as a base class for several specific types brush.

Solid Color Brushes

What we’ve been using up to this point have been SolidColorBrush objects. This is the simplest of the brush classes, and simply paints with a solid color, i.e.:

Solid Color Brush Example Solid Color Brush Example

<TextBlock Foreground="BlueViolet" Background="DarkSeaGreen" FontSize="25">
  Look, Ma!  I'm in color!
</TextBlock>

The simplest way to set the color in XAML is to use a value from the predefined brush name list, like the "BlueViolet" and "DarkSeaGreen" in the example.

Alternatively, you can use a hexadecimal number defining the red, green, and blue channels in that order, i.e. to use K-State purple and white we’d use:

<TextBlock Foreground="#FFFFFF" Background="#512888" FontSize="25">
  Look, Ma!  I'm in color!
</TextBlock>

The various formats the hex values can be given are detailed here

Gradient Brushes

Gradient brushes gradually transition between colors. There are two kinds of gradient brushes in WPF: with a LinearGradientBrush the brush gradually changes along a line. With a RadialGradientBrush, the color changes radially from a center point.

In both cases, the gradient is defined in terms of <GradientStops> - a distance along the line (or from the center) where the expected color value is defined. In the spaces between gradient stops, the color value is interpolated between the two stops on either side of the point. The gradient stop needs both an Offset value (a double indicating the percentage of how far along the line or from the center this stop falls, between 0.0 and 1.0) and a Color value (which can be defined as with solid color brushes).

For example, the XAML:

<TextBlock Foreground="#FFFFFF" FontSize="25">
  <TextBlock.Background>
      <LinearGradientBrush>
          <LinearGradientBrush.GradientStops>
              <GradientStop Color="Red" Offset="0.0"/>
              <GradientStop Color="Yellow" Offset="0.25"/>
              <GradientStop Color="Green" Offset="0.50"/>
              <GradientStop Color="Blue" Offset="0.75"/>
              <GradientStop Color="Violet" Offset="1.0"/>
          </LinearGradientBrush.GradientStops>
      </LinearGradientBrush>
  </TextBlock.Background>
  Look, Ma!  I'm in color!
</TextBlock>

Produces this rainbow gradient:

Rainbow Linear Gradient Example Rainbow Linear Gradient Example

Further, the line along which the linear gradient is created is defined by the StartPoint and EndPoint properties of the <LinearGradientBrush>. These points are relative to the area the brush is covering (i.e. the space occupied by the element), and fall in the range of [0.0 .. 1.0]. The default (as seen above) is a diagonal line from the upper left corner (0,0) to the lower right corner (1.0, 1.0).

To make the above gradient fall in the center half of the element, and be horizontal, we could tweak the gradient definition:

<TextBlock Foreground="#FFFFFF" FontSize="25">
    <TextBlock.Background>
        <LinearGradientBrush StartPoint="0.25, 0.5" EndPoint="0.75, 0.5">
            <LinearGradientBrush.GradientStops>
                <GradientStop Color="Red" Offset="0.0"/>
                <GradientStop Color="Yellow" Offset="0.25"/>
                <GradientStop Color="Green" Offset="0.50"/>
                <GradientStop Color="Blue" Offset="0.75"/>
                <GradientStop Color="Violet" Offset="1.0"/>
            </LinearGradientBrush.GradientStops>
        </LinearGradientBrush>
    </TextBlock.Background>
    Look, Ma!  I'm in color!
</TextBlock>

Rainbow Linear Gradient with Start and End Points Example Rainbow Linear Gradient with Start and End Points Example

A <RadialGradientBrush> is defined similarly through the use of GradientStops, only this time they are in relation to the center around which the gradient radiates:

<TextBlock Foreground="#FFFFFF" FontSize="25">
    <TextBlock.Background>
        <RadialGradientBrush>
            <RadialGradientBrush.GradientStops>
                <GradientStop Color="Red" Offset="0.0"/>
                <GradientStop Color="Yellow" Offset="0.25"/>
                <GradientStop Color="Green" Offset="0.50"/>
                <GradientStop Color="Blue" Offset="0.75"/>
                <GradientStop Color="Violet" Offset="1.0"/>
            </RadialGradientBrush.GradientStops>
        </RadialGradientBrush>
    </TextBlock.Background>
    Look, Ma!  I'm in color!
</TextBlock>

Rainbow Radial Gradient Example Rainbow Radial Gradient Example

The gradient fills an ellipse defined by the Center property and the RadiusX and RadiusY properties. By default these values are (0.5. 0.5), 0.5, and 0.5 respectively. Like other gradient properties, they are doubles between 0.0 and 1.0. Finally, the gradient emanates from the GradientOrigin, also a point with values defined by this coordinate system.

To center the above gradient in the left half of the block, we would therefore use:

<TextBlock.Background>
    <RadialGradientBrush Center="0.25, 0.5" RadiusX="0.25" RadiusY="0.5" GradientOrigin="0.25, 0.5">
        <RadialGradientBrush.GradientStops>
            <GradientStop Color="Red" Offset="0.0"/>
            <GradientStop Color="Yellow" Offset="0.25"/>
            <GradientStop Color="Green" Offset="0.50"/>
            <GradientStop Color="Blue" Offset="0.75"/>
            <GradientStop Color="Violet" Offset="1.0"/>
        </RadialGradientBrush.GradientStops>
    </RadialGradientBrush>
</TextBlock.Background>

Rainbow Positioned Radial Gradient Example Rainbow Positioned Radial Gradient Example

And of course, we can use a gradient for a Foreground property as well:

<TextBlock Background="White" FontSize="40">
    <TextBlock.Foreground>
        <LinearGradientBrush>
            <LinearGradientBrush.GradientStops>
                <GradientStop Color="Red" Offset="0.0"/>
                <GradientStop Color="Yellow" Offset="0.25"/>
                <GradientStop Color="Green" Offset="0.50"/>
                <GradientStop Color="Blue" Offset="0.75"/>
                <GradientStop Color="Violet" Offset="1.0"/>
            </LinearGradientBrush.GradientStops>
        </LinearGradientBrush>
    </TextBlock.Foreground>
    Look, Ma!  I'm in color!
</TextBlock>

Gradient Foreground Example Gradient Foreground Example

Image Brushes

To draw a saved image, we use an ImageBrush, setting its ImageSource property to the image we want to use. In XAML, that can be as simple as:

<Button Margin="40" Foreground="White" FontSize="30">
    <Button.Background>
        <ImageBrush ImageSource="Dish.jpg"/>
    </Button.Background>
    Broadcast
</Button>

Image Brush Example Image Brush Example

We can apply image brushes to any WPF control, allowing for some interesting layering effects, i.e.:

<Grid>
    <Grid.Background>
        <ImageBrush ImageSource="watering-can.jpg"/>
    </Grid.Background>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <Button Margin="40" Foreground="White" FontSize="30">
        <Button.Background>
            <ImageBrush ImageSource="Dish.jpg"/>
        </Button.Background>
        Broadcast
    </Button>
</Grid>

Image Brush Layering Example Image Brush Layering Example

You probably notice that the dish image on the button is distorted. We can correct this by changing the Stretch property. The possible values are: "None", "Fill", "Uniform", and "UniformToFill". This graphic from the documentation visual shows these properties:

The applied Stretch property The applied Stretch property

The ImageBrush extends the TileBrush, so the image can actually be tiled if the tile size is set to be smaller than the element that it is painting. The TileBrush Overview provides a detailed breakdown of applying tiling.

Warning

When using images with Visual Studio, it is important to understand how those are used and distributed. You should make sure the images are physically located within the project folder (so that they are included in your source control). Additionally, you want to mark the property “Copy to Output Directory” to either “Copy Always” or “Copy if Newer.” When distributing your project, these files will also need to be distributed, or the image will be unavailable to your executable. Copy to Output Directory Copy to Output Directory

Editing WPF Controls

To create a new WPF control from within Visual Studio, we usually choose “Add > User Control (WPF…)” from the solution context menu.

Adding a UserControl in Visual Studio Adding a UserControl in Visual Studio

This creates two files, the [filename].xaml:

<UserControl x:Class="WpfApp1.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfApp1"
             mc:Ignorable="d" 
             d:DesignHeight="100" d:DesignWidth="400">
    <Grid>
    </Grid>
</UserControl>

and the codebehind for that XAML file, [filename].xaml.cs (where [filename] is the name you supplied):

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for UserControl1.xaml
    /// </summary>
    public partial class UserControl1 : UserControl
    {
        public UserControl1()
        {
            InitializeComponent();
        }
    }

}

As was mentioned previously, the InitializeComponent() call in the constructor is what builds the structure specified in the XAML file to the object, so it should not be removed, nor should any manipulation of that structure be done before this method invocation.

Tip

Most custom controls are subclasses of the UserControl class, and choosing this option has Visual Studio create the boilerplate for us. However, if you need to extend a specific control, i.e. a ListView, it is often easiest to start with a UserControl made this way, and then change the base class to ListView in the [filename].xaml.cs file, as well as changing the <UserControl> element to a <ListView> element in the [filename].xaml.

Namespaces and Assemblies

Also, notice how the attributes of the control in the XAML file contain a local namespace:

 xmlns:local="clr-namespace:WpfApp1"

This is the equivalent of a using statement for XAML; it creates the local namespace and ties it to the project’s primary namespace. We can then create an element corresponding to any class in that namespace. Let’s say we have another custom control, ThingAMaJig that we want to utilize in this control. We can use the element notation to add it to our grid:

    <Grid>
      <local:ThingAMaJig>
    </Grid>

Note that we must preface the class name in the element with the local namespace, with a colon (:) separating the two.

We can also add additional namespace statements. For example:

xmlns:system="clr-namespace:System;assembly=mscorlib"

This brings in the System namespace, so now we can use the classes and types defined there, i.e String:

<system:String>Hello World!</system:String>

Note that for the namespace attribute, we also included the assembly information. This is necessary for any assemblies that are not defined by this project (i.e. exist in their own DLL files).

The WPF Editor

In Visual Studio, opening a WPF XAML file will open a special editor that provides a side-by-side visual and XAML code editors for the control:

The Visual Studio WPF XAML Editor The Visual Studio WPF XAML Editor

As you edit the XAML, it also updates the visualization in the visual editor. Also, many element properties can be edited from the visual editor or the properties pane - and these changes are automatically applied to the XAML. And, just like with Windows Forms, you can drag controls from the toolbox into the visualization to add them to the layout.

However, you will likely find yourselves often directly editing the XAML. This is often the fastest and most foolproof way of editing WPF controls. Remember that in WPF controls resize to fit the available space, and are not positioned by coordinates. For this reason, the visual editor will actually apply margins instead of positioning elements, which can cause unexpected results if your application is viewed at a different resolution (including some controls being inaccessible as they are covered by other controls).

A couple of buttons in the editor deserve some closer attention:

Visual Studio WPF XAML Editor Button Detail 1 Visual Studio WPF XAML Editor Button Detail 1

  1. The zoom factor in the design editor
  2. Refreshes the design editor - sometimes it hangs on re-rendering, and you need to click this.
  3. Toggles rendering effects (These use the graphics hardware, and can be computationally involved. Turning them off can improve performance on weaker machines)
  4. Toggles the snap grid (provides grid lines for easier layout)
  5. Toggles snap-to-grid
  6. Toggles the artboard background (which provides a checkered background to make it easier to judge what is transparent)
  7. Toggles snapping to snap lines (those lines that appear between controls to help you align them)
  8. Toggles showing platform-only vs. all controls (for when targeting multiple platforms)

Visual Studio WPF XAML Editor Button Detail 2 Visual Studio WPF XAML Editor Button Detail 2

  1. Switches to a vertical split between the design editor and XAML editor
  2. Switches to a horizontal split between the design editor and XAML editor
  3. Switches to showing only the design editor or XAML editor

Component-Based Design

WPF and XAML lend themselves to a design approach known as Component-Based Design or Component-Based Development, which rather than focusing on developing the entire GUI in one go, focuses on decomposing user experiences (UX) into individual, focused, and potentially reusable components. These can, in turn, be used to build larger components, and eventually, the entire GUI (see “UX Principles for Designing Component Based Systems” for more details).

Let’s dig deeper by focusing on a specific example. Let’s say we want to build an application for keeping track of multiple shopping lists. So our core component is a displayed list, plus a mechanism for adding to it. Let’s create a UserComponent to represent this.

For laying out the component, let’s say at the very top, we place the text “Shopping List For”, and directly below that we have an editable text box where the user can enter a store name. On the bottom, we’ll have a text box to enter a new item, and a button to add that item to the list. And in the space between, we’ll show the list in its current form. This sounds like an ideal fit for the DockPanel:

<UserControl x:Class="ShopEasy.ShoppingList"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:ShopEasy"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="200">
    <DockPanel>
        <TextBlock DockPanel.Dock="Top" FontWeight="Bold" TextAlignment="Center">
            Shopping List For:
        </TextBlock>
        <TextBox DockPanel.Dock="Top" FontWeight="Bold" TextAlignment="Center" />
        <Button DockPanel.Dock="Bottom" Click="AddItemToList">Add Item To List</Button>
        <TextBox Name="itemTextBox" DockPanel.Dock="Bottom"/>
        <ListView Name="itemsListView" />
    </DockPanel>
</UserControl>

Now in our codebehind, we’ll need to define the AddItemToList event handler:

using System.Windows;
using System.Windows.Controls;

namespace ShopEasy
{
    /// <summary>
    /// Interaction logic for ShoppingList.xaml
    /// </summary>
    public partial class ShoppingList : UserControl
    {
        /// <summary>
        /// Constructs a new ShoppingList
        /// </summary>
        public ShoppingList()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Adds the item in the itemTextBox to the itemsListView
        /// </summary>
        /// <param name="sender">The object sending the event</param>
        /// <param name="e">The events describing the event</param>
        void AddItemToList(object sender, RoutedEventArgs e)
        {
            // Make sure there's an item to add
            if (itemTextBox.Text.Length == 0) return;
            // Add the item to the list
            itemsListView.Items.Add(itemTextBox.Text);
            // Clear the text box
            itemTextBox.Clear();
        }
    }
}

This particular component is pretty much self-contained. We can use it in other components that need a shopping list. In our case, we’ll add it to a collection of shopping lists we can flip through with a couple of buttons, as well as create new lists in. Let’s call this control ListSwitcher.

This time, let’s use a Grid layout and divide the available space into three columns and two rows. The columns we’ll leave with the default width ("1*"), but the bottom row we’ll set as 100 units high, leaving the top row to expand to fill the remaining space. Along the bottom we’ll create three buttons to navigate between shopping lists. On the top, we’ll use the Grid.ColumnSpan property on a Border to span the three columns, creating a container where we’ll display the current ShoppingList:

<UserControl x:Class="ShopEasy.ListSwitcher"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:ShopEasy"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="200">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="100"/>
        </Grid.RowDefinitions>
        <Border Name="listContainer" Grid.ColumnSpan="3">
        </Border>
        <Button Grid.Row="1" Click="OnPriorList">
            &lt; Prior List
        </Button>
        <Button Grid.Row="1" Grid.Column="1" Click="OnNewList">
            New List
        </Button>
        <Button Grid.Row="1" Grid.Column="2" Click="OnNextList">
            Next List &gt;
        </Button>
    </Grid>
</UserControl>

Now we’ll implement the three button Click event handlers in the codebehind, as well as creating a List<ShoppingList> to store all of our lists:

using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;

namespace ShopEasy
{
    /// <summary>
    /// Interaction logic for ListSwitcher.xaml
    /// </summary>
    public partial class ListSwitcher : UserControl
    {
        /// <summary>
        /// The list of shopping lists managed by this control
        /// </summary>
        List<ShoppingList> lists = new List<ShoppingList>();

        /// <summary>
        /// The index of the currently displayed shopping list
        /// </summary>
        int currentListIndex = 0;

        /// <summary>
        /// Constructs a new ListSwitcher
        /// </summary>
        public ListSwitcher()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Creates a new ShoppingList and displays it
        /// </summary>
        /// <param name="sender">What triggered this event</param>
        /// <param name="e">The parameters of this event</param>
        void OnNewList(object sender, RoutedEventArgs e)
        {
            // Create a new shopping list
            var list = new ShoppingList();
            // The current count of lists will be the index of the next list added
            currentListIndex = lists.Count;
            // Add the list to the list of shopping lists
            lists.Add(list);
            // Display the list on the control
            listContainer.Child = list;
        }

        /// <summary>
        /// Displays the prior shopping list
        /// </summary>
        /// <param name="sender">What triggered this event</param>
        /// <param name="e">The parameters of this event</param>
        void OnPriorList(object sender, RoutedEventArgs e)
        {
            // don't try to access an empty list 
            if (lists.Count == 0) return;
            // decrement the currentListIndex
            currentListIndex--;
            // make sure we don't go below the first index in the list (0)
            if (currentListIndex < 0) currentListIndex = 0;
            // display the indexed list 
            listContainer.Child = lists[currentListIndex];
        }

        /// <summary>
        /// Displays the next shopping list
        /// </summary>
        /// <param name="sender">What triggered this event</param>
        /// <param name="e">The parameters of this event</param>
        void OnNextList(object sender, RoutedEventArgs e)
        {
            // don't try to access an empty list 
            if (lists.Count == 0) return;
            // increment the currentListIndex
            currentListIndex++;
            // make sure we don't go above the last index in the list (Count - 1)
            if (currentListIndex > lists.Count - 1) currentListIndex = lists.Count - 1;
            // display the indexed list 
            listContainer.Child = lists[currentListIndex];
        }
    }
}

And finally, we’ll modify our MainWindow XAML to display a ListSwitcher:

<Window x:Class="ShopEasy.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ShopEasy"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="200">
    <Grid>
        <local:ListSwitcher/>
    </Grid>
</Window>

The resulting app allows us to create multiple shopping lists, and swap between them using the buttons:

ShopEasy App ShopEasy App

Much like we can use objects to break program functionality into smaller, more focused units, we can use component-based design to break GUIs into smaller, more focused units. Both reflect one of the principles of good programming practice - the Single Responsibility Principle. This principle suggests each unit of code should focus on a single responsibility, and more complex behaviors be achieved by using multiple units together. As we see here, this principle extends across multiple programming paradigms.

Summary

In this chapter, we introduced a new desktop application programming framework - Windows Presentation Foundation (WPF). We explored how WPF uses XAML to define partial classes, allowing for a graphical design editor with regular C# codebehind. We explored XAML syntax and many of the controls found in WPF. We also compared WPF with Windows Forms, which you have previously explored in prior classes. Finally, we discussed an approach to developing GUIs, Component-Based Design, which applies the Single Responsibility Principle to controls, and builds more complex controls through composition of these simpler controls.

Chapter 2

Exploring Elements

Our application is a tree?

Subsections of Exploring Elements

Introduction

In the previous chapter, we introduced Windows Presentation Foundation and XAML, and discussed common layouts and controls, as well as some of the most common features of each of them. We also saw the concept of component-based design and explored its use. In this chapter, we’ll take a deeper dive into how WPF and XAML structure GUIs into an elements tree, and some different ways we can leverage these features for greater control and customization in our programs.

Key Terms

  • The Elements Tree
  • Styles
  • Resources

C# Keywords and Elements

  • <Style>
  • <Setter>
  • StaticResource

Key Skills

In this chapter, you should learn how to navigate the elements tree, declare styles to simplify styling your applications, and declare resources that can be bound to properties of controls.

The Elements Tree

Consider the ShoppingList class we developed in the last chapter:

<UserControl x:Class="ShopEasy.ShoppingList"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:ShopEasy"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="200">
    <DockPanel>
        <TextBlock DockPanel.Dock="Top" FontWeight="Bold" TextAlignment="Center">
            Shopping List For:
        </TextBlock>
        <TextBox DockPanel.Dock="Top" FontWeight="Bold" TextAlignment="Center" />
        <Button DockPanel.Dock="Bottom" Click="AddItemToList">Add Item To List</Button>
        <TextBox Name="itemTextBox" DockPanel.Dock="Bottom"/>
        <ListView Name="itemsListView" />
    </DockPanel>
</UserControl>

Each element in this XAML corresponds to an object of a specific Type, and the nesting of the elements implies a tree-like structure we call the element tree. We can draw this out as an actual tree:

The elements tree for the ShoppingList component The elements tree for the ShoppingList component

The relationships in the tree are also embodied in the code. Each element has either a Child or Children property depending on if it can have just one or multiple children, and these are populated by the elements defined in the XAML. Thus, because the <DockPanel> has nested within it, a <TextBlock>, <TextBox>, <Button>, <TextBox>, and <ListView>, these are all contained in its Children Property. In turn, the <Button> element has text as a child, which is implemented as another <TextBlock>. Also, each component has a Parent property, which references the control that is its immediate parent.

In other words, all the WPF controls are effectively nodes in a tree data structure. We can modify this data structure by adding or removing nodes. This is exactly what happens in the ListSwitcher control - when you click the “New List” button, or the “Prior” or “Next” button, you are swapping the subtree that is the child of its <Border> element:

The elements tree for the ListSwitcher component The elements tree for the ListSwitcher component

In fact, the entire application is one large tree of elements, with the <Application> as its root:

ShopEasy App element tree ShopEasy App element tree

Navigating the Tree

When you first learned about trees, you also learned about tree traversal algorithms. This is one reason that WPF is organized into a tree - the rendering process actually uses a tree traversal algorithm to determine how large to make each control!

You can also traverse the tree yourself, by exploring Child, Children, or Parent properties. For example, if we needed to gain access to the ListSwitcher from the ShoppingList in the previous example, you could reach it by invoking:

   ListSwitcher switcher = this.Parent.Parent.Parent as ListSwitcher;

In this example, this is our ShoppingList, the first Parent is the Border containing the ShoppingList, the second Parent is the Grid containing that Border, and the third Parent is the actual ListSwitcher. We have to cast it to be a ListSwitcher because the type of the Parent property is a DependencyObject (a common base class of all controls).

Of course, this is a rather brittle way of finding an ancestor, because if we add any nodes to the element tree (perhaps move the Grid within a DockPanel), we’ll need to rewrite it. It would be better to use a loop to iteratively climb the tree until we find the control we’re looking for. This is greatly aided by the LogicalTreeHelper library, which provides standardized static methods for accessing parents and children in the elements tree:

// Start climbing the tree from this node
DependencyObject parent = this;
do
{
    // Get this node's parent
    parent = LogicalTreeHelper.GetParent(parent);
}
// Invariant: there is a parent element, and it is not a ListSwitcher 
while(!(parent is null || parent is ListSwitcher));
// If we get to this point, parent is either null, or the ListSwitcher we're looking for

Searching the ancestors is a relatively easy task, as each node in the tree has only one parent. Searching the descendants takes more work, as each node may have many children, with children of their own.

This approach works well for complex applications with complex GUIs, where it is infeasible to keep references around. However, for our simple application here, it might make more sense to refactor the ShoppingList class to keep track of the ListSwitcher that created it, i.e.:

using System.Windows;
using System.Windows.Controls;

namespace ShopEasy
{
    /// <summary>
    /// Interaction logic for ShoppingList.xaml
    /// </summary>
    public partial class ShoppingList : UserControl
    {
        /// <summary>
        /// The ListSwitcher that created this list
        /// </summary>
        private ListSwitcher listSwitcher;

        /// <summary>
        /// Constructs a new ShoppingList
        /// </summary>
        public ShoppingList(ListSwitcher listSwitcher)
        {
            InitializeComponent();
            this.listSwitcher = listSwitcher;
        }

        /// <summary>
        /// Adds the item in the itemTextBox to the itemsListView
        /// </summary>
        /// <param name="sender">The object sending the event</param>
        /// <param name="e">The events describing the event</param>
        void AddItemToList(object sender, RoutedEventArgs e)
        {
            // Make sure there's an item to add
            if (itemTextBox.Text.Length == 0) return;
            // Add the item to the list
            itemsListView.Items.Add(itemTextBox.Text);
            // Clear the text box
            itemTextBox.Clear();
        }
    }
}

However, this approach now tightly couples the ListSwitcher and ShoppingList - we can no longer use the ShoppingList for other contexts without a ListSwitcher.

If we instead employed the the traversal algorithm detailed above:

using System.Windows;
using System.Windows.Controls;

namespace ShopEasy
{
    /// <summary>
    /// Interaction logic for ShoppingList.xaml
    /// </summary>
    public partial class ShoppingList : UserControl
    {
        /// <summary>
        /// The ListSwitcher that created this list
        /// </summary>
        private ListSwitcher listSwitcher { 
            get {
                DependencyObject parent = this;
                do
                {
                    // Get this node's parent
                    parent = LogicalTreeHelper.GetParent(parent);
                }
                // Invariant: there is a parent element, and it is not a ListSwitcher 
                while(!(parent is null || parent is ListSwitcher));
                return parent;
            }
        }

        /// <summary>
        /// Constructs a new ShoppingList
        /// </summary>
        public ShoppingList()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Adds the item in the itemTextBox to the itemsListView
        /// </summary>
        /// <param name="sender">The object sending the event</param>
        /// <param name="e">The events describing the event</param>
        void AddItemToList(object sender, RoutedEventArgs e)
        {
            // Make sure there's an item to add
            if (itemTextBox.Text.Length == 0) return;
            // Add the item to the list
            itemsListView.Items.Add(itemTextBox.Text);
            // Clear the text box
            itemTextBox.Clear();
        }
    }
}

We could invoke the listSwitcher property to get the ancestor ListSwitcher. If this control is being used without one, the value will be Null.

Styling the Tree

Windows Presentation Foundation takes advantage of the elements tree in other ways. One of the big ones is for styling related elements. Let’s say we are creating a calculator GUI:

<UserControl x:Class="Calculator.Calculator"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Calculator"
        mc:Ignorable="d"
        d:DesignWidth="450" d:DesignHeight="450">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        
        <Button Grid.Column="0" Grid.Row="1">7</Button>
        <Button Grid.Column="1" Grid.Row="1">8</Button>
        <Button Grid.Column="2" Grid.Row="1">9</Button>
        <Button Grid.Column="0" Grid.Row="2">4</Button>
        <Button Grid.Column="1" Grid.Row="2">5</Button>
        <Button Grid.Column="2" Grid.Row="2">6</Button>
        <Button Grid.Column="0" Grid.Row="3">1</Button>
        <Button Grid.Column="1" Grid.Row="3">2</Button>
        <Button Grid.Column="2" Grid.Row="3">3</Button>
        <Button Grid.Column="0" Grid.Row="4" Grid.ColumnSpan="3">0</Button>
        <Button Grid.Column="3" Grid.Row="1">+</Button>
        <Button Grid.Column="3" Grid.Row="2">-</Button>
        <Button Grid.Column="3" Grid.Row="3">*</Button>
        <Button Grid.Column="3" Grid.Row="4">/</Button>
    </Grid>
</Window>

Once we have the elements laid out, we realize the text of the buttons is too small. Fixing this would mean setting the FontSize property of each <Button>. That’s a lot of repetitive coding.

Thankfully, the XAML developers anticipated this kind of situation, and allow us to attach a <Style> resource to the control. We typically would do this above the controls we want to style - in this case, either on the <Grid> or the <UserControl>. If we were to attach it to the <Grid>, we’d declare a <Grid.Resources> property, and inside it, a <Style>:

<Grid.Resources>
    <Style>
    </Style>
</Grid.Resources>

The <Style> element allows us to specify a TargetType property, which is the Type we want the style to apply to - in this case "Button". Inside the <Style> element, we declare <Setter> elements, which need Property and Value attributes. As you might guess from the names, the <Setter> will set the specified property to the specified value on each element of the target type.

Therefore, if we use:

<Grid.Resources>
    <Style TargetType="Button">
        <Setter Property="FontSize" Value="40"/>
    </Style>
</Grid.Resources>

The result will be that all buttons that are children of the <Grid> will have their FontSize set to 40 device-independent pixels. We don’t need to add a separate FontSize="40" to each one! However, if we add FontSize="50" to a single button, that button alone will have a slightly larger font.

We can declare as many <Setters> as we want in a <Style> element, and as many <Style> elements as we want in a <.Resources> element. Moreover, styles apply to all children in the elements tree. Closer setters override those farther up the tree, and setting the property directly on an element always gives the final say.

Thus, we might put application-wide styles directly in our MainWindow using <Window.Resources>, and override those further down the elements tree when we want a different behavior.

Info

You may notice some similarities between <Style> elements and the cascading style sheets (css) of web technologies. This is not surprising, as the styling approach used in WPF was inspired by CSS, much as XAML drew inspiration from HTML. However, the implementation details are necessarily different, as XAML is effectively declaring C# objects. Hence, the use of ‘setters’ to set ‘properties’ to a specific ‘value’.

Resources

The <Style> element represents just one kind of resource. We can provide other kinds of resources, like raw data. Say we want to provide a string to display in our program, but want that string declared somewhere easy to find and change (perhaps our customers change their mind frequently). We could declare the string in the Application resources:

<Application x:Class="WpfTutorialSamples.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:sys="clr-namespace:System;assembly=mscorlib"
             StartupUri="WPF application/ExtendedResourceSample.xaml">
    <Application.Resources>
        <sys:String x:Key="StringToDisplay">Hello World!</sys:String>
    </Application.Resources>
</Application>

Then, in our actual control we can use that string as a static resource:

<TextBlock Text="{StaticResource StringToDisplay}">

As long as that element is a descendant of the element the resource is declared on, it will be used in the property. In this case, we’ll display the string “Hello World!” in the TextBlock. Note that we have to use the x:Key property to identify the resource, and repeat the key in the "{StaticResource StringToDisplay}". The curly braces and the StaticResource both need to be there (technically, they are setting up a data binding, which we’ll talk about in a future chapter).

We can declare any kind of type as a resource and make it available in our XAML this way.

For example, we could create a LinearGradientBrush:

<Application x:Class="WpfTutorialSamples.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:sys="clr-namespace:System;assembly=mscorlib"
             StartupUri="WPF application/ExtendedResourceSample.xaml">
    <Application.Resources>
        <LinearGradientBrush x:Key="Rainbow">
            <LinearGradientBrush.GradientStops>
                <GradientStop Color="Red" Offset="0.0"/>
                <GradientStop Color="Yellow" Offset="0.25"/>
                <GradientStop Color="Green" Offset="0.50"/>
                <GradientStop Color="Blue" Offset="0.75"/>
                <GradientStop Color="Violet" Offset="1.0"/>
            </LinearGradientBrush.GradientStops>
        </LinearGradientBrush>
    </Application.Resources>
</Application>

And then use it as a Background or Foreground property in our controls:

<Grid Background="{StaticResource Rainbow}">

Since it is only defined in one place, it is now easier to reuse, and if we ever need to change it, we only need to change it in one location.

Finally, we can create static resources from images and other media. First, we have to set its build action to “Resource” in the “Properties” window after adding it to our project:

Setting the image properties Setting the image properties

Then we can declare a <BitmapImage> resource using a UriSource property that matches the path to the image within our project:

<Application x:Class="WpfTutorialSamples.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:sys="clr-namespace:System;assembly=mscorlib"
             StartupUri="WPF application/ExtendedResourceSample.xaml">
    <Application.Resources>
        <BitmapImage x:Key="MountainImage" UriSource="Images/mountains.jpg"/>
    </Applicaton.Resources>
</Application>

And then we can use this as the ImageSource for an ImageBrush:

<Grid>
    <Grid.Background>
        <ImageBrush ImageSource="{StaticResource MountainImage}"/>
    </Grid.Background>
</Grid>

The benefit of using images and other media as resources is that they are compiled into the binary assembly (the .dll or .exe file). This means they don’t need to be copied separately when we distribute our application.

Templates

Most WPF controls are themselves composed of multiple, simpler, controls. For example, a <Button> is composed of a <Border> and whatever content you place inside the button. A simplified version of this structure appears below (I removed the styling information and the VisualState components responsible for presenting the button differently when it is enabled, disabled, hovered on, or clicked):

<Border TextBlock.Foreground="{TemplateBinding Foreground}"
        x:Name="Border"
        CornerRadius="2"
        BorderThickness="1">
  <Border.BorderBrush>
    <LinearGradientBrush StartPoint="0,0"
                          EndPoint="0,1">
      <LinearGradientBrush.GradientStops>
        <GradientStopCollection>
          <GradientStop Color="{DynamicResource BorderLightColor}"
                        Offset="0.0" />
          <GradientStop Color="{DynamicResource BorderDarkColor}"
                        Offset="1.0" />
        </GradientStopCollection>
      </LinearGradientBrush.GradientStops>
    </LinearGradientBrush>
  </Border.BorderBrush>
  <Border.Background>
    <LinearGradientBrush EndPoint="0.5,1"
                          StartPoint="0.5,0">
      <GradientStop Color="{DynamicResource ControlLightColor}"
                    Offset="0" />
      <GradientStop Color="{DynamicResource ControlMediumColor}"
                    Offset="1" />
    </LinearGradientBrush>
  </Border.Background>
  <ContentPresenter Margin="2"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center"
                    RecognizesAccessKey="True" />
</Border>

This has some implications for working with the control - for example, if you wanted to add rounded corners to the <Button>, they would actually need to be added to the <Border> inside the button. This can be done by nesting styles, i.e.:

<Grid>
    <Grid.Resources>
        <Style TargetType="Button">
            <Style.Resources>
                <Style TargetType="Border">
                    <Setter Property="CornerRadius" Value="25"/>
                </Style>
            </Style.Resources>
        </Style>
    </Grid.Resources>
    <Button>I have rounded corners now!</Button>
</Grid>

Note how the <Style> targeting the <Border> is nested inside the Resources of the <Style> targeting the <Button>? This means that the style rules for the <Border> will only be applied to <Border> elements that are part of a <Button>.

Templates

Above I listed a simplified version of the XAML used to create a button. The full listing can be found in the Microsoft Documentation

What’s more, you can replace this standard rendering in your controls by replacing the Template property. For example, we could replace our button with a super-simple rounded <Border> that nested a <TextBlock> that does word-wrapping of the button content:

<Button>
    <Button.Template>
        <ControlTemplate>
            <Border CornerRadius="25">
                <TextBlock TextWrapping="Wrap">  
                    <ContentPresenter Margin="2"
                        HorizontalAlignment="Center"
                        VerticalAlignment="Center"
                        RecognizesAccessKey="True" />
                </TextBlock>
            </Border>
        </ControlTemplate>
    </Button.ControlTemplate>
    This is a simple button!
</Button>

The <ContentPresenter> is what presents the content nested inside the button - in this case, the text This is a simple button!. Of course, this super-simple button will not change its appearance when you hover over it or click it, or when it is disabled. But it helps convey the idea of a <ControlTemplate>. As with any other property, you can also set the Template property of a control using a <Setter> within a <Style> targeting that element.

If you only need a simple tweak - like applying word-wrapping to the text of a button, it often makes more sense to supply as content a control that will do so, i.e.:

<Button>
    <TextBlock TextWrapping="Wrap">
        I also wrap text!
    </TextBlock>
</Button>

This allows the <Button> to continue to use the default ControlTemplate while providing the desired word-wrapping with a minimum of extra code.

A similar idea appears with <DataTemplate>, which allows you to customize how bound data is displayed in a control. For example, we often want to display the items in a <ListBox> in a different way than the default (a <TextBlock> with minimal styling). We’ll visit this in the upcoming binding lists section.

Summary

In this chapter, we saw how WPF applications are organized into a tree of controls. Moreover, we discussed how WPF uses this tree to perform its layout and rendering calculations. We also saw how we can traverse this tree in our programs to find parent or child elements of a specific type.

In addition, we saw how declaring resources at a specific point in the tree makes them available to all elements descended from that node. The resources we looked at included <Style> elements, which allow us to declare setters for properties of a specific type of element, to apply consistent styling rules.

We also saw how we could declare resources with a x:Key property, and bind them as static resources to use in our controls - including strings and other common types. Building on that idea, we saw how we could embed images and other media files as resources.

We also explored how <ControlTemplates> are used to compose complex controls from simpler controls, and make it possible to swap out that implementation for a custom one. We also briefly discussed when it may make more sense to compose the content of a control differently to get the same effect.

When we explore events and data binding in later chapters, we will see how these concepts also interact with the element tree in novel ways.

Chapter 3

Event-Driven Programming

I Fight for the Users!

Subsections of Event-Driven Programming

Introduction

Event-Driven programming is a programming paradigm where the program primarily responds to events - typically generated by a user, but also potentially from sensors, network connections, or other sources. We cover it here because event-driven programming is a staple of graphical user interfaces. These typically display a fairly static screen until the user interacts with the program in some meaningful way - moving or clicking the mouse, hitting a key, or the like.

As you might suspect, event-driven programming is often used alongside other programming paradigms, including structured programming and object-orientation. We’ll be exploring how event-driven programming is implemented in C# in this chapter, especially how it interacts with Windows Presentation Foundation.

Key Terms

  • Event
  • Message Loop
  • Message Queue
  • Event Handler
  • Event Listener
  • Event Arguments

C# Keywords and Operators

  • event
  • EventArgs
  • +=
  • -=
  • ?.

Key Skills

The key skills to learn in this chapter are how to write event listeners, attach event listeners to event handlers, and how to define custom event handlers.

Message Loops

At the heart of every Windows program (and most operating systems), is an infinitely repeating loop we call the message loop and a data structure we call a message queue (some languages/operating systems use the term event instead of message). The message queue is managed by the operating system - it adds new events that the GUI needs to know about (i.e. a mouse click that occurred within the GUI) to this queue. The message loop is often embedded in the main function of the program, and continuously checks for new messages in the queue. When it finds one, it processes the message. Once the message is processed, the message loop again checks for a new message. The basic code for such a loop looks something like this:

function main
    initialize()
    while message != quit
        message := get_next_message()
        process_message(message)
    end while
end function

This approach works well for most GUIs as once the program is drawn initially (during the initialize() function), the appearance of the GUI will not change until it responds to some user action.

In a WPF or Windows Forms application, this loop is buried in the Application class that the App inherits from. Instead of writing the code to process these system messages directly, this class converts these messages into C# events, which are then consumed by the event listeners the programmer provides. We’ll look at these next.

Event Handlers

In C#, we use event handlers (sometimes called event listeners in other languages) to register the behavior we want to happen in response to specific events. You’ve probably already used these, i.e. declaring a handler:

private void OnEvent(object sender, EventArgs e) {
    // TODO: Respond to the event
}

Most event handlers follow the same pattern. They do not have a return value (their return type is void), and take two parameters. The first is always an object, and it is the source of the event (hence “sender”). The second is an EventArgs object, or a class descended from EventArgs, which provides details about the event.

For example, the various events dealing with mouse input (MouseMove, MouseDown, MouseUp) supply a MouseEventArgs object. This object includes properties defining the mouse location, number of clicks, mouse wheel rotations, and which button was involved in the event.

You’ve probably attached event handlers using the “Properties” panel in Visual Studio, but you can also attach them in code:

Button button = new Button();
button.Click += OnClick;

Note you don’t include parenthesis after the name of the event handler. You aren’t invoking the event handler, you’re attaching it (so it can be invoked in the future when the event happens). Also, note that we use the += operator to signify attaching an event handler.

This syntax is a deliberate choice to help reinforce the idea that we can attach multiple event handlers in C#, i.e.:

Button button = new Button();
button.Click += onClick1;
button.Click += onClick2;

In this case, both onClick1 and onClick2 will be invoked when the button is clicked. This is also one reason to attach event handlers programmatically rather than through the “Properties” window (it can only attach one).

We can also remove an event handler if we no longer want it to be invoked when the event happens. We do this with the -= operator:

button.Click -= onClick1;

Again, note we use the handler’s name without parenthesis.

Declaring Events

Up to this point, you’ve probably only used events that were defined on library objects, like the Button’s Click event. However, you can also declare events in your own classes, and even create new event types.

In order to attach an event handler to an object in C#, we must first declare that that object has the corresponding event. To do so, we need both a name for the event, and a delegate.

In C# a Delegate is a special type that represents a method with a specific method signature and return type. A delegate allows us to associate the delegate with any method that matches that signature and return type. For example, the Click event handler we discussed earlier is a delegate which matches a method that takes two arguments: an object and an EventArgs, and returns void. Any event listener that we write that matches this specification can be attached to the button.Click. In a way, a Delegate is like an Interface, only for methods instead of objects.

Consider a class representing an egg. What if we wanted to define an event to represent when it hatched? We’d need a delegate for that event, which can be declared a couple of ways. The traditional method would be:

public delegate void HatchHandler(object sender, EventArgs args);

And then in our class we’d declare the corresponding event. In C#, these are written much like a field, but with the addition of the event keyword:

public event HatchHandler Hatch;

Like a field declaration, an event declaration can have an access modifier (public, private, or protected), a name (in this case Hatch), and a type (the delegate). It also gets marked with the event keyword.

When C# introduced generics, it became possible to use a generic delegate as well, EventHandler<T>, where the T is the type for the event arguments. This simplifies writing an event, because we no longer need to define the delegate ourselves. So instead of the two lines above, we can just use:

public event EventHandler<EventArgs> Hatch;

The second form is increasingly preferred, as it makes testing our code much easier (we’ll see this soon), and it’s less code to write.

Custom EventArgs

We might also want to create our own custom event arguments to accompany the event. Perhaps we want to provide a reference to an object representing the baby chick that hatched. To do so we can create a new class that inherits from EventArgs:

/// <summary>
/// A class representing the hatching of a chick 
/// </summary>
public class HatchEventArgs : EventArgs 
{
    /// <summary>
    /// The chick that hatched 
    /// </summary>
    public Chick Chick { get; protected set; }

    /// <summary>
    /// Constructs a new HatchEventArgs 
    /// </summary>
    /// <param name="chick">The chick that hatched</param>
    public HatchEventArgs(Chick chick) 
    {
        this.Chick = chick;
    }
}

And we use this custom event args in our event declaration as the type for the generic EventHandler<T> generic:

public event EventHandler<HatchEventArgs> Hatch;

Now let’s say we set up our Egg constructor to start a timer to determine when the egg will hatch:

public Egg() 
{
    // Set a timer to go off in 20 days 
    // (ms = 20 days * 24 hours/day * 60 minutes/hour * 60 seconds/minute * 1000 milliseconds/seconds)
    var timer = new System.Timers.Timer(20 * 24 * 60 * 60 * 1000);
    timer.Elapsed += StartHatching;
}

In the event handler StartHatching, we’ll want to create our new baby chick, and then trigger the Hatch event. To do this, we need to raise the event to pass to any attached handlers with Hatch.Invoke(), passing in both the event arguments and the source of the event (our egg):

private void StartHatching(object source, ElapsedEventArgs e) 
{
    var chick = new Chick();
    var args = new HatchEventArgs(chick);
    Hatch.Invoke(this, args);
}

However we might have the case where there are no registered event handlers, in which case Hatch evaluates to null, and attempting to call Invoke() will cause an error. We can prevent this by wrapping our Invoke() within a conditional:

if(Hatch != null) {
    Hatch.Invoke(this, args);
}

However, there is a handy shorthand form for doing this (more syntactic sugar):

Hatch?.Invoke(this, args);

Using the question mark (?) before the method invocation is known as the Null-condition operator. We use this to avoid calling the Invoke() method if PropertyChanged is null (which is the case if no event handlers have been assigned to it). It tests the object to see if it is null. If it is null, the method is not invoked.

Info

You might be wondering why an event with no assigned event handlers is Null instead of some form of empty collection. The answer is rooted in efficiency - remember that each object (even empty collection) requires a certain amount of memory to hold its details. Now think about all the possible events we might want to listen for in a GUI. The System.Windows.Controls.Control Class (a base class for all WPF controls) defines around 100 events. Now multiply that by all the controls used in a single GUI, and you’ll see that small amount of memory consumption adds up quickly. By leaving unused events null, C# saves significant memory!

Thus, our complete egg class would be:

/// <summary>
/// A class representing an egg 
/// </summary>
public class Egg 
{
    /// <summary>
    /// An event triggered when the egg hatches 
    /// </summary>
    public event EventHandler<HatchEventArgs> Hatch;

    /// <summary>
    /// Constructs a new Egg instance 
    /// </summary>
    public Egg() 
    {
        // Set a timer to go off in 20 days 
        // (ms = 20 days * 24 hours/day * 60 minutes/hour * 60 seconds/minute * 1000 milliseconds/seconds)
        var timer = new System.Timers.Timer(20 * 24 * 60 * 60 * 1000);
        timer.Elapsed += StartHatching;
    }

    /// <summary>
    /// Handles the end of the incubation period 
    /// by triggering a Hatch event 
    /// </summary>
    private void StartHatching(object source, ElapsedEventArgs e) 
    {
        var chick = new Chick();
        var args = new HatchEventArgs(chick);
        Hatch?.Invoke(this, args);
    }
}

Events as Messages

It might be becoming clear that in many ways, events are another form of message passing, much like methods are. In fact, they are processed much the same way: the Invoke() method of the event calls each attached event handler in turn.

Tip

Regular event invocation in C# is synchronous, just as is method calling - invoking an event passes execution to the event handlers one at a time the same way calling a method hands program execution to the method. Once they have finished executing, program execution continues back in the code that invoked the event. Let’s see a practical example based on our discussion of the Hatch event. If we were to give our chick a name:

private void StartHatching(object source, ElapsedEventArgs e) 
{
    var chick = new Chick();
    var args = new HatchEventArgs(chick);
    Hatch?.Invoke(this, args);
    chick.Name = "Cluckzilla";
}

And in our event handler, we tried to print that name:

private void OnHatch(object sender, HatchEventArgs e)
{
    Console.WriteLine($"Welcome to the world, {e.Chick.Name}!");
}

The OnHatch event handler would be triggered by the Hatch?.Invoke() line before the name was set, so the Chick.Name property would be null! We would need to move the name assignment to before the Invoke() call for it to be available in any attached event handlers.

The EventArgs define what the message contains, the sender specifies which object is sending the event, and the objects defining the event handlers are the ones receiving it.

That last bit is the biggest difference between using an event to pass a message and using a method to pass the same message. With a method, we always have one object sending and one object receiving. In contrast, an event can have no objects receiving, one object receiving, or many objects receiving.

An event is therefore more flexible and open-ended. We can determine which object(s) should receive the message at any point - even at runtime. In contrast, with a method we need to know what object we are sending the message to (i.e invoking the method of) as we write the code to do so.

Let’s look at a concrete example of where this can come into play.

PropertyChanged

We often have classes which encapsulate data we might need to look at. For example, we might have a “Smart” dog dish, which keeps track of the amount of food it contains in ounces. So it exposes a Weight property.

Now let’s assume we have a few possible add-on products that can be combined with that smart bowl. One is a “dinner bell”, which makes noises when the bowl is filled (ostensibly to attract the dog, but mostly just to annoy your neighbors). Another is a wireless device that sends texts to your phone to let you know when the bowl is empty.

How can the software running on these devices determine when the bowl is empty or full? One possibility would be to check the bowl’s weight constantly, or at a set interval. We call this strategy polling:

/// <summary>
/// The run button for the Dinner Bell add-on
/// </summary>
public void Run()
{
    while(bowl.Weight != 0) {
        // Do nothing
    }
    // If we reach here, the bowl is empty!
    sendEmptyText();
}

The problem with this approach is that it means our program is running full-bore all the time. If this is a battery-operated device, those batteries will drain quickly. It might be better if we let the smart bowl notify the Dinner Bell, but if we did this using methods, the Smart Bowl would need a reference to that dinner bell… and any other accessories we plug in.

This was a common problem in GUI design - sometimes we need to know when a property changes because we are displaying that property’s value in the GUI, possibly in multiple places. But if that property is not part of a GUI display, we may not care when it changes.

The INotifyPropertyChanged Interface

The standard answer to this dilemma in .NET is the INotifyPropertyChanged interface - an interface defined in the System.ComponentModel namespace that requires you to implement a single event PropertyChanged on the class that is changing. You can define this event as:

public event PropertyChangedEventHandler? PropertyChanged;

This sets up the PropertyChanged event handler on your class. Let’s first look at writing event listeners to take advantage of this event.

PropertyChanged Event Listeners

In our example, we would do this with the smart dog bowl, and add listeners to the dinner bell and empty notification tools. The PropertyChangedEventArgs includes the name of the property that is changing (PropertyName) - so we can check that 1) the property changing is the weight, and 2) that the weight meets our criteria, i.e.:

/// <summary>
/// A SmartBowl accessory that sends text notifications when the SmartBowl is empty
/// </summary>
public class EmptyTexter
{
    /// <summary>
    /// Constructs a new EmptyTexter object 
    /// </summary>
    /// <param Name="bowl">The SmartBowl to listen to</param>
    public EmptyTexter(SmartBowl bowl)
    {
        bowl.PropertyChanged += onBowlPropertyChanged;
    }

    /// <summary>
    /// Responds to changes in the Weight property of the bowl
    /// </summary>
    /// <param Name="Sender">The bowl sending the event</param>
    /// <param Name="e">The event arguments (specifying which property changed)</param>
    private void onBowlPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        // Only move forward if the property changing is the weight
        if (e.PropertyName == "Weight")
        {
            if (sender is SmartBowl)
            {
                var bowl = sender as SmartBowl;
                if (bowl.Weight == 0) textBowlIsEmpty();
            }
        }
    }

    /// <summary>
    /// Helper method to notify bowl is empty
    /// </summary>
    private void textBowlIsEmpty()
    {
        // TODO: Implement texting
    }
}

Note that in our event listener, we need to check the specific property that is changing is the one we care about - the Weight. We also cast the source of the event back into a SmartBowl, but only after checking the cast is possible. Alternatively, we could have stored the SmartBowl instance in a class variable rather than casting.

Or, we can use the new is type pattern expression:

if(sender is SmartBowl bowl) {
    // Inside this body, bowl is the sender cast as a SmartBowl
    // TODO: logic goes here
}

This is syntactic sugar for:

if(sender is SmartBowl) {
    var bowl = sender as SmartBowl;
    // TODO: logic goes here
}

Notice how the is type pattern expression merges the if test and variable assignment?

Also, notice that the only extra information supplied by our PropertyChangedEventArgs is the name of the property - not its prior value, or any other info. This helps keep the event lightweight, but it does mean if we need to keep track of prior values, we must implement that ourselves, as we do in the DinnerBell implementation:

/// <summary>
/// A SmartBowl accessory that makes noise when the bowl is filled
/// </summary>
public class DinnerBell
{
    /// <summary>
    /// Caches the previous weight measurement 
    /// </summary>
    private double lastWeight;

    /// <summary>
    /// Constructs a new DinnerBell object 
    /// </summary>
    /// <param Name="bowl">The SmartBowl to listen to</param>
    public DinnerBell(SmartBowl bowl)
    {
        lastWeight = bowl.Weight;
        bowl.PropertyChanged += onBowlPropertyChanged;
    }

    /// <summary>
    /// Responds to changes in the Weight property of the bowl
    /// </summary>
    /// <param Name="Sender">The bowl sending the event</param>
    /// <param Name="e">The event arguments (specifying which property changed)</param>
    private void onBowlPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        // Only move forward if the property changing is the weight
        if (e.PropertyName == "Weight")
        {
            // Cast the sender to a smart bowl using the is type expression
            if (sender is SmartBowl bowl)
            {
                // Ring the dinner bell if the bowl is now heavier 
                // (i.e. food has been added)
                if (bowl.Weight > lastWeight) ringTheBell();
                // Cache the new weight
                lastWeight = bowl.Weight;
            }
        }
    }

    /// <summary>
    /// Helper method to make noise 
    /// </summary>
    private void ringTheBell()
    {
        // TODO: Implement noisemaking
    }
}

PropertyChanged Event Declaration

For the event listeners to work as expected, we need to implement the PropertyChanged event in our SmartBowl class with:

public event PropertyChangedEventHandler? PropertyChanged;

Which makes it available for the event handlers to attach to. But this is only part of the process, we also need to invoke this event when it happens. This is done with the Invoke(object sender, EventArgs e) method defined for every event handler. It takes two parameters, an object which is the source of the event, and the EventArgs defining the event. The specific kind of EventArgs corresponds to the event declaration - in our case, PropertyChangedEventArgs.

Let’s start with a straightforward example. Assume we have a Name property in the SmartBowl that is a customizable string, allowing us to identify the bowl, i.e. “Water” or “Food”. When we change it, we need to invoke the PropertyChanged event, i.e.:

private string name = "Bowl";
public string Name {
    get {return name;}
    set 
    {
        name = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
    }
}

Notice how we use the setter for Name to invoke the PropertyChanged event handler, after the change to the property has been made. This invocation needs to be done after the change, or the responding event listener may grab the old value (remember, event listeners are triggered synchronously).

Also note that we use the null-conditional operator ?. to avoid calling the Invoke() method if PropertyChanged is null (which is the case if no event listeners have been assigned).

Now let’s tackle a more complex example. Since our SmartBowl uses a sensor to measure the weight of its contents, we might be able to read the sensor data - probably through a driver or a class representing the sensor. Rather than doing this constantly, let’s set a polling interval of 1 minute:

/// <summary>
/// A class representing a "smart" dog bowl.
/// </summary>
public class SmartBowl : INotifyPropertyChanged 
{
    /// <summary>
    /// Event triggered when a property changes
    /// </summary>
    public event PropertyChangedEventHandler? PropertyChanged;

    /// <summary>
    /// The weight sensor installed in the bowl
    /// </summary>
    Sensor sensor;

    private string name = "Bowl";
    /// <summary>
    /// The name of this bowl
    /// </summary>
    public string Name
    {
        get { return name; }
        set
        {
            name = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
        }
    }

    private double weight;
    /// <summary>
    /// The weight of the bowl contents, measured in ounces
    /// </summary>
    public double Weight
    {
        get { return weight; }
        set
        {
            // We only want to treat the weight as changing 
            // if the change is more than a 16th of an ounce
            if (Math.Abs(weight - value) > 1 / 16)
            {
                weight = value;
                // Notify of the property changing
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Weight"));
            }
        }
    }

    /// <summary>
    /// Constructs a new SmartBowl 
    /// </summary>
    /// <param Name="sensor">the weight sensor</param>
    public SmartBowl(Sensor sensor)
    {
        this.sensor = sensor;
        // Set the initial weight
        weight = sensor.Value;
        // Set a timer to go off in 1 minute  
        // (ms = 60 seconds/minute * 1000 milliseconds/seconds)
        var timer = new System.Timers.Timer(60 * 1000);
        // Set the timer to reset when it goes off 
        timer.AutoReset = true;
        // Trigger a sensor read each time the timer elapses
        timer.Elapsed += readSensor;
    }

    /// <summary>
    /// Handles the elapsing of the polling timer by updating the weight
    /// </summary>
    private void readSensor(object Sender, System.Timers.ElapsedEventArgs e)
    {
        this.Weight = sensor.Value;
    }

}

Notice in this code, we use the setter of the Weight property to trigger the PropertyChanged event. Because we’re dealing with a real-world sensor that may have slight variations in the readings, we also only treat changes of more than 1/16th of an ounce as significant enough to change the property.

Warning

With the INotifyPropertyChanged interface, the only aspect Visual Studio checks is that the PropertyChanged event is declared. There is no built-in check that the programmer is using it as expected. Therefore it is upon you, the programmer, to ensure that you meet the expectation that comes with implementing this interface: that any public or protected property that changes will invoke the PropertyChanged event.

Testing the PropertyChanged Event

Finally, we should write unit tests to confirm that our PropertyChanged event works as expected:

public class SmartBowlUnitTests {

    /// <summary>
    /// A mock sensor that increases its reading by one ounce 
    /// every time its Value property is invoked.
    /// </summary>
    class MockChangingWeightSensor : Sensor
    {
        double value = 0.0;

        public double Value {
            get {
                value += 1;
                return value;
            }
        }
    }

    [Fact]
    public void NameChangeShouldTriggerPropertyChanged() 
    {
        var bowl = new SmartBowl(new MockChangingWeightSensor());
        Assert.PropertyChanged(bowl, "Name", () => {
            bowl.Name = "New Name";
        });
    }

    [Fact]
    public void WeightChangeShouldTriggerPropertyChanged()
    {
        var bowl = new SmartBowl(new MockChangingWeightSensor());
        Assert.PropertyChangedAsync(bowl, "Weight", () => {
            return Task.Delay(2 * 60 * 1000);
        });
    }
}

The PropertyChanged interface is so common in C# programming that we have two assertions dealing with it. The first we use to test the Name property:

[Fact]
public void NameChangeShouldTriggerPropertyChanged() 
{
    var bowl = new SmartBowl(new MockChangingWeightSensor());
    Assert.PropertyChanged(bowl, "Name", () => {
        bowl.Name = "New Name";
    });
}

Notice that Assert.PropertyChanged(@object obj, string propertyName, Action action) takes three arguments - first the object with the property that should be changing, second the name of the property we expect to change, and third an action that should trigger the event. In this case, we change the name property.

The second is a bit more involved, as we have an event that happens based on a timer. To test it therefore, we have to wait for the timer to have had an opportunity to trigger. We do this with an asynchronous action, so we use the Assert.PropertyChangedAsync(@object obj, string propertyName, Func<Task> action). The first two arguments are the same, but the last one is a Func (a function) that returns an asynchronous Task object. The simplest one to use here is Task.Delay, which delays for the supplied period of time (in our case, two minutes). Since our property should change on one-minute intervals, we’ll know if there was a problem if it doesn’t change after two minutes.

Inheritance And Events

Considering that C# was developed as an object-oriented language from the ground up, you would expect that events would be inheritable just like properties, fields, and methods. Unfortunately this is not the case. Remember, the C# language is compiled into intermediate language to run on the .NET Runtime, and this Runtime proceeded C# (it is also used to compile Visual Basic), and the way events are implemented in intermediate language does not lend itself to inheritance patterns.

This has some important implications for writing C# events:

  • You cannot invoke events defined in a base class in a derived class
  • The virtual and override keywords used with events do not actually create an overridden event - you instead end up with two separate implementations.

The standard way programmers have adapted to this issue is to:

  1. Define the event normally in the base class
  2. Add a protected helper method to that base class that will invoke the event, taking whatever parameters are needed
  3. Calling that helper method from derived classes.

For example, the PropertyChanged event we discussed previously is often invoked from a helper method named OnPropertyChanged() that is defined like this:

protected virtual void OnPropertyChanged(string propertyName)
{
    this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

In derived classes, you can indicate a property is changing by calling this event, and passing in the property name, i.e.:

private object _tag = null;

/// <summary>
/// An object to represent whatever you need 
/// </summary>
public object Tag {
  get => _tag;
  set 
  {
    if(value != _tag) 
    {
      _tag = value;
      OnPropertyChanged(nameof(this.Tag));
    }
  }
}

Note the call to OnPropertyChanged() - this will trigger the PropertyChanged event handler on the base class.

Tip

You might have noticed the use of nameof(this.Tag) in the example code above. The nameof expression returns the name of a property as a string. This approach is preferred over just writing the string, as it makes it less likely a typo will result in your code not working as expected.

Routed Events

While events exist in Windows Forms, Windows Presentation Foundation adds a twist with their concept of routed events. Routed events are similar to regular C# events, but provide additional functionality. One of the most important of these is the ability of the routed event to “bubble” up the elements tree. Essentially, the event will be passed up each successive WPF element until one chooses to “handle” it, or the top of the tree is reached (in which case the event is ignored).

Consider a Click event handler for a button. In Windows Forms, we have to attach our listener directly to the button, i.e:

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            IncrementButton.Click += HandleClick;
        }

        private void HandleClick(object sender, EventArgs e)
        {
            // TODO: Handle our click
        }
    }
}

With WPF we can also attach an event listener directly to the button, but we can also attach an event listener to an ancestor of the button (a component further up the element tree). The click event will “bubble” up the element tree, and each successive parent will have the opportunity to handle it. I.e. we can define a button in the ChildControl:

<UserControl x:Class="WpfApp1.ChildControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfApp1"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Button Name="IncrementButton">Count</Button>
    </Grid>
</UserControl>

And add an instance of ChildControl to our MainWindow:

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid Button.Click="HandleClick">
        <local:ChildControl/>        
    </Grid>
</Window>

Note that in our <Grid> we attached a Button.Click handler? The attached listener, HandleClick, will be invoked for all Click events arising from Buttons that are nested under the <Grid> in the elements tree. We can then write this event handler in the codebehind of our MainWindow:

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void HandleClick(Object sender, RoutedEventArgs e)
        {
            if(e.OriginalSource is Button button && button.Name == "IncrementButton")
            {
                // TODO: Handle increment;

                e.Handled = true;
            }
        }
    }
}

Note that because this event listener will be triggered for all buttons, we need to make sure it’s a button we care about - so we cast the OriginalSource of the event to be a button and check its Name property. We use the RoutedEventArgs.OriginalSource because the sender won’t necessarily be the specific control the event originated in - in this case it actually is the Grid containing the button. Also, note that we mark e.Handled as true. This tells WPF it can stop “bubbling” the event, as we have taken care of it.

We’ll cover routed events in more detail in the upcoming Dependency Objects chapter, but for now you need to know that the GUI events you know from Windows Forms (Click, Select, Focus, Blur), are all routed events in WPF, and therefore take a RoutedEventArgs object instead of the event arguments you may be used to.

CollectionChanged

The PropertyChanged event notifies us when a property of an object changes, which covers most of our GUI notification needs. However, there are some concepts that aren’t covered by it - specifically, when an item is added or removed from a collection. We use a different event, NotifyCollectionChanged to convey when this occurs.

The INotifyCollectionChanged Interface

The INotifyCollectionChanged interface defined in the System.Collections.Specialized namespace indicates the collection implements the NotifyCollectionChangedEventHandler, i.e.:

public event NotifyCollectionChangedEventHandler? NotifyCollectionChanged;

And, as you would expect, this event is triggered any time the collection’s contents change, much like the PropertyChanged event we discussed earlier was triggered when a property changed. However, the NotifyCollectionChangedEventArgs provides a lot more information than we saw with the PropertyChangedEventArgs,as you can see in the UML diagram below:

UML or NotifyCollectionChangedEventArgs and PropertyChangedEventArgs UML or NotifyCollectionChangedEventArgs and PropertyChangedEventArgs

With PropertyChangedEventArgs we simply provide the name of the property that is changing. But with NotifyCollectionChangedEventArgs, we are describing both what the change is (i.e. an Add, Remove, Replace, Move, or Reset), and what item(s) we affected. So if the action was adding an item, the NotifyCollectionChangedEventArgs will let us know what item was added to the collection, and possibly at what position it was added at.

When implementing the INotifyCollectionChanged interface, you must supply a NotifyCollectionChangedEventArgs object that describes the change to the collection. This class has multiple constructors, and you must select the correct one, or your code will cause a runtime error when the event is invoked.

Info

You might be wondering why PropertyChangedEventArgs contains so little information compared to NotifyCollectionChangedEventArgs. Most notably, the old and new values of the property could have been included. I suspect the reason they were not is that there are many times where you don’t actually need to know what the property value is - just that it changed, and you can always retrieve that value once you know you need to.

In contrast, there are situations where a GUI displaying a collection may have hundreds of entries. Identifying exactly which ones have changed means that only those entries need to be modified in the GUI. If we didn’t have that information, we’d have to retrieve the entire collection and re-render it, which can be a very computationally expensive process.

But ultimately, the two interfaces were developed by different teams at different times, which probably accounts for most of the differences.

NotifyCollectionChangedAction

The only property of the NotifyCollectionChangedArgs that will always be populated is the Action property. The type of This property is the NotifyCollectionChangedAction enumeration, and its values (and what they represent) are:

  • NotifyCollectionChangedAction.Add - one or more items were added to the collection
  • NotifyCollectionChangedAction.Move - an item was moved in the collection
  • NotifyCollectionChangedAction.Remove - one or more items were removed from the collection
  • NotifyCollectionChangedAction.Replace - an item was replaced in the collection
  • NotifyCollectionChangedAction.Reset - drastic changes were made to the collection

NotifyCollectionChangedEventArgs Constructors

A second feature you probably noticed from the UML is that there are a lot of constructors for the NotifyCollectionChangedEventArgs. Each represents a different situation, and you must pick the appropriate one.

For example, the NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction) constructor represents a NotifyCollectionChangedAction.Reset change. This indicates the collection’s content changed dramatically, and the best recourse for a GUI is to ask for the full collection again and rebuild the display. You should only use this one-argument constructor for a Reset action.

Warning

In C#, there is no mechanism for limiting a constructor to specific argument values. So you actually can call the above constructor for a different kind of event, i.e.:

new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add);

However, doing so will throw a InvalidArgumentException when the code is actually executed.

In general, if you are adding or removing an object, you need to provide the object to the constructor. If you are adding or removing multiple objects, you will need to provide an IList of the affected objects. And you may also need to provide the object’s index in the collection. You can read more about the available constructors and their uses in the Microsoft Documentation.

Testing Generic Events

In the testing chapter, we introduced the XUnit assertion for testing events, Assert.Raises<T>. Let’s imagine a doorbell class that raises a Ring event when it is pressed, with information about the door, which can be used to do things like ring a physical bell, or send a text notification:

/// <summary>
/// A class representing details of a ring event 
/// </summary>
public class RingEventArgs : EventArgs 
{
    private string _door;

    /// <summary>
    /// The identity of the door for which the doorbell was activated
    public string Door => _door;
    
    /// <summary>
    /// Constructs a new RingEventArgs 
    /// </summary>
    public RingEventArgs(string door) 
    {
      _door = door;
    }
}

/// <summary>
/// A class representing a doorbell
/// </summary>
public class Doorbell
{
    /// <summary>
    /// An event triggered when the doorbell rings 
    /// </summary>
    public event EventHandler<RingEventArgs> Ring;

    /// <summary>
    /// The name of the door where this doorbell is mounted 
    /// </summary>
    public string Identifier {get; set;}

    /// <summary>
    /// Handles the push of a doorbell
    /// by triggering a Ring event 
    /// </summary>
    public void Push() 
    {
        Ring?.Invoke(this, new RingEventArgs(Identifier));
    }
}

To test this doorbell, we’d want to make sure that the Ring event is invoked when the Push() method is called. The Assert.Raises<T> does exactly this:

[Fact]
public void PressingDoorbellShouldRaiseRingEvent
Doorbell db = new Doorbell();
Assert.Raises<RingEventArgs>(
  handler => db.Ring += handler,
  handler => db.Ring -= handler, 
  () => {
    db.Push();
  });

This code may be a bit confusing at first, so let’s step through it. The <T> is the type of event arguments we expect to receive, in this case, RingEventArgs. The first argument is a lambda expression that attaches an event handler handler (provided by the Assert.Raises method) to our object to test, db. The second argument is a lambda expression that removes the event handler handler. The third is an action (also written as a lambda expression) that should trigger the event if the code we are testing is correct.

This approach allows us to test events declared with the generic EventHandler<T>, which is one of the reasons we prefer it. It will not work with custom event handlers though; for those we’ll need a different approach.

Testing Custom Events

In the previous section, we discussed using XUnit’s Assert.Raises<T> to test generic events (events declared with the EventHandler<T> generic). However, this approach does not work with non-generic events, like PropertyChanged and CollectionChanged. That is why XUnit provides an Assert.PropertyChanged() method. Unfortunately, it does not offer a corresponding test for CollectionChanged. So to test for this expectation we will need to write our own assertions.

To do that, we need to understand how assertions in the XUnit framework work. Essentially, they test the truthfulness of what is being asserted (i.e. two values are equal, a collection contains an item, etc.). If the assertion is not true, then the code raises an exception - specifically, a XunitException or a class derived from it. This class provides a UserMessage (the message you get when the test fails) and a StackTrace (the lines describing where the error was thrown). With this in mind, we can write our own assertion method. Let’s start with a simple example that asserts the value of a string is “Hello World”:

public static class MyAssert
{
  public class HelloWorldAssertionException: XunitException 
  {
      public HelloWorldAssertionException(string actual) : base($"Expected \"Hello World\" but instead saw \"{actual}\"") {}
  }

  public static void HelloWorld(string phrase)
  {
    if(phrase != "Hello World") throw new HelloWorldAssertionException(phrase);
  }
}

Note that we use the base keyword to execute the XunitException constructor as part of the HelloWorldAssertionException, and pass along the string parameter actual. Then the body of the XunitException constructor does all the work of setting values, so the body of our constructor is empty.

Now we can use this assertion in our own tests:

[Theory]
[InlineData("Hello World")]
[InlineData("Hello Bob")]
public void ShouldBeHelloWorld(string phrase)
{
  MyAssert.HelloWorld(phrase);
}

The first InlineData will pass, and the second will fail with the report Expected "Hello World" but instead saw "Hello Bob".

This was of course, a silly example, but it shows the basic concepts. We would probably never use this in our own work, as Assert.Equal() can do the same thing. Now let’s look at a more complex example that we would use.

Assertions for CollectionChanged

As we discussed previously, the CollectionChanged event cannot be tested with the Xunit Assert.Throws. So this is a great candidate for custom assertions. To be thorough, we should test all the possible actions (and we would do this if expanding the Xunit library). But for how we plan to use it, we really only need two actions covered - adding and removing items one at a time from the collection. Let’s start with our exception definitions:

public static class MyAssert
{
  public class NotifyCollectionChangedNotTriggeredException: XunitException 
  {
      public NotifyCollectionChangedNotTriggeredException(NotifyCollectionChangedAction expectedAction) : base($"Expected a NotifyCollectionChanged event with an action of {expectedAction} to be invoked, but saw none.") {}
  }

  public class NotifyCollectionChangedWrongActionException: XunitException 
  {
      public NotifyCollectionChangedWrongActionException(NotifyCollectionChangedAction expectedAction, NotifyCollectionChangedAction actualAction) : base($"Expected a NotifyCollectionChanged event with an action of {expectedAction} to be invoked, but saw {actualAction}") {}
  }

  public class NotifyCollectionChangedAddException: XunitException 
  {
      public NotifyCollectionChangedAddException(object expected, object actual) : base($"Expected a NotifyCollectionChanged event with an action of Add and object {expected} but instead saw {actual}") {}
  }

  public class NotifyCollectionChangedRemoveException : XunitException
  {
    public NotifyCollectionChangedRemoveException(object expectedItem, int expectedIndex, object actualItem, int actualIndex) : base($"Expected a NotifyCollectionChanged event with an action of Remove and object {expectedItem} at index {expectedIndex} but instead saw {actualItem} at index  {actualIndex}") {}
  }
}

We have four different exceptions, each with a very specific message conveying what the failure was due to - no event being triggered, an event with the wrong action being triggered, or an event with the wrong information being triggered. We could also handle this with one exception class using multiple constructors (much like the NotifyCollectionChangedEventArgs does).

Then we need to write our assertions, which are more involved than our previous example as 1) the event uses a generic type, so our assertion also must be a generic, and 2) we need to handle an event - so we need to attach an event handler, and trigger code that should make that event occur. Let’s start with defining the signature of the Add method:

public static class MyAssert {

  public static void NotifyCollectionChangedAdd<T>(INotifyCollectionChanged collection, T item, Action testCode) 
  {
    // Assertion tests here.
  }
}

We use the generic type T to allow our assertion to be used with any kind of collection - and the second parameter item is also this type. That is the object we are trying to add to the collection. Finally, the Action is the code the test will execute that would, in theory, add item to collection. Let’s flesh out the method body now:

public static class MyAssert
{
  public static void NotifyCollectionChangedAdd<T>(INotifyCollectionChanged collection, T newItem, Action testCode)
  {
    // A flag to indicate if the event triggered successfully
    bool notifySucceeded = false;

    // An event handler to attach to the INotifyCollectionChanged and be 
    // notified when the Add event occurs.
    NotifyCollectionChangedEventHandler handler = (sender, args) =>
    {
      // Make sure the event is an Add event
      if (args.Action != NotifyCollectionChangedAction.Add)
      {
        throw new NotifyCollectionChangedWrongActionException(NotifyCollectionChangedAction.Add, args.Action);
      }

      // Make sure we added just one item
      if (args.NewItems?.Count != 1)
      {
        // We'll use the collection of added items as the second argument
        throw new NotifyCollectionChangedAddException(newItem, args.NewItems);
      }

      // Make sure the added item is what we expected
      if (!args.NewItems[0].Equals(newItem))
      {
        // Here we only have one item in the changed collection, so we'll report it directly
        throw new NotifyCollectionChangedAddException(newItem, args.NewItems[0]);
      } 

      // If we reach this point, the NotifyCollectionChanged event was triggered successfully
      // and contains the correct item! We'll set the flag to true so we know.
      notifySucceeded = true;
    };

    // Now we connect the event handler 
    collection.CollectionChanged += handler;

    // And attempt to trigger the event by running the actionCode
    // We place this in a try/catch to be able to utilize the finally 
    // clause, but don't actually catch any exceptions
    try
    {
      testCode();
      // After this code has been run, our handler should have 
      // triggered, and if all went well, the notifySucceed is true
      if (!notifySucceeded)
      {
        // If notifySucceed is false, the event was not triggered
        // We throw an exception denoting that
        throw new NotifyCollectionChangedNotTriggeredException(NotifyCollectionChangedAction.Add);
      }
    }
    // We don't actually want to catch an exception - we want it to 
    // bubble up and be reported as a failing test.  So we don't 
    // have a catch () {} clause to this try/catch.
    finally
    {
      // However, we *do* want to remove the event handler. We do 
      // this in a finally block so it will happen even if we do 
      // have an exception occur. 
      collection.CollectionChanged -= handler;
    }
  }
}

Now we can test this in our code. For example, if we had a collection of ShoppingList objects named shoppingLists that implemented INotifyCollectionChanged, we could test adding a new shopping list, shoppingList, to it with:

var newList = new ShoppingList();
MyAssert.NotifyCollectionChangedAdd(shoppingLists, newList, () => {
  shoppingLists.Add(newList);
});

Note that we didn’t need to explicitly state T in this case is ShoppingList - the compiler infers this from the arguments supplied to the method.

Our assertion method handles adding a single item. We can use method overloading providing another method of the same name with different arguments to handle when multiple items are added. For that case, the signature might look like:

public static void NotifyCollectionChangedAdd<T>(INotifyCollectionChanged collection, ICollection<T> items, Action testCode) 
{
  // Assertion tests here.
}

We’d also want to write assertion methods for handling removing items, and any other actions we might need to test. I’ll leave these as exercises for the reader.

Summary

In this chapter we discussed the Windows Message Loop and Queue, and how messages provided to this loop are transformed into C# events by the Application class. We examined C#’s approach to events, which is a more flexible form of message passing. We learned how to write both C# event listeners and handlers, and how to invoke event handlers with Invoke(). We also learned how to create and trigger our own custom events with custom event arguments.

In addition, we learned about the INotifyPropertyChanged interface, and how it can be used to notify listeners that one of an Object’s properties have changed through a NotifyPropertyChanged event handler. We also saw how to test our implementations of INotifyPropertyChanged using xUnit. In our next chapter on Data Binding, we will see how this interface is used by Windows Presentation Foundation to update user interfaces automatically when bound data objects change.

We saw that Windows Presentation Foundation also uses Routed Events, which can bubble up the elements tree and be handled by any ancestor element. This approach replaces many of the familiar UI events from Windows Forms. We’ll take a deeper look at this approach, including defining our own Routed Events and alternative behaviors like “tunnelling” down the elements tree in the upcoming Dependency Objects chapter.

Finally, we discussed testing strategies for testing if our events work as expected. We revisited the Xunit Assert.Raises<t>() and discussed how it works with generic event handlers. We also saw how for non-generic event handlers, we may have to author our own assertions, and even created one for the CollectionChanged event.

Chapter 4

Data Binding

Linking GUIs to Data

Subsections of Data Binding

Introduction

The term data binding refers to binding two objects together programmatically so that one has access to the data of the other. We most commonly see this with user interfaces and data objects - the user interface exposes some of the state of the data object to the user. As with many programming tasks, there are a number of ways to approach data binding. The Windows Presentation Foundation in C# has adopted an event and component-based approach that we will explore in this chapter.

Key Terms

Some key terms to learn in this chapter are:

  • Data Binding
  • One-way data binding
  • Two-way data binding
  • Data Context

Key Skills

Some key skills you need to develop in this chapter are:

  • Binding data objects to UI Components
  • Implementing (realizing) the INotifyPropertyChanged interface
  • Invoking event handlers
  • Using the DataContext property
  • Casting objects to a specific Type without triggering errors

Data Binding

Data binding is a technique for synchronizing data between a provider and consumer, so that any time the data changes, the change is reflected in the bound elements. This strategy is commonly employed in graphical user interfaces (GUIs) to bind controls to data objects. Both Windows Forms and Windows Presentation Foundation employ data binding.

In WPF, the data object is essentially a normal C# object, which represents some data we want to display in a control. However, this object must implement the INotifyPropertyChanged interface in order for changes in the data object to be automatically applied to the WPF control it is bound to. Implementing this interface comes with two requirements. First, the class will define a PropertyChanged event:

public event PropertyChangedEventHandler? PropertyChanged;

And second, it will invoke that PropertyChanged event handler whenever one of its properties changes:

PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ThePropertyName"));

The string provided to the PropertyChangedEventArgs constructor must match the property name exactly, including capitalization.

For example, this simple person implementation is ready to serve as a data object:

/// <summary>
/// A class representing a person 
/// </summary>
public class Person : INotifyPropertyChanged
{
    /// <summary>
    /// An event triggered when a property changes 
    /// </summary>
    public event PropertyChangedEventHandler? PropertyChanged;

    private string firstName = "";
    /// <summary>
    /// This person's first name 
    /// </summary>
    public string FirstName
    {
        get { return firstName; }
        set
        {
            firstName = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("FirstName"));
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("FullName"));
        }
    }

    private string lastName = "";
    /// <summary>
    /// This person's last name 
    /// </summary>
    public string LastName
    {
        get { return lastName; }
        set
        {
            lastName = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("LastName"));
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("FullName"));
        }
    }

    /// <summary>
    /// This persons' full name 
    /// </summary>
    public string FullName
    {
        get { return $"{firstName} {lastName}"; }
    }

    /// <summary>
    /// Constructs a new person 
    /// </summary>
    /// <param Name="first">The person's first name</param>
    /// <param Name="last">The person's last name</param>
    public Person(string first, string last)
    {
        this.firstName = first;
        this.lastName = last;
    }
}

There are several details to note here. As the FirstName and LastName properties have setters, we must invoke the PropertyChanged event within them. Because of this extra logic, we can no longer use auto-property syntax. Similarly, as the value of FullName is derived from these properties, we must also notify that "FullName" changes when one of FirstName or LastName changes.

To accomplish the binding in XAML, we use a syntax similar to that we used for static resources. For example, to bind a <TextBlock> element to the FullName property, we would use:

<TextBlock Text="{Binding Path=FullName}" />

Just as with our static resource, we wrap the entire value in curly braces ({}), and declare a Binding. The Path in the binding specifies the property we want to bind to - in this case, FullName. This is considered a one-way binding, as the TextBlock element only displays text - it is not editable. The corresponding control for editing a textual property is the <TextBox>. A two-way binding is declared the same way i.e.:

<TextBox Text="{Binding Path=FirstName}" />

However, we cannot bind a read-only property (one that has no setter) to an editable control - only those with both accessible getters and setters. The XAML for a complete control for editing a person might look something like:

<UserControl x:Class="DataBindingExample.PersonControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"     
             xmlns:local="clr-namespace:DataBindingExample"
             xmlns:system="clr-namespace:System;assembly=mscorlib"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="400">    
    <StackPanel>
        <TextBlock Text="{Binding Path=FullName}"/>
        <Label>First</Label>
        <TextBox Text="{Binding Path=FirstName}"/>
        <Label>Last</Label>
        <TextBox Text="{Binding Path=LastName}"/>
    </StackPanel>
</UserControl>

We also need to set the DataContext property of the control. This property holds the specific data object whose properties are bound in the control. For example, we could pass a Person object into the PersonControl’s constructor and set it as the DataContext in the codebehind:

namespace DataBindingExample
{
    /// <summary>
    /// Interaction logic for PersonControl.xaml
    /// </summary>
    public partial class PersonEntry : UserControl
    {
        /// <summary>
        /// Constructs a new PersonEntrycontrol 
        /// </summary>
        /// <param Name="person">The person object to data bind</param>
        public PersonEntry(Person person)
        {
            InitializeComponent();
            this.DataContext = person;
        }
    }
}

However, this approach means we can no longer declare a <PersonControl> in XAML (as objects declared this way must have a parameterless constructor). An alternative is to bind the DataContext in the codebehind of an ancestor control; for example, a window containing the control:

<Window x:Class="DataContextExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DataContextExample"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <local:PersonEntry x:Name="personEntry"/>
    </Grid>
</Window>
namespace DataContextExample
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {        
        public MainWindow()
        {
            InitializeComponent();
            personControl.DataContext = new Person("Bugs", "Bunny");
        }
    }
}

Finally, the DataContext has a very interesting relationship with the elements tree. If a control in this tree does not have its own DataContext property directly set, it uses the DataContext of the first ancestor where it has been set. I.e. were we to set the DataContext of the window to a person:

namespace DataContextExample
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {        
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new Person("Elmer", "Fudd");
        }
    }
}

And have a PersonElement nested somewhere further down the elements tree:

<Window x:Class="DataBindingExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DataBindingExample"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Border>
            <local:PersonEntry/>
        </Border>
    </Grid>
</Window>

The bound person (Elmer Fudd)’s information would be displayed in the <PersonEntry>!

The Binding Class

In Windows Presentation Foundation, data binding is accomplished by a binding object that sits between the binding target (the control) and the binding source (the data object):

The WPF Data Binding implementation The WPF Data Binding implementation

It is this Binding object that we are defining the properties of in the XAML attribute with "{Binding}". Hence, Path is a property defined on this binding.

As we mentioned before, bindings can be OneWay or TwoWay based on the direction the data flows. The binding mode is specified by the Binding object’s Mode property, which can also be set in XAML. There are actually two additional options. The first is a OneWayToSource that is basically a reversed one-way binding (the control updates the data object, but the data object does not update the control)

For example, we actually could use a <TextEditor> with a read-only property, if we changed the binding mode:

<TextEditor Text="{Binding Path=FullName Mode=OneWay}" />

Though this might cause your user confusion because they would seem to be able to change the property, but the change would not actually be applied to the bound object. However, if you also set the IsEnabled property to false to prevent the user from making changes:

<TextEditor Text="{Binding Path=FullName Mode=OneWay}" IsEnabled="False" />

The second binding mode is OneTime which initializes the control with the property, but does not apply any subsequent changes. This is similar to the behavior you will see from a data object that does not implement the INotifyPropertyChanged interface, as the Binding object depends on it for notifications that the property has changed.

Generally, you’ll want to use a control meant to be used with the mode you intend to employ - editable controls default to TwoWay and display controls to OneWay.

One other property of the Binding class that’s good to know is the Source property. Normally this is determined by the DataContext of the control, but you can override it in the XAML.

Binding Lists

For list controls, i.e. ListView and ListBox, the appropriate binding is a collection implementing IEnumerable, and we bind it to the ItemsSource property. Let’s say we want to create a directory that displays information for a List<Person>. We might write a custom DirectoryControl like:

<UserControl x:Class="DataBindingExample.DirectoryControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:DataBindingExample"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <ListBox ItemsSource="{Binding}"/>
    </Grid>
</UserControl>

Notice that we didn’t supply a Path with our binding. In this case, we’ll be binding directly to the DataContext, which is a list of People objects drawn from the 1996 classic “Space Jam”, i.e.:

<Window x:Class="DataBindingExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DataBindingExample"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <local:DirectoryControl x:Name="directory"/>
    </Grid>
</Window>
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        ObservableCollection<Person> people = new ObservableCollection<Person>()
        {
            new Person("Bugs", "Bunny", true),
            new Person("Daffy", "Duck", true),
            new Person("Elmer", "Fudd", true),
            new Person("Tazmanian", "Devil", true),
            new Person("Tweety", "Bird", true),
            new Person("Marvin", "Martian", true),
            new Person("Michael", "Jordan"),
            new Person("Charles", "Barkely"),
            new Person("Patrick", "Ewing"),
            new Person("Larry", "Johnson")
        };
        DataContext = people;
    }
}

Instead of a List<Person>, we’ll use an ObservableCollection<Person> which is essentially a list that implements the INotifyPropertyChanged interface.

When we run this code, our results will be:

The ListView in the running application The ListView in the running application

This is because the ListBox (and the ListView) by default are composed of <TextBlock> elements, so each Person in the list is being bound to a <TextBlock>’s Text property. This invokes the ToString() method on the Person object, hence the DataBindingExample.Person displayed for each entry.

We could, of course, override the ToString() method on person. But we can also overwrite the DataTemplate the list uses to display its contents. Instead of using the default <TextView>, the list will use the DataContext, and the bindings, we supply. For example, we could re-write the DirectoryControl control as:

<UserControl x:Class="DataBindingExample.DirectoryControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:DataBindingExample"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <ListBox ItemsSource="{Binding}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Border BorderBrush="Black" BorderThickness="2">
                        <StackPanel>
                            <TextBlock Text="{Binding Path=FullName}"/>
                            <CheckBox IsChecked="{Binding Path=IsCartoon}" IsEnabled="False">
                                Is a Looney Toon
                            </CheckBox>
                        </StackPanel>
                    </Border>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</UserControl>

And the resulting application would display:

The updated ListView in the running application The updated ListView in the running application

Note that in our DataTemplate, we can bind to properties in the Person object. This works because as the ListBox processes its ItemsSource property, it creates a new instance of its ItemTemplate (in this case, our custom DataTemplate) and assigns the item from the ItemSource to its DataContext.

Using custom DataTemplates for XAML controls is a powerful feature to customize the appearance and behavior of your GUI.

Lists also can interact with other elements through bindings. Let’s refactor our window so that we have a <PersonRegistry> side-by-side with our <PersonControl>:

<Window x:Class="DataBindingExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DataBindingExample"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <local:PersonControl Grid.Column="0" DataContext="{Binding Path=CurrentItem}"/>
        <local:DirectoryControl Grid.Column="1"/>
    </Grid>
</Window>

Note how we bind the <PersonControl>’s DataContext to the CurrentItem of the ObservableCollection<Person>. In our <RegistryControl>’s ListBox, we’ll also set its IsSynchronizedWithCurrentItem property to true:

<UserControl x:Class="DataBindingExample.DirectoryControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:DataBindingExample"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <ListBox ItemsSource="{Binding}" HorizontalContentAlignment="Stretch" IsSynchronizedWithCurrentItem="True">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Border BorderBrush="Black" BorderThickness="1">
                        <StackPanel>
                            <TextBlock Text="{Binding Path=FullName}"/>
                            <CheckBox IsChecked="{Binding Path=IsCartoon, Mode=OneWay}" IsEnabled="False">Cartoon</CheckBox>
                        </StackPanel>
                    </Border>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</UserControl>

With these changes, when we select a person in the <RegistryControl>, their information will appear in the <PersonControl>:

The bound controls The bound controls

Binding Enumerations

Now let’s delve into a more complex data binding examples - binding enumerations. For this discussion, we’ll use a simple enumeration of fruits:

/// <summary>
/// Possible fruits
/// </summary>
public enum Fruit
{
    Apple,
    Orange,
    Peach,
    Pear
}

And add a FavoriteFruit property to our Person class:

private Fruit favoriteFruit;
/// <summary>
/// The person' favorite fruit
/// </summary>
public Fruit FavoriteFruit
{
    get { return favoriteFruit; }
    set
    {
        favoriteFruit = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("FavoriteFruit"));
    }
}

For example, what if we wanted to use a ListView to select an item out of this enumeration? We’d actually need to bind two properties, the ItemSource to get the enumeration values, and the SelectedItem to mark the item being used. To accomplish this binding, we’d need to first make the fruits available for binding by creating a static resource to hold them using an ObjectDataProvider:

<ObjectDataProvider x:Key="fruits" ObjectType="system:Enum" MethodName="GetValues">
    <ObjectDataProvider.MethodParameters>
        <x:Type TypeName="local:Fruit"/>
    </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

The ObjectDataProvider is an object that can be used as a data source for WPF bindings, and wraps around an object and invokes a method to get the data - in this case the Enum class, and its static method GetValues(), which takes one parameter, the Type of the enum we want to pull the values of (provided as the nested element, <x:Type>).

Also, note that because the Enum class is defined in the System namespace, we need to bring it into the XAML with an xml namespace mapped to it, with the attribute xmlns defined on the UserControl, i.e.: xmlns:system="clr-namespace:System;assembly=mscorlib".

Now we can use the fruits key as part of a data source for a listview: <ListView ItemsSource="{Binding Source={StaticResource fruits}}" SelectedItem="{Binding Path=FavoriteFruit}"/>

Notice that we use the Source property of the Binding class to bind the ItemsSource to the enumeration values exposed in the static resource fruits. Then we bind the SelectedItem to the person’s FavoriteFruit property. The entire control would be:

<UserControl x:Class="DataBindingExample.PersonControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"     
             xmlns:local="clr-namespace:DataBindingExample"
             xmlns:system="clr-namespace:System;assembly=mscorlib"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="400">
    <UserControl.Resources>
        <ObjectDataProvider x:Key="fruits" MethodName="GetValues" ObjectType="{x:Type system:Enum}">
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="local:Fruit"/>
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </UserControl.Resources>
    <StackPanel>
        <TextBlock Text="{Binding Path=FullName}"/>
        <Label>First</Label>
        <TextBox Text="{Binding Path=First}"/>
        <Label>Last</Label>
        <TextBox Text="{Binding Path=Last}"/>
        <CheckBox IsChecked="{Binding Path=IsCartoon}">
            Is a Looney Toon
        </CheckBox>
        <ListView ItemsSource="{Binding Source={StaticResource fruits}}" SelectedItem="{Binding Path=FavoriteFruit}"/>
    </StackPanel>
</UserControl>

Binding a ComboBox to an Enum

Binding a <ComboBox> is almost identical to the ListView example; we just swap a ComboBox for a ListView:

<UserControl x:Class="DataBindingExample.PersonControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"     
             xmlns:local="clr-namespace:DataBindingExample"
             xmlns:system="clr-namespace:System;assembly=mscorlib"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="400">
    <UserControl.Resources>
        <ObjectDataProvider x:Key="fruits" MethodName="GetValues" ObjectType="{x:Type system:Enum}">
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="local:Fruit"/>
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </UserControl.Resources>
    <StackPanel>
        <TextBlock Text="{Binding Path=FullName}"/>
        <Label>First</Label>
        <TextBox Text="{Binding Path=First}"/>
        <Label>Last</Label>
        <TextBox Text="{Binding Path=Last}"/>
        <CheckBox IsChecked="{Binding Path=IsCartoon}">
            Is a Looney Toon
        </CheckBox>
        <ComboBox ItemsSource="{Binding Source={StaticResource fruits}}" SelectedItem="{Binding Path=FavoriteFruit}"/>
    </StackPanel>
</UserControl>

Binding RadioButtons to an Enum

Binding a <RadioButton> requires a very different approach, as a radio button exposes an IsChecked boolean property that determines if it is checked, much like a <CheckBox>, but we want it bound to an enumeration property. There are a lot of attempts to do this by creating a custom content converter, but ultimately they all have flaws.

Instead, we can restyle a ListView to look like radio buttons, but still provide the same functionality by adding a <Style> that applies to the ListViewItem contents of the ListView:

<Style TargetType="{x:Type ListViewItem}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate>
                <RadioButton Content="{TemplateBinding ContentPresenter.Content}" IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"/>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

This style can be used in conjunction with a ListView declared as we did above:

<UserControl x:Class="DataBindingExample.PersonControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"     
             xmlns:local="clr-namespace:DataBindingExample"
             xmlns:system="clr-namespace:System;assembly=mscorlib"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="400">
    <UserControl.Resources>
        <ObjectDataProvider x:Key="fruits" MethodName="GetValues" ObjectType="{x:Type system:Enum}">
            <ObjectDataProvider.MethodParameters>
                <x:Type TypeName="local:Fruit"/>
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
        <Style TargetType="{x:Type ListViewItem}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate>
                        <RadioButton Content="{TemplateBinding ContentPresenter.Content}" IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"/>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </UserControl.Resources>
    <StackPanel>
        <TextBlock Text="{Binding Path=FullName}"/>
        <Label>First</Label>
        <TextBox Text="{Binding Path=First}"/>
        <Label>Last</Label>
        <TextBox Text="{Binding Path=Last}"/>
        <CheckBox IsChecked="{Binding Path=IsCartoon}">
            Is a Looney Toon
        </CheckBox>
        <ListView ItemsSource="{Binding Source={StaticResource fruits}}" SelectedItem="{Binding Path=FavoriteFruit}"/>
    </StackPanel>
</UserControl>

Summary

In this chapter we explored the concept of data binding and how it is employed in Windows Presentation Foundation. We saw how bound classes need to implement the INotifyPropertyChanged interface for bound properties to automatically synchronize. We saw how the binding is managed by a Binding class instance, and how we can customize its Path, Mode, and Source properties in XAML to modify the binding behavior. We bound simple controls like <TextBlock> and <CheckBox> and more complex elements like <ListView> and <ListBox>. We also explored how to bind enumerations to controls. And we explored the use of templates like DataTemplate and ControlTemplate to modify WPF controls.

The full example project discussed in this chapter can be found at https://github.com/ksu-cis/DataBindingExample.

Chapter 5

Dependency Objects

The Bedrock of WPF

Subsections of Dependency Objects

Introduction

You’ve now worked with a variety of WPF controls, laid out components using containers, traversed the elements tree, performed data binding,and worked with routed events. Each of these is made possible through the use of several classes: DependencyObject, UIElement, and FrameworkElement, which serve as a base classes for all WPF controls. In this chapter we’ll dig deeper into how these base classes implement dependency properties and routed events.

Key Terms

Some key terms to learn in this chapter are:

  • Dependency Property
  • Routed Event
  • MVVM Pattern

Key Skills

Some key skills you need to develop in this chapter are:

  • Creating custom dependency properties
  • Handling routed events
  • Creating custom routed events
  • Using dependency property callbacks

Dependency Properties

Perhaps the most important aspect of the DependencyObject is its support for hosting dependency properties. While these appear and can be used much like the C# properties we have previously worked with, internally they are managed very differently. Consider when we place a <TextBox> in a <Grid>:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <TextBox Name="textBox" Grid.Column="1" Grid.Row="1"/>
</Grid>

Where do the Column and Row properties come from? They aren’t defined on the TextBox class - you can check the documentation. The answer is they are made available through the dependency property system.

At the heart of this system is a collection of key/value pairs much like the Dictionary. When the XAML code Grid.Column="1" is processed, this key and value are added to the TextBox’s dependency properties collection, and is thereafter accessible by the WPF rendering algorithm.

The DependencyObject exposes these stored values with the GetValue(DependencyProperty) and SetValue(DependencyProperty, value) methods. For example, we can set the Column property to 2 with:

textBox.SetValue(Grid.ColumnProperty, 2);

We can also create new dependency properties on our own custom classes extending the DependencyObject (which is also a base class for all WPF controls). Let’s say we are making a custom control for entering number values on a touch screen, which we’ll call NumberBox. We can extend a UserControl to create a textbox centered between two buttons, one to increase the value, and one to decrease it:

<UserControl x:Class="CustomDependencyObjectExample.NumberBox"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:CustomDependencyObjectExample"
             mc:Ignorable="d" 
             d:DesignHeight="50" d:DesignWidth="200">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition Width="2*"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Button Grid.Column="0">+</Button>
        <TextBox Grid.Column="1" />
        <Button Grid.Column="2">-</Button>
    </Grid>
</UserControl>

Now, let’s assume we want to provide a property Step of type double, which is the amount the number should be incremented when the “+” or “-” button is pressed.

The first step is to register the dependency property by creating a DependencyProperty instance. This will serve as the key to setting and retrieving the dependency property on a dependency object. We register new dependency properties with DependencyProperty.Register(string propertyName, Type propertyType, Type dependencyObjectType). The string is the name of the property, the first type is the type of the property, and the second is the class we want to associate this property with. So our Step property would be registered with:

DependencyProperty.Register(nameof(Step), typeof(double), typeof(NumberBox));

There is an optional fourth property to DependencyProperty.Register() which is a PropertyMetadata. This is used to set the default value of the property. We probably should specify a default step, so let’s add a PropertyMetadata object with a default value of 1:

DependencyProperty.Register(nameof(Step), typeof(double), typeof(NumberBox), new PropertyMetadata(1.0));

The DependencyProperty.Register() method returns a registered DependencyObject to serve as a key for accessing our new property. To make sure we can access this key from other classes, we define it as a field that is public, static, and readonly. The naming convention for DependencyProperties is to name this field by appending “Property” to the name of the property.

Thus, the complete registration, including saving the result to the public static field is:

/// <summary>
/// Identifies the NumberBox.Step XAML attached property
/// </summary>
public static readonly DependencyProperty StepProperty = DependencyProperty.Register(nameof(Step), typeof(double), typeof(NumberBox), new PropertyMetadata(1.0)); 

We also want to declare a traditional property with the name “Step”. But instead of declaring a backing field, we will use the key/value pair stored in our DependencyObject using GetValue() and SetValue():

/// <summary>
/// The amount each increment or decrement operation should change the value by
/// </summary>
public double Step
{
    get { return (double)GetValue(StepProperty); }
    set { SetValue(StepProperty, value); }
}

As dependency property values are stored as an object, we need to cast the value to a the appropriate type when it is returned.

One of the great benefits of dependency properties is that they can be set using XAML. I.e. we could declare an instance of our <NumberBox> and set its Step using an attribute:

<StackPanel>
    <NumberBox Step="3.0"/>
</StackPanel>

Framework Elements

WPF controls are built on the foundation of dependency objects - the DependencyObject is at the bottom of their inheritance chain. But they also add additional functionality on top of that through another common base class, FrameworkElement. The FrameworkElement is involved in the layout algorithm, as well as helping to define the elements tree. Let’s add a second dependency property to our <NumberBox>, a Value property that will represent the value the <NumberBox> currently represents, which will be displayed in the <TextBox>.

We register this dependency property in much the same way as our Step. But instead of supplying the DependencyProperty.Register() method a PropertyMetadata, we’ll instead supply a FrameworkPropertyMetadata, which extends PropertyMetadata to include additional data about how the property interacts with the WPF rendering and layout algorithms. This additional data is in the form of a bitmask defined in FrameworkPropertyMetadataOptions enumeration.

Some of the possible options are:

  • FrameworkPropertyMetadataOptions.AffectsMeasure - changes to the property may affect the size of the control
  • FrameworkPropertyMetadataOptions.AffectsArrange - changes to the property may affect the layout of the control
  • FrameworkPropertyMetadataOptions.AffectsRender - changes to the property may affect the appearance of the control
  • FrameworkPropertyMetadataOptions.BindsTwoWayByDefault - This property uses two-way bindings by default (i.e. the control is an editable control)
  • FrameworkPropertyMetadataOptions.NotDataBindable - This property does not allow data binding

In this case, we want a two-way binding by default, so we’ll include that flag, and also we’ll note that it affects the rendering process. Multiple flags can be combined with a bitwise OR. Constructing our FrameworkPropertyMetadata object would then look like:

new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)

And registering the dependency property would be:

/// <summary>
/// Identifies the NumberBox.Value XAML attached property
/// </summary>
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(nameof(Value), typeof(double), typeof(NumberBox), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

As with the Step, we also want to declare a traditional property with the name “Value”. But instead of declaring a backing field, we will use the key/value pair stored in our DependencyObject using GetValue() and SetValue():

/// <summary>
/// The NumberBox's displayed value
/// </summary>
public double Value {
    get { return (double)GetValue(ValueProperty); }
    set { SetValue(ValueProperty, value); }
}

If we want to display the current value of Value in the textbox of our NumberBox control, we’ll need to bind the <TextBox> element’s Text property. This is accomplished in a similar fashion to the other bindings we’ve done previously, only we need to specify a RelativeSource. This is a source relative to the control in the elements tree. We’ll specify two properties on the RelativeSource: the Mode which we set to FindAncestor to search up the tree, and the AncestorType which we set to our NumberBox. Thus, instead of binding to the DataContext, we’ll bind to the NumberBox the <TextBox> is located within. The full declaration would be:

<TextBox Grid.Column="1" Text="{Binding Path=Value, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:NumberBox}}"/>

Now a two-way binding exists between the Value of the <NumberBox> and the Text value of the textbox. Updating either one will update the other. We’ve in effect made an editable control!

Routed Events

Another aspect of WPF elements are routed events. Just as dependency properties are similar to regular C# properties, but add additional functionality, routed events are similar to regular C# events, but provide additional functionality. One of the most important of these is the ability of the routed event to “bubble” up the elements tree. Essentially, the event will be passed up each successive WPF element until one chooses to “handle” it, or the top of the tree is reached (in which case the event is ignored). This routed event functionality is managed by the UIElement base class, a third base class shared by all WPF elements.

Let’s consider the two buttons we declared in our <NumberBox>. When clicked, these each trigger a Click routed event. We could attach a handler to each button, but it is also possible to instead attach it to any other element up the tree; for example, our <Grid>:

<UserControl x:Class="CustomDependencyObjectExample.NumberBox"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:CustomDependencyObjectExample"
             mc:Ignorable="d"
             d:DesignHeight="50" d:DesignWidth="200">
    <Grid Button.Click="HandleButtonClick">
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition Width="2*"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Button Grid.Column="0" Name="Increment">+</Button>
        <TextBox Grid.Column="1" Text="{Binding Path=Value, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:NumberBox}}"/>
        <Button Grid.Column="2" Name="Decrement">-</Button>
    </Grid>
</UserControl>

We’d need to define HandleButtonClick in our codebehind:

/// <summary>
/// Handles the click of the increment or decrement button
/// </summary>
/// <param name="sender">The button clicked</param>
/// <param name="e">The event arguments</param>
void HandleButtonClick(object sender, RoutedEventArgs e)
{
    if(sender is Button button)
    {
        switch(button.Name)
        {
            case "Increment":
                Value += Step;
                break;
            case "Decrement":
                Value -= Step;
                break;
        }
    }
    e.Handled = true;
}

When either button is clicked, it creates a Button.Click event. As the buttons don’t handle it, the event bubbles to the next element in the elements tree - in this case, the <Grid>. As the <Grid> does attach a Button.Click listener, the event is passed to HandleButtonClick. In this method we use the button’s Name property to decide the correct action to take. Also, note that we set the RoutedEventArgs.Handled property to true. This lets WPF know that we’ve taken care of the event, and it does not need to bubble up any farther (if we didn’t, we could process the event again further up the elements tree).

Much like dependency properties, we can declare our own routed events. These also use a Register() method, but for events this is a static method of the EventHandler class: EventManager.Register(string eventName, RoutingStrategy routing, Type eventHandlerType, Type controlType). The first argument is a string, which is the name of the event, the second is one of the values from the RoutingStrategy enum, the third is the type of event handler, and the fourth is the type of the control it is declared in. This Register() method returns a RoutedEvent that is used as a key when registering event listeners, which we typically store in a public static readonly RoutedEvent field.

The RoutingStrategy options are

  • RoutingStrategy.Bubble - which travels up the elements tree through ancestor nodes
  • RoutingStrategy.Tunnel - which travels down the elements tree through descendant nodes
  • RoutingStrategy.Direct - which can only be handled by the source element

Let’s create an example routed event for our NumberBox. Let’s assume we define two more routed properties MinValue and MaxValue, and that any time we change the value of our NumberBox it must fall within this range, or be clamped to one of those values. To make it easer for UI designers to provide user feedback, we’ll create a NumberBox.ValueClamped event that will trigger in these circumstances. We need to register our new routed event:

/// <summary>
/// Identifies the NumberBox.ValueClamped event
/// </summary>
public static readonly RoutedEvent ValueClampedEvent = EventManager.RegisterRoutedEvent(nameof(ValueClamped), RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(NumberBox));

Also like dependency properties also need to declare a corresponding C# property, routed events need to declare a corresponding C# event:

/// <summary>
/// Event that is triggered when the value of this NumberBox changes
/// </summary>
public event RoutedEventHandler ValueClamped
{
    add { AddHandler(ValueClampedEvent, value); }
    remove { RemoveHandler(ValueClampedEvent, value); }
}

Finally, we would want to raise this event whenever the value is clamped. This can be done with the RaiseEvent(RoutedEventArgs) method defined on the UIElement base class that we inherit in our custom controls. But where should we place this call?

You might think we would do this in the HandleButtonClick() method, and we could, but that misses when a user types a number directly into the textbox, as well as when Value is updated through a two-way binding. Instead, we’ll utilize the callback functionality available in the FrameworkPropertyMetadata for the Value property. Since the dependency property and its metadata are both static, our callback also needs to be declared static:

/// <summary>
/// Callback for the ValueProperty, which clamps the Value to the range
/// defined by MinValue and MaxValue
/// </summary>
/// <param name="sender">The NumberBox whose value is changing</param>
/// <param name="e">The event args</param>
static void HandleValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
    if(e.Property.Name == "Value" && sender is NumberBox box)
    {
        if(box.Value < box.MinValue)
        {
            box.Value = box.MinValue;
            box.RaiseEvent(new RoutedEventArgs(ValueClampedEvent));
        }
        if(box.Value > box.MaxValue)
        {
            box.Value = box.MaxValue;
            box.RaiseEvent(new RoutedEventArgs(ValueClampedEvent));
        }
    }
}

Note that since this method is static, we must get the instance of the NumberBox by casting the sender. We also double-check the property name, though this is not strictly necessary as the method is private and only we should be invoking it from within this class.

Now we need to refactor our Value dependency property registration to use this callback:

/// <summary>
/// Identifies the NumberBox.Value XAML attached property
/// </summary>
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(NumberBox), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, HandleValueChanged));

By adding the callback to the dependency property, we ensure that any time it changes, regardless of the method the change occurs by, we will ensure the value is clamped to the specified range.

There are additional options for dependency property callbacks, including validation callbacks and the ability to coerce values. See the documentation for details.

MVVM Architecture

You have probably noticed that as our use of WPF grows more sophisticated, our controls start getting large, and often filled with complex logic. You are not alone in noticing this trend. Microsoft architects Ken Cooper and Ted Peters also struggled with the idea, and introduced a new software architectural pattern to help alleviate it: Model-View-ViewModel. This approach splits the user interface code into two classes: the View (the XAML + codebehind), and a ViewModel, which applies any logic needed to format the data from the model object into a form more easily bound and consumed by the view.

MVVM Pattern MVVM Pattern

There are several benefits to this pattern:

  1. Complex logic is kept out of the View classes, allowing them to focus on the task of presentation
  2. Presentation logic is kept out of the Model classes, allowing them to focus on the task of data management and allowing them to be easily re-used for other views
  3. Presentation logic is gathered in the ViewModel class, where it can be easily tested

Essentially, this pattern is an application of the Single-Responsibility Principle (that each class in your project should bear a single responsibility).

Summary

In this chapter we examined how dependency properties and routed events are implemented in WPF. The DependencyObject, which serves as a base class for WPF elements, provides a collection of key/value pairs, where the key is a DependencyProperty and the value is the object it is set to. This collection can be accessed through the GetValue() and SetValue() methods, and is also used as a backing store for regular C# properties. We also saw that we can register callbacks on dependency properties to execute logic when the property is changed. The UIElement, which also serves as a base class for WPF elements, provided similar functionality for registering routed event listeners, whose key is RoutedEvent. We saw how these routed events could “bubble” up the elements tree, or “tunnel” down it, and how marking the event Handled property would stop it. Finally, we discussed the MVVM architecture, which works well with WPF applications to keep our code manageable.

We also created an example control using these ideas. The full project can be found here.

Chapter 60

Testing WPF

How do we test this stuff?

Subsections of Testing WPF

Introduction

Now that you’ve learned how to build a WPF application, how do you test that it is working? For that matter, how do you test any GUI-based application? In this chapter, we’ll explore some common techniques used to test GUIs. We’ll also explore the MVVM architecture developed in parallel with WPF to make unit-testing WPF apps easier.

Key Terms

Some key terms to learn in this chapter are:

  • Test Plan
  • Model View ViewModel (MVVM) Architecture

Key Skills

Some key skills you need to develop in this chapter are:

  • Writing and Using Test Plans
  • Structuring a GUI using the MVVM Approach

Testing GUIs

Testing a GUI-based application presents some serious challenges. A GUI has a strong dependence on the environment it is running in - the operating system is ultimately responsible for displaying the GUI components, and this is also influenced by the hardware it runs on. As we noted in our discussion of WPF, screen resolution can vary dramatically. So how our GUI appears on one machine may be completely acceptable, but unusable on another.

For example, I once had an installer that used a fixed-size dialog that was so large, on my laptop the “accept” button was off-screen below the bottom of the screen - and there was no way to click it. This is clearly a problem, but the developer failed to recognize it because on their development machine (with nice large monitors) everything fit! So how do we test a GUI application in this uncertain environment?

One possibility is to fire the application up on as many different hardware platforms as we can, and check that each one performs acceptably. This, of course, requires a lot of different computers, so increasingly we see companies instead turning to virtual machines - a program that emulates the hardware of a different computer, possibly even running a different operating system! In either case, we need a way to go through a series of checks to ensure that on each platform, our application is usable.

How can we ensure rigor in this process? Ideally we’d like to automate it, just as we do with our Unit tests… and while there have been some steps in this direction, the honest truth is we’re just not there yet. Currently, there is no substitute for human eyes - and human judgement - on the problem. But humans are also notorious for losing focus when doing the same thing repeatedly… which is exactly what this kind of testing is. Thus, we develop test plans to help with this process. We’ll take a look at those next.

Testing Plans

A testing plan is simply a step-by-step guide for a human tester to follow when testing software. You may remember that we mentioned them back on our testing chapter’s discussion on manual testing. Indeed, we can use a test plan to test all aspects of software, not just the GUI. However, automated testing is usually cheaper and more effective in many aspects of software design, which is why we prefer it when possible. So what does a GUI application testing plan look like?

It usually consists of a description of the test to perform, broken down into tasks, and populated with annotated screenshots. Here is an example:

  1. Launch the application

  2. Select the “Cowpoke Chili” button from the “Entrees” menu

The Cowpoke Chili Button The Cowpoke Chili Button

The app should switch to a customization screen that looks like this:

The Customize Cowpoke Chili Screen The Customize Cowpoke Chili Screen

There should be a checkbox for “Cheese”, “Sour Cream”, “Green Onions”, and “Tortilla Strips”

InitialTest Item
Cheese
Sour Cream
Green Onion
Tortilla Strips

A Cowpoke Chili entry should appear in the order, with a cost of $6.10

InitialTest Item
Chili Entry in the order
Price of $6.10
  1. Uncheck the checkboxes, and a corresponding “Hold” detail should appear in the order, i.e. un-checking cheese should cause the order to look like:

Unchecked Cheese Unchecked Cheese

InitialTest Item
Cheese checkbox appears and functions
Sour Cream checkbox appears and functions
Green Onion checkbox appears and functions
Tortilla Strips checkbox appears and functions
4. Click the "Menu Item Selection" Button. This should return you to the main menu screen, with the order still containing the details about the Cowpoke Chili:

Return to Main Menu Return to Main Menu

InitialTest Item
Chili Entry in the order
Price of $6.10
with "Hold Cheese"
with "Hold Sour Cream"
with "Hold Green Onion"
with "Hold Tortilla Strips"

If you encountered problems with this test, please describe:

The essential parts of the test plan are clear instructions of what the tester should do, and what they should see, and a mechanism for reporting issues. Note the tables in this testing plan, where the tester can initial next to each “passing” test, as well as the area for describing issues at the bottom. This reporting can either be integrated into the test document, or, it can be a separate form used with the test document (allowing the printed instructional part of the test documents to be reused). Additionally, some test documents are created in spreadsheet software or specialized testing documentation software for ease of collection and processing.

Test plans like this one are then executed by people (often titled “Tester” or “Software Tester”) by opening the application, following the steps outlined in the plan, and documenting the results. This documentation then goes back to the software developers so that they can address any issues found.

Tip

Taking screen shots of your running program is an easy way to quickly generate visuals for your testing documentation.

In Windows, CTRL + SHIFT + ALT + PRINT SCREEN takes a screen shot and copies it to the clipboard (from which you can paste it into a text editor of your choice).

On a Mac, you can take a screenshot with COMMAND + SHIFT + 4. This launches a utility that changes the mouse cursor and allows you to drag a rectangle across a section of the screen. When you release the mouse, a capture will be taken of the area, and saved as a picture to the desktop.

MVVM Architecture

Compared to automated tests, using a testing plan with human testers is both slow and expensive. It should not be surprising then that Microsoft developers sought ways to shift as much of the testing burden for WPF projects to automated tests. Their solution was to develop a new architectural approach known as Model-View-ViewModel.

MVVM Pattern MVVM Pattern

This approach expands upon the usual Model-View relationship in a GUI. A Model class is a class that represents some data, i.e. a Student, and the View is the GUI exposing that object’s data, i.e. a WPF <StudentControl>. Thus, we might have our Student class:

public class Student : INotifyPropertyChanged
{
    /// <summary>Notifies when a property changes</summary>        
    public event PropertyChangedEventHandler? PropertyChanged;

    /// <summary>The student's course-taking history</summary>
    private List<CourseRecord> _courseRecords = new();

    /// <summary>The student's first name</summary>
    public string FirstName { get; init; }

    /// <summary>The student's last name</summary>
    public string LastName { get; init; }

    /// <summary>The student's course records</summary>
    /// <remarks>We return a copy of the course records to prevent modifications</remarks>
    public IEnumerable<CourseRecord> CourseRecords => _courseRecords.ToArray();

    /// <summary>The student's GPA</summary>
    public double GPA
    {
        get
        {
            var points = 0.0;
            var hours = 0.0;
            foreach (var cr in CourseRecords)
            {
                points += (double)cr.Grade * cr.CreditHours;
                hours += cr.CreditHours;
            }
            return points / hours;
        }
    }

    /// <summary>
    /// Adds <paramref name="cr"/> to the students' course history
    /// </summary>
    /// <param name="cr">The course record to add</param>
    public void AddCourseRecord(CourseRecord cr)
    {
        _courseRecords.Add(cr);
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CourseRecords))); 
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(GPA)));
    }

    /// <summary>
    /// Constructs the student object
    /// </summary>
    /// <param name="firstName">The student's first name</param>
    /// <param name="lastName">The student's last name</param>
    public Student(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }
}

And our <ComputerScienceStudentControl>:

<UserControl x:Class="MvvmExample.ComputerScienceStudentControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:MvvmExample"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="300">
    <StackPanel Orientation="Vertical">
        <StackPanel Orientation="Horizontal">
            <TextBlock FontWeight="Bold" Margin="0,0,10,0">Name</TextBlock>
            <TextBlock Text="{Binding Path=FirstName}"/>
            <TextBlock Text="{Binding Path=LastName}"/>
        </StackPanel>
        <StackPanel Orientation="Horizontal">
            <TextBlock Margin="0,0,10,0" FontWeight="Bold">GPA</TextBlock>
            <TextBlock Text="{Binding Path=GPA, StringFormat={}{0:N2}}"/>
        </StackPanel>
        <TextBlock FontWeight="Bold">Course History</TextBlock>
        <ListView ItemsSource="{Binding Path=CourseRecords}" Margin="2,0,2,0"/>
    </StackPanel>
</UserControl>

Now, this control is simply a thin layer using data binding to connect it to the model class. But what if we needed to add some complex logic? Let’s say we want to display the student’s GPA calculated for only their computer science courses. We could put this in the Student class, but if every department in the university added their own custom logic and properties to that class, it would get very bloated very quickly. Instead, we might create a <ComputerScienceStudentControl> that would be used for this purpose, and compute the Computer Science GPA in its codebehind, but now we have complex logic that we’d prefer to test using automated tests.

Instead, we could create two new classes, our <ComputerScienceStudentControl> (a new View), and a ComputerScienceStudentViewModel (a ViewModel), as well as our existing Student (the Model).

Our ViewModel can now incorporate the custom logic for calculating a students’ computer science GPA, as well holding a reference to the Student class it is computed from:

public class ComputerScienceStudentViewModel : INotifyPropertyChanged
{
    /// <summary>
    /// The PropertyChanged event reports when properties change
    /// </summary>
    public event PropertyChangedEventHandler? PropertyChanged;

    /// <summary>
    /// The student this model represents
    /// </summary>
    /// <remarks>
    /// We require the student to be set in the constructor, and use 
    /// the init accessor to prevent changing out the student object
    /// </remarks>
    public Student Student { get; init; }

    /// <summary>
    /// THe first name of the student
    /// </summary>
    public string FirstName => Student.FirstName;

    /// <summary>
    /// The last name of the student
    /// </summary>
    public string LastName => Student.LastName;

    /// <summary>
    /// The course history of the student
    /// </summary>
    public IEnumerable<CourseRecord> CourseRecords => Student.CourseRecords;

    /// <summary>
    /// The university GPA of the student
    /// </summary>
    public double GPA => Student.GPA;

    /// <summary>
    /// The student's Computer Science GPA
    /// </summary>
    public double ComputerScienceGPA
    {
        get
        {
            var points = 0.0;
            var hours = 0.0;
            foreach (var cr in Student.CourseRecords)
            {
                if (cr.CourseName.Contains("CIS"))
                {
                    points += (double)cr.Grade * cr.CreditHours;
                    hours += cr.CreditHours;
                }
            }
            return points / hours;
        }
    }

    /// <summary>
    /// An event handler for passing forward PropertyChanged events from the student object
    /// </summary>
    /// <param name="sender">The student object</param>
    /// <param name="e">The eventargs describing the property that is changing</param>
    private void HandleStudentPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        switch (e.PropertyName)
        {
            // Both first and last names map to properties of the same name,
            // so we can reuse the PropertyChangedEventARgs
            case nameof(FirstName):
            case nameof(LastName):
                PropertyChanged?.Invoke(this, e);
                break;
            // The Student.GPA maps to GPA, and changes to it may
            // also signal a change to the CIS GPA
            case nameof(Student.GPA):
                PropertyChanged?.Invoke(this, e);
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ComputerScienceGPA)));
                break;
            // We don't care about any other properites of the Student, as they
            // are not present in this ViewModel, so ignore them
            default:
                break;
        }
    }

    /// <summary>
    /// Constructs a new ComputerScienceStudentViewModel, which wraps around the
    /// <paramref name="student"/> object and provides some additional functionality.
    /// </summary>
    /// <param name="student">The student who is this view model</param>
    public ComputerScienceStudentViewModel(Student student)
    {
        Student = student;
        Student.PropertyChanged += HandleStudentPropertyChanged;
    }
}

And a control to display it:

<UserControl x:Class="MvvmExample.StudentControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:MvvmExample"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="300">
    <StackPanel Orientation="Vertical">
        <StackPanel Orientation="Horizontal">
            <TextBlock FontWeight="Bold" Margin="0,0,10,0">Name</TextBlock>
            <TextBlock Text="{Binding Path=FirstName}"/>
            <TextBlock Text="{Binding Path=LastName}"/>
        </StackPanel>
        <StackPanel Orientation="Horizontal">
            <TextBlock Margin="0,0,10,0" FontWeight="Bold">GPA</TextBlock>
            <TextBlock Text="{Binding Path=GPA, StringFormat={}{0:N2}}"/>
        </StackPanel>
        <TextBlock FontWeight="Bold">Course History</TextBlock>
        <ListView ItemsSource="{Binding Path=CourseRecords}" Margin="2,0,2,0"/>
    </StackPanel>
</UserControl>

The ComputerScienceViewModel can then be used interchangeably with the Student model class, as both have the same properties (though the view model has one additional one). We could then either tweak the student control or create a new one that binds to this new property, i.e.:

<StackPanel Orientation="Horizontal">
    <TextBlock Margin="0,0,10,0" FontWeight="Bold">Computer Science GPA</TextBlock>
    <TextBlock Text="{Binding Path=ComputerScienceGPA, StringFormat={}{0:N2}}"/>
</StackPanel>

This represents just one of the ways a ViewModel can be used. A View Model can also be leveraged to combine multiple data classes into a single object that can serve as a DataContext. One can also be utilized to create a wrapper object around a web-based API or other data source to provide the ability to data bind that source to GUI controls.

Finally, because a ViewModel is simply another data class, it can be unit tested just like any other. This helps make sure that complex logic which we want thoroughly tested is not embedded in a GUI component, and simplifies our testing strategies.

Info

We’ve really only scratched the surface of the MVVM architecture as it is used in WPF applications. In addition to providing properties to bind to, a WPF MVVM can also define commands that decouple event handlers from their effects. When using commands, the GUI event handler simply signals the command, which is consumed by a ViewModel to perform the requested action.

Commands are outside the scope of this course, but you can refer to the Microsoft documentation and books from the O’Riley Learning Library if you would like to explore this concept in more depth.

Summary

In this chapter we looked at some of the challenges of testing GUIs, and saw why most GUI applications are still manually tested. We also explored the process of writing a test plan, a step-by-step process for a human tester to follow to provide rigor in the testing process.

We also explored the Model View ViewModel architecture in more depth, especially how it can allow us to move complex logic out of our GUI into a simple class that can be unit tested.

Chapter III

Web Development

Taking Objects Online

Subsections of Web Development

Chapter 1

Core Web Technologies

The Big Three plus HTTP

Subsections of Core Web Technologies

Introduction

The World-Wide-Web is a tool that you likely use every day - and it’s being used to deliver you this textbook. There are several core technologies that enable the web to work, and these are the focus of this chapter.

Key Terms

Some key terms to learn in this chapter are:

  • World-Wide-Web
  • Hyper-Text Markup Language (HTML)
  • Cascading Style Sheets (CSS)
  • JavaScript (JS)
  • Hyper-Text Transfer Protocol (HTTP)

Core Web Technologies

The World-Wide Web was the brainchild of Sir Tim Berners-Lee. It was conceived as a way to share information across the Internet; in Sir Berners-Lee’s own words describing the idea as he first conceived it:

This project is experimental and of course comes without any warranty whatsoever. However, it could start a revolution in information access.

Clearly that revolution has come to pass. The web has become part of our daily lives.

There were three key technologies that Sir Tim Berners-Lee proposed and developed. These remain the foundations upon which the web runs even today. Two are client-side, and determine how web pages are interpreted by browsers. These are:

  • Hyper-Text Markup Language
  • Cascading Style Sheets

They are joined with a third key client-side technology, which began as a scripting language developed by Brendan Eich to add interactivity to web pages in the Netscape Navigator.

  • JavaScript

You have already studied each of these core client-side web technologies in CIS 115, and used them to create your own personal web pages.

The other foundational web technology created by Sir Tim Berners-Lee is the communication protocol used to request and transmit web pages and other files across the Internet:

  • Hyper-Text Transfer Protocol

We will review each of these technologies briefly, before we see how ASP.NET builds upon them to deliver web applications.

Hyper-Text Markup Language

Hyper-Text Markup Language (HTML), is one of the three core technologies of the world-wide-web, along with Cascading Style Sheets (CSS) and Javascript (JS). Each of these technologies has a specific role to play in delivering a website. HTML defines the structure and contents of the web page. It is a markup language, similar to XML and the XAML you have been working with (indeed, HTML is based on the SGML (Standardized General Markup Language) standard, which XML is also based on, and XAML is an extension of XML).

HTML Elements

Thus, it uses the same kind of element structure, consisting of tags. For example, a button in HTML looks like this:

<button onclick="doSomething">
    Do Something
</button>

You likely notice how similar this definition is to buttons in XAML. As with XAML elements, HTML elements have an opening and closing tag, and can have additional HTML content nested inside these tags. HTML tags can also be self-closing, as is the case with the line break tag:

<br/>

Let’s explore the parts of an HTML element in more detail.

HTML Element Structure HTML Element Structure

The Start Tag

The start tag is enclosed in angle brackets (< and >). The angle brackets differentiate the text inside them as being HTML elements, rather than text. This guides the browser to interpret them correctly.

Note

Because angle brackets are interpreted as defining HTML tags, you cannot use those characters to represent greater than and less than signs. Instead, HTML defines escape character sequences to represent these and other special characters. Greater than is &gt;, less than is &lt;. A full list can be found on mdn.

The Tag Name

Immediately after the < is the tag name. In HTML, tag names like button should be expressed in lowercase letters (unlike XAML where they are expressed in Pascal case - each word starting with a capital letter). This is a convention (as most browsers will happily accept any mixture of uppercase and lowercase letters), but is very important when using popular modern web technologies like Razor and React, as these use Pascal case tag names to differentiate between HTML and components they inject into the web page.

The Attributes

After the tag name comes optional attributes, which are key-value pairs expressed as key="value". Attributes should be separated from each other and the tag name by whitespace characters (any whitespace will do, but traditionally spaces are used). As with XAML, different elements have different attributes available - and you can read up on what these are by visiting the MDN article about the specific element.

However, several attributes bear special mention:

  • The id attribute is used to assign a unique id to an element, i.e. <button id="that-one-button">. The element can thereafter be referenced by that id in both CSS and JavaScript code. An element ID must be unique in an HTML page, or unexpected behavior may result!

  • The class attribute is also used to assign an identifier used by CSS and JavaScript. However, classes don’t need to be unique; many elements can have the same class. Further, each element can be assigned multiple classes, as a space-delimited string, i.e. <button class="large warning"> assigns both the classes “large” and “warning” to the button.

Also, some web technologies (like Angular) introduce new attributes specific to their framework, taking advantage of the fact that a browser will ignore any attributes it does not recognize.

The Tag Content

The content nested inside the tag can be plain text, or another HTML element (or collection of elements). Unlike XAML elements, which usually can have only one child, HTML elements can have multiple children. Indentation should be used to keep your code legible by indenting any nested content, i.e.:

<div>
    <h1>A Title</h1>
    <p>This is a paragraph of text that is nested inside the div</p>
    <p>And this is another paragraph of text</p>
</div>

The End Tag

The end tag is also enclosed in angle brackets (< and >). Immediately after the < is a forward slash /, and then the tag name. You do not include attributes in an end tag.

If the element has no content, the end tag can be combined with the start tag in a self-closing tag, i.e. the input tag is typically written as self-closing:

<input id="first-name" type="text" placeholder="Your first name"/>

Text in HTML

Text in HTML works a bit differently than you might expect. Most notably, all white space is converted into a single space. Thus, the lines:

<blockquote>
    If you can keep your head when all about you   
        Are losing theirs and blaming it on you,   
    If you can trust yourself when all men doubt you,
        But make allowance for their doubting too;   
    If you can wait and not be tired by waiting,
        Or being lied about, don’t deal in lies,
    Or being hated, don’t give way to hating,
        And yet don’t look too good, nor talk too wise:
    <i>-Rudyard Kipling, excerpt from "If"</i>
</blockquote>

Would be rendered:

If you can keep your head when all about you Are losing theirs and blaming it on you, If you can trust yourself when all men doubt you, But make allowance for their doubting too; If you can wait and not be tired by waiting, Or being lied about, don’t deal in lies, Or being hated, don’t give way to hating, And yet don’t look too good, nor talk too wise: -Rudyard Kipling, excerpt from "If"

If, for some reason you need to maintain formatting of the included text, you can use the <pre> element (which indicates the text is preformatted):

<blockquote>
    <pre>
If you can keep your head when all about you   
    Are losing theirs and blaming it on you,   
If you can trust yourself when all men doubt you,
    But make allowance for their doubting too;   
If you can wait and not be tired by waiting,
    Or being lied about, don’t deal in lies,
Or being hated, don’t give way to hating,
    And yet don’t look too good, nor talk too wise:
    </pre>
    <i>-Rudyard Kipling, excerpt from "If"</i>
</blockquote>

Which would be rendered:

If you can keep your head when all about you   
    Are losing theirs and blaming it on you,   
If you can trust yourself when all men doubt you,
    But make allowance for their doubting too;   
If you can wait and not be tired by waiting,
    Or being lied about, don’t deal in lies,
Or being hated, don’t give way to hating,
    And yet don’t look too good, nor talk too wise:
    
-Rudyard Kipling, excerpt from "If"

Note that the <pre> preserves all formatting, so it is necessary not to indent its contents.

Alternatively, you can denote line breaks with <br/>, and non-breaking spaces with &nbsp;:

<blockquote>        
    If you can keep your head when all about you<br/>
    &nbsp;&nbsp;&nbsp;&nbsp;Are losing theirs and blaming it on you,<br/>   
    If you can trust yourself when all men doubt you,<br/>
    &nbsp;&nbsp;&nbsp;&nbsp;But make allowance for their doubting too;<br/>
    If you can wait and not be tired by waiting,<br/>
    &nbsp;&nbsp;&nbsp;&nbsp;Or being lied about, don’t deal in lies,<br/>
    Or being hated, don’t give way to hating,<br/>
    &nbsp;&nbsp;&nbsp;&nbsp;And yet don’t look too good, nor talk too wise:<br/>    
    <i>-Rudyard Kipling, excerpt from "If"</i>
</blockquote>

Which renders:

If you can keep your head when all about you
    Are losing theirs and blaming it on you,
If you can trust yourself when all men doubt you,
    But make allowance for their doubting too;
If you can wait and not be tired by waiting,
    Or being lied about, don’t deal in lies,
Or being hated, don’t give way to hating,
    And yet don’t look too good, nor talk too wise:

-Rudyard Kipling, excerpt from "If"

Additionally, as a programmer you may want to use the the code element in conjunction with the pre element to display preformatted code snippets in your pages.

HTML Comments

HTML comments are identical to XAML comments (as both inherited from SGML). Comments start with the sequence <!-- and end with the sequence -->, i.e.:

<!-- This is an example of a HTML comment -->

Basic Page Structure

HTML5.0 (the current HTML standard) pages have an expected structure that you should follow. This is:

<!DOCTYPE html>
<html>
    <head>
        <title><!-- The title of your page goes here --></title>
        <!-- other metadata about your page goes here -->
    </head>
    <body>
        <!-- The contents of your page go here -->
    </body>
</html>

HTML Elements

Rather than include an exhaustive list of HTML elements, I will direct you to the list provided by MDN. However, it is useful to recognize that elements can serve different purposes:

There are more tags than this, but these are the most commonly employed, and the ones you should be familiar with.

Learning More

The MDN HTML Docs are recommended reading for learning more about HTML.

Cascading Style Sheets

Cascading Style Sheets (CSS) is the second core web technology of the web. It defines the appearance of web pages by applying stylistic rules to matching HTML elements. CSS is normally declared in a file with the .css extension, separate from the HTML files it is modifying, though it can also be declared within the page using the <style> element, or directly on an element using the style attribute.

CSS Rules

A CSS rule consists of a selector and a definition block, i.e.:

h1
{
    color: red;
    font-weight: bold;
}

CSS Selectors

A CSS selector determines which elements the associated definition block apply to. In the above example, the h1 selector indicates that the style definition supplied applies to all <h1> elements. The selectors can be:

  • By element type, indicated by the name of the element. I.e. the selector p applies to all <p> elements.
  • By the element id, indicated by the id prefixed with a #. I.e. the selector #foo applies to the element <span id="foo">.
  • By the element class, indicated by the class prefixed with a .. I.e. the selector .bar applies to the elements <div class="bar">, <span class="bar none">, and <p class="alert bar warning">.

CSS selectors can also be combined in a number of ways, and pseudo-selectors can be applied under certain circumstances, like the :hover pseudo-selector which applies only when the mouse cursor is over the element.

You can read more on MDN’s CSS Selectors Page.

CSS Definition Block

A CSS definition block is bracketed by curly braces and contains a series of key-value pairs in the format key=value;. Each key is a property that defines how an HTML Element should be displayed, and the value needs to be a valid value for that property.

Measurements can be expressed in a number of units, from pixels (px), points (pt), the font size of the parent (em), the font size of the root element (rem), a percentage of the available space (%), or a percentage of the viewport width (vw) or height (vh). See MDN’s CSS values and units for more details.

Other values are specific to the property. For example, the cursor property has possible values help, wait, crosshair, not-allowed, zoom-in, and grab. You should use the MDN documentation for a reference.

Styling Text

One common use for CSS is to change properties about how the text in an element is rendered. This can include changing attributes of the font (font-style, font-weight, font-size, font-family), the color, and the text (text-align, line-break, word-wrap, text-indent, text-justify). These are just a sampling of some of the most commonly used properties.

Styling Elements

A second common use for CSS is to change properties of the element itself. This can include setting dimensions (width, height), adding margins, borders, and padding.

These values provide additional space around the content of the element, following the CSS Box Model:

CSS Box Model CSS Box Model

Providing Layout

The third common use for CSS is to change how elements are laid out on the page. By default HTML elements follow the flow model, where each element appears on the page after the one before it. Some elements are block level elements, which stretch across the entire page (so the next element appears below it), and others are inline and are only as wide as they need to be to hold their contents, so the next element can appear to the right, if there is room.

The float property can make an element float to the left or right of its container, allowing the rest of the page to flow around it.

Or you can swap out the layout model entirely by changing the display property to flex (for flexbox, similar to the XAML StackPanel) or grid (similar to the XAML Grid). For learning about these two display models, the CSS-Tricks A Complete Guide to Flexbox and A Complete Guide to Grid are recommended reading. These can provide quite powerful layout tools to the developer.

Learning More

This is just the tip of the iceberg of what is possible with CSS. Using CSS media queries can change the rules applied to elements based on the size of the device it is viewed on, allowing for responsive design. CSS Animation can allow properties to change over time, making stunning visual animations easy to implement. And CSS can also carry out calculations and store values, leading some computer scientists to argue that it is a Turing Complete language.

The MDN Cascading Stylesheets Docs and CSS Tricks are recommended reading to learn more about CSS and its uses.

JavaScript

Javascript (or ECMAScript, which is the standard Javascript is derived from), was originally developed for Netscape Navigator by Brendon Eich. The original version was completed in just 10 days. The name “javascript” was a marketing move by Netscape as they had just secured the rights to use Java Applets in their browser, and wanted to tie the two languages together. Similarly, they pushed for a Java-like syntax, which Brandon accommodated. However, he also incorporated functional behaviors based on the Scheme and drew upon Self’s implementation of object-orientation. The result is a language that may look familiar to you, but often works in unexpected ways.

Javascript is a Dynamically Typed Language

Unlike the statically-typed C# we’ve been working with, Javascript has dynamic types. This means that we always declare variables using the var keyword, i.e.:

var i = 0;
var story = "Jack and Jill went up a hill...";
var pi = 3.14;

Much like the var type in C#, the type of the variable is inferred when it is set. Unlike C# though, the type can change with a new assignment, i.e.:

var i = 0; // i is an integer
i = "The sky is blue"; // now i is a string
i = true; // now i is a boolean

This would cause an error in C#, but is perfectly legal in Javascript. Because Javascript is dynamically typed, it is impossible to determine type errors until the program is run.

In addition to var, variables can be declared with the const keyword (for constants that cannot be re-assigned), or the let keyword (discussed below).

JavaScript Types

While the type of a variable is inferred, Javascript still supports types. You can determine the type of a variable with the typeof() function. The available types in Javascript are:

  • integers (declared as numbers without a decimal point)
  • floats (declared as numbers with a decimal point)
  • booleans (the constants true or false)
  • strings (declared using double quotes ("I'm a string"), single quotes 'Me too!', or backticks `I'm a template string ${2 + 3}`) which indicate a template string and can execute and concatenate embedded Javascript expressions.
  • lists (declared using square brackets, i.e. ["I am", 2, "listy", 4, "u"]), which are a generic catch-all data structure, which can be treated as an array, list, queue, or stack.
  • objects (declared using curly braces or constructed with the new keyword, discussed later)

In Javascript, there are two keywords that represent a null value, undefined and null. These have a different meaning: undefined refers to values that have not yet been initialized, while null must be explicitly set by the programmer (and thus intentionally meaning nothing).

Javascript is a Functional Langauge

As suggested in the description, Javascript is a functional language incorporating many ideas from Scheme. In JavaScript we declare functions using the function keyword, i.e.:

function add(a, b) {
  return a + b;
}

We can also declare an anonymous function (one without a name):

function (a, b) {
  return a + b;
}

or with the lambda syntax:

(a,b) => {
  return a + b;
}

In Javascript, functions are first-class objects, which means they can be stored as variables, i.e.:

var add = function(a,b) {
  return a + b;
}

Added to arrays:

var math = [
  add,
  (a,b) => {return a - b;},
  function(a,b) { a * b; },
]

Or passed as function arguments.

Javascript has Function Scope

Variable scope in Javascript is bound to functions. Blocks like the body of an if or for loop do not declare a new scope. Thus, this code:

for(var i = 0; i < 3; i++;)
{
  console.log("Counting i=" + i);
}
console.log("Final value of i is: " + i);

Will print:

Counting i=0
Counting i=1
Counting i=2
Final value of i is: 3

Because the i variable is not scoped to the block of the for loop, but rather, the function that contains it.

The keyword let was introduced in ECMAScript version 6 as an alternative for var that enforces block scope. Using let in the example above would result in a reference error being thrown, as i is not defined outside of the for loop block.

Javascript is Event-Driven

Javascript was written to run within the browser, and was therefore event-driven from the start. It uses the event loop and queue pattern we saw in C#. For example, we can set an event to occur in the future with setTimeout():

setTimeout(function(){console.log("Hello, future!")}, 2000);

This will cause “Hello, future!” to be printed 2 seconds (2000 milliseconds) in the future (notice too that we can pass a function to a function).

Javascript is Object-Oriented

As suggested above, Javascript is object-oriented, but in a manner more similar to Self than to C#. For example, we can declare objects literally:

var student = {
  first: "Mark",
  last: "Delaney"
}

Or we can write a constructor, which in Javascript is simply a function we capitalize by convention:

function Student(first, last){
  this.first = first;
  this.last = last;
}

And invoke with the new keyword:

var js = new Student("Jack", "Sprat");

Objects constructed from classes have a prototype, which can be used to attach methods:

Student.prototype.greet = function(){
  console.log(`Hello, my name is ${this.first} ${this.last}`);
}

Thus, js.greet() would print Hello, my name is Jack Sprat;

ECMAScript 6 introduced a more familiar form of class definition:

class Student{
  constructor(first, last) {
    this.first = first;
    this.last = last;
    this.greet = this.greet.bind(this);
  }
  greet(){
    console.log(`Hello, my name is ${this.first} ${this.last}`);
  }
}

However, because JavaScript uses function scope, the this in the method greet would not refer to the student constructed in the constructor, but the greet() method itself. The constructor line this.greet = this.greet.bind(this); fixes that issue by binding the greet() method to the this of the constructor.

The Document Object Model

The Document Object Model (DOM) is a tree-like structure that the browser constructs from parsed HTML to determine size, placement, and appearance of the elements on-screen. In this, it is much like the elements tree we used with Windows Presentation Foundation (which was most likely inspired by the DOM). The DOM is also accessible to Javascript - in fact, one of the most important uses of Javascript is to manipulate the DOM.

You can learn more about the DOM from MDN’s Document Object Model documentation entry.

Hyper-Text Transfer Protocol

At the heart of the world wide web is the Hyper-Text Transfer Protocol (HTTP). This is a protocol defining how HTTP servers (which host web pages) interact with HTTP clients (which display web pages).

It starts with a request initiated from the web browser (the client). This request is sent over the Internet using the TCP protocol to a web server. Once the web server receives the request, it must decide the appropriate response - ideally sending the requested resource back to the browser to be displayed. The following diagram displays this typical request-response pattern.

HTTP’s request-response pattern HTTP’s request-response pattern

This HTTP request-response pattern is at the core of how all web applications communicate. Even those that use websockets begin with an HTTP request.

The HTTP Request

A HTTP Request is just text that follows a specific format and sent from a client to a server. It consists of one or more lines terminated by a CRLF (a carriage return and a line feed character, typically written \r\n in most programming languages).

  1. A request-line describing the request
  2. Additional optional lines containing HTTP headers. These specify details of the request or describe the body of the request
  3. A blank line, which indicates the end of the request headers
  4. An optional body, containing any data belonging of the request, like a file upload or form submission. The exact nature of the body is described by the headers.

The HTTP Response

Similar to an HTTP Request, an HTTP response consists of one or more lines of text, terminated by a CRLF (sequential carriage return and line feed characters):

  1. A status-line indicating the HTTP protocol, the status code, and a textual status
  2. Optional lines containing the Response Headers. These specify the details of the response or describe the response body
  3. A blank line, indicating the end of the response metadata
  4. An optional response body. This will typically be the text of an HTML file, or binary data for an image or other file type, or a block of bytes for streaming data.

Making a Request

With our new understanding of HTTP requests and responses as consisting of streams of text that match a well-defined format, we can try manually making our own requests, using a Linux command line tool netcat.

Open a PowerShell instance (Windows) or a terminal (Mac/Linux) and enter the command:

$ ssh [eid]@cslinux.cs.ksu.edu

Alternatively, you can use Putty to connect to cslinux. Detailed instructions on both approaches can be found on the Computer Science support pages.

Warning

If you are connecting from off-campus, you will also need to connect through the K-State VPN to access the Computer Science Linux server. You can find more information about the K-State VPN on the K-State IT pages

The $ indicates a terminal prompt; you don’t need to type it. The [eid] should be replaced with your eid. This should ssh you into the CS Linux system. It will prompt you for your CS password, unless you’ve set up public/private key access.

Once in, type the command:

$ nc google.com 80

The nc is the netcat executable - we’re asking Linux to run netcat for us, and providing two command-line arguments, google.com and 80, which are the webserver we want to talk to and the port we want to connect to (port 80 is the default port for HTTP requests).

Now that a connection is established, we can stream our request to Google’s server:

GET / HTTP/1.1

The GET indicates we are making a GET request, i.e. requesting a resource from the server. The / indicates the resource on the server we are requesting (at this point, just the top-level page). Finally, the HTTP/1.1 indicates the version of HTTP we are using.

Note that you need to press the return key twice after the GET line, once to end the line, and the second time to end the HTTP request. Pressing the return key in the terminal enters the CRLF character sequence (Carriage Return & Line Feed) the HTTP protocol uses to separate lines

Once the second return is pressed, a whole bunch of text will appear in the terminal. This is the HTTP Response from Google’s server. We’ll take a look at that next.

Reading the Response

Scroll up to the top of the request, and you should see something like:

HTTP/1.1 200 OK
Date: Wed, 16 Jan 2019 15:39:33 GMT
Expires: -1
Cache-Control: private, max-age=0
Content-Type: text/html; charset=ISO-8859-1
P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info."
Server: gws
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN
Set-Cookie: 1P_JAR=2019-01-16-15; expires=Fri, 15-Feb-2019 15:39:33 GMT; path=/; domain=.google.com
Set-Cookie: NID=154=XyALfeRzT9rj_55NNa006-Mmszh7T4rIp9Pgr4AVk4zZuQMZIDAj2hWYoYkKU6Etbmjkft5YPW8Fens07MvfxRSw1D9mKZckUiQ--RZJWZyurfJUyRtoJyTfSOMSaniZTtffEBNK7hY2M23GAMyFIRpyQYQtMpCv2D6xHqpKjb4; expires=Thu, 18-Jul-2019 15:39:33 GMT; path=/; domain=.google.com; HttpOnly
Accept-Ranges: none
Vary: Accept-Encoding

<!doctype html>...

The first line indicates that the server responded using the HTTP 1.1 protocol, the status of the response is a 200 code, which corresponds to the human meaning “OK”. In other words, the request worked. The remaining lines are headers describing aspects of the request - the Date, for example, indicates when the request was made, and the path indicates what was requested. Most important of these headers, though, is the Content-Type header, which indicates what the body of the response consists of. The content type text/html means the body consists of text, which is formatted as HTML – in other words, a webpage.

Everything after the blank line is the body of the response - in this case, the page content as HTML text. If you scroll far enough through it, you should be able to locate all of the HTML elements in Google’s search page.

That’s really all there is with a HTTP request and response. They’re just streams of data. A webserver just receives a request, processes it, and sends a response.

Summary

In this chapter we explored the three client-side core web technologies: HTML, which defines the content of a web page; CSS, which defines the appearance of the web page; and Javascript, which adds interactivity to the web page. We also examined Hyper-Text Transfer Protocol (HTTP) which is used to transmit web pages from the server to the client. We learned that HTTP always follows a request-response pattern, and how both requests and responses are simply streams of data that follow a specific layout.

With this basic understanding of the web client files, and the means to transmit them to the client, we are ready to tackle creating a web server, which we will do in the next chapter.

Chapter 2

ASP.NET

.NET Goes Online

Subsections of ASP.NET

Introduction

While web browsers request resources (including HTTP, CSS, and JavaScript) files over HTTP, the other end of this connection, and what supplies those files, is a web server. Unlike web clients, which are limited by what technologies a browser understands (namely HTML, CSS, and JS), a web server can be written in any programming language. In this chapter, we will explore writing web servers in C#, using aspects of the ASP.NET framework.

Key Terms

Some key terms to learn in this chapter are:

  • Web Server
  • ASP.NET
  • Dynamic Web Pages
  • Templates
  • Razor Pages

Key Skills

The key skills you will be developing in this chapter are:

  • Creating a web server to serve a web application using ASP.NET
  • The ability to author Razor Pages combining HTML with embedded C# code

Static Webservers

The earliest web servers simply served files held in a directory. If you think back to your web development assignment from CIS 115, this is exactly what you did - you created some HTML, CSS, and JS files and placed them in the public_html directory in your user directory on the CS Linux server. Anything placed in this folder is automatically served by an instance of the Apache web server running on the Linux server, at the address https://people.cs.ksu.edu/~[eid]/ where [eid] is your K-State eid.

Apache is one of the oldest and most popular open-source web servers in the world. Microsoft introduced their own web server, Internet Information Services (IIS) around the same time. Unlike Apache, which can be installed on most operating systems, IIS only runs on the Windows Server OS.

While Apache installations typically serve static files from either a html or public_html directory, IIS serves files from a wwwroot directory.

As the web grew in popularity, there was tremendous demand to supplement static pages with pages created on the fly in response to requests - allowing pages to be customized to a user, or displaying the most up-to-date information from a database. In other words, dynamic pages. We’ll take a look at these next.

Dynamic Pages

Modern websites are more often full-fledged applications than collections of static files. But these applications remain built upon the foundations of the core web technologies of HTML, CSS, and JavaScript. In fact, the client-side application is typically built of exactly these three kinds of files! So how can we create a dynamic web application?

One of the earliest approaches was to write a program to dynamically create the HTML file that was being served. Consider this method:

public string GeneratePage()
{
    StringBuilder sb = new StringBuilder();
    sb.Append("<!DOCTYPE html>");
    sb.Append("<html>");
    sb.Append("<head>");
    sb.Append("<title>My Dynamic Page</title>");
    sb.Append("</head>");
    sb.Append("<body>");
    sb.Append("<h1>Hello, world!</h1>");
    sb.Append("<p>Time on the server is ");
    sb.Append(DateTime.Now);
    sb.Append("</p>");
    sb.Append("</body>");
    sb.Append("</html>");
    return sb.ToString();
}

It generates the HTML of a page showing the current date and time. Remember too that HTTP responses are simply text, so we can generate a response as a string as well:

public string GenerateResponse()
{
    string page = GeneratePage();
    StringBuilder sb = new StringBuilder();
    sb.AppendLine("HTTP/1.1 200");
    sb.AppendLine("Content-Type: text/html; charset=utf-8");
    sb.AppendLine("ContentLength:" + page.Length);
    sb.AppendLine("");
    sb.Append(page);
    return sb.ToString();
}

The resulting string could then be streamed back to the requesting web browser. This is the basic technique used in all server-side web frameworks: they dynamically assemble the response to a request by assembling strings into an HTML page. Where they differ is what language they use to do so, and how much of the process they’ve abstracted.

This approach was adopted by Microsoft and implemented as Active Server Pages (ASP). By placing files with the .asp extension among those served by an IIS server, C# or Visual Basic code written on that page would be executed, and the resulting string would be served as a file. This would happen on each request - so a request for http://somesite.com/somepage.asp would execute the code in the somepage.asp file, and the resulting text would be served.

You might have looked at the above examples and shuddered. After all, who wants to assemble text like that? And when you assemble HTML using raw string concatenation, you don’t have the benefit of syntax highlighting, code completion, or any of the other modern development tools we’ve grown to rely on. Thankfully, most web development frameworks provide some abstraction around this process, and by and large have adopted some form of template syntax to make the process of writing a page easier.

Template Rendering

It was not long before new technologies sprang up to replace the ad-hoc string concatenation approach to creating dynamic pages. These template approaches allow you to write a page using primarily HTML, but embed snippets of another language to execute and concatenate into the final page. This is very similar to the template strings we have used in C#, i.e.:

string time = $"The time is {DateTime.Now}";

Which concatenates the invoking of the DateTime.Now property’s ToString() method into the string time. While the C# template string above uses curly braces to call out the script snippets, most HTML template libraries initially used some variation of angle brackets + additional characters. As browsers interpret anything within angle brackets (<>) as HTML tags, these would not be rendered if the template was accidentally served as HTML without executing and concatenating scripts. Two early examples are:

  • <?php echo "This is a PHP example" ?>
  • <% Response.Write("This is a classic ASP example) %>

And abbreviated versions:

  • <?= "This is the short form for PHP" ?>
  • <%= "This is the short form for classic ASP" %>

Template rendering proved such a popular and powerful tool that rendering libraries were written for most programming languages, and could be used for more than just HTML files - really any kind of text file can be rendered with a template. Thus, you can find template rendering libraries for JavaScript, Python, Ruby, and pretty much any language you care to (and they aren’t that hard to write either).

Microsoft’s classic ASP implementation was limited to the Visual Basic programming language. As the C# language gained in popularity, they replaced classic ASP with ASP.NET web pages. Like classic ASP, each page file (named with a .aspx extension) generates a corresponding HTML page. The script could be either Visual Basic or C#, and a new syntax using the at symbol (@) to proceed the code snippets was adopted. Thus the page:

<html>
    <body>
        <h1>Hello Web Pages</h1>
        <p>The time is @DateTime.Now</p>
    </body>
</html>

Would render the current time. You can run (and modify) this example on the w3schools.com.

This template syntax is the Razor syntax, and used throughout Microsoft’s ASP.NET platform. Additionally it can be used outside of ASP.NET with the open-source RazorEngine.

Classic PHP, Classic ASP, and ASP.NET web pages all use a single-page model, where the client (the browser) requests a specific file, and as that file is interpreted, the dynamic page is generated. This approach worked well in the early days of the world-wide-web, where web sites were essentially a collection of pages. However, as the web grew increasingly interactive, many web sites grew into full-fledged web applications, full-blown programs that did lend themselves to a page-based structure. This new need resulted in new technologies to fill the void - web frameworks. We’ll talk about these next.

Web Frameworks

As web sites became web applications, developers began looking to use ideas and techniques drawn from traditional software development. These included architectural patterns like Model-View-Controller (MVC) and Pipeline that simply were not possible with the server page model. The result was the development of a host of web frameworks across multiple programming languages, including:

  • Ruby on Rails, which uses the Ruby programming language and adopts a MVC architecture
  • Laravel, which uses the PHP programming language and adopts a MVC architecture
  • Django, which uses the Python programming language and adopts a MVC architecture
  • Express, which uses the Node implementation of the JavaScript programming language and adopts the Pipeline architecture
  • Revel, which uses the Go programming language and adopts a Pipeline architecture
  • Cowboy, which uses the erlang programming language and adopts a Pipeline architecture
  • Phoenix, which uses the elixir programming language, and adopts a Pipeline architecture

ASP.NET Frameworks

This is only a sampling of the many frameworks and languages used in the modern web. Microsoft adapted to the new approach by creating their own frameworks within the ASP.NET family:

  • ASP.NET MVC uses C# (or Visual Basic) for a language and adopts a MVC architecture
  • ASP.NET Razor Pages, which also uses C# (or Visual Basic) for its language, and adopts a Pipeline architecture
  • ASP.NET API is a web framework focused on creating RESTful web APIs (i.e. a web application that serves data instead of HTML)

IIS and ASP.NET Core

While ASP.NET applications are traditionally hosted on IIS running on the Windows Server operating system, the introduction of .NET Core made it possible to run .NET programs on Linux machines. As Linux operating systems are typically free and dominate the web server market (W3Cook1 reports 98.1% of web servers worldwide run on a Linux OS).

Microsoft has accordingly migrated its ASP.NET family to a new implementation can run on .NET Core or IIS: ASP.NET Core. When you build a ASP.NET Core application, you can choose your deployment target: IIS, .NET Core, or even Microsoft’s cloud service, Azure. The same application can run on any of these platforms.

Razor Pages

ASP.NET Core adds a project type to Visual Studio’s new project wizard, ASP.NET Core web application which uses Razor Pages. The Razor Page approach represents a hybrid approach between a MVC and Pipeline architecture and leverages some of the ideas of component-based design that we saw with WPF applications.

The program entry point is Program.cs, which creates the web server our application will run on. In it, we initialize and configure the server based on the Startup.cs class, which details what aspects of the ASP.NET program we want to use. The wizard does the initial configuration for us, and for now we’ll leave the defaults:

  • Adding the Razor Pages service (which allows us to use Razor Pages)
  • Enabling HTTPS redirection (which instructs browsers making HTTP requests against our server to make HTTPS requests instead)
  • Enabling the use of static files, which means files in the wwwroot folder will be served as they are, in as efficient a manner of possible
  • Mapping requests to razor pages (this makes a request against a route like /index map to the Pages/Index.cshtml razor page)

Under this architecture, any file we want to serve as-is (i.e. our CSS and JavaScript files), we’ll place in wwwroot folder. Any route we want to serve dynamically, we’ll create a corresponding Razor page for in the Pages folder.

Razor Page Syntax

Let’s look at an example Razor page, index.cshtml, and then break down its components:

@page 
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}
<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>

The @page line indicates to the compiler that this file represents a Razor page. This is necessary for the page to be interpreted correctly, and for setting up the mapping from a request for the route /index to be mapped to this page (Index.cshtml).

The @model line indicates the model class to use with this page. Conventionally, the model class has the same name as the Razor page, plus a .cs extension, though we can use a different model file if needed. If we follow the convention, the model file is grouped with the Razor page file, much like the codebehind files in WPF and Forms. The model class provides the data for the web page, in a manner somewhat like the ViewModel classes we worked with in WPF. We’ll talk more about model classes shortly.

The @{} section is a place to define variables. In this case, we add a key/value pair to the ViewData dictionary. This dictionary is available in both the page and the layout, and is an easy way to pass values between them (in this case, we are providing a title to the layout). The layout is discussed below.

Finally, the page content itself is presented in Razor syntax - a mixture of HTML and embedded C# preceded by the @ symbol. Note that we do not need to provide termination to the C# code - the compiler will automatically determine when we switch from code back to HTML based on the grammar of the C# language.

Layouts

If you remember from our discussions of HTML, a valid HTML page must have a <!DOCTYPE html> element, and <html>, <head>, <title>, and <body> elements. But where are these in our Razor page? It exists in the _Pages/Shared/Layout.cshtml file:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - ExampleWebApplication</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-page="/Index">ExampleWebApplication</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2020 - ExampleWebApplication - <a asp-area="" asp-page="/Privacy">Privacy</a>
        </div>
    </footer>

    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>

    @RenderSection("Scripts", required: false)
</body>
</html>

Using a layout file allows us to place boilerplate HTML (code that is repeated on every page of our site) in a single location, and share it amongst all pages in our application. The @RenderBody() line indicates where the content of the Razor page will be rendered.

Note that we also implement a navigation menu in this layout. Instead of giving the links in this navigation page a href element, we use asp-page, which converts into an appropriate href linking to one of our Razor pages on compilation. Thus asp-page="/Index" will point to our Index.cshtml.cs page. The asp-page is an example of a TagHelper, syntax that provides extra details to be processed by the Razor rendering engine.

We can include other sections within the layout with @RenderSection() For example, the @RenderSection("Scripts", required: false) will render a “Scripts” section, if there is one defined in our Razor page. We define such sections with the @section syntax, i.e.:

@section Scripts{
    <script src="my-script.js"></script>
}

Would place the additional <script> element in the rendered Razor page. You can define as many sections as you want.

While the _Pages/Shared/Layout.cshtml file is the default layout, you can define your own layout files. These should also be placed in the Pages/Shared folder, and their name should begin with an underscore. You can then set it to be used as the layout for your page by setting the page’s Layout property:

@{
    Layout = "_YourLayout";
}

Where the string you set the property to is the name of your layout.

Model Classes

The model class serves a similar role to the codebehind classes of your WPF and Windows Forms applications. Any public properties defined in the model class are accessible in the Razor page. I.e. if we defined a property:

public class IndexModel:PageModel {

    public DateTime CurrentTime 
    {
        get 
        {
            return DateTime.Now;
        }
    }

    public IActionResult OnGet()
    {

    }
}

We could use it in the corresponding Razor page:

@page 
@model IndexModel 

<p>The current time is @Model.CurrentTime</p>

In addition, the model class can define a method to be triggered on each HTTP Request, i.e. the OnGet() method will be triggered with each HTTP GET request, and OnPost() will be called with each HTTP POST request. You can define a method for any of the valid HTTP request verbs that will be invoked on each corresponding request.

Summary

In this chapter we explored how server-side web technologies have evolved to create dynamic web sites and web applications. The ASP.NET Core platform is Microsoft’s answer to this evolution. It can run on either Microsoft’s flagship IIS server, or as a stand-alone server on a variety of platforms. It brings together a suite of technologies to build web pages and web applications. We took an in-depth look at one of these - Razor pages, and learned how to build our own Razor pages app.

Chapter 3

Form Data

Talk Back to your Server

Subsections of Form Data

Introduction

Now that we have explored some ideas about getting data from the web server, let’s turn our attention to sending data to the webserver. One of the earliest approaches for doing so is to use a HTML form sent as a HTTP Request, which we’ll take a look at in this chapter.

Key Terms

Some key terms to learn in this chapter are:

  • Form
  • Input
  • Encoding
  • Nullable<T> and ?

And the HTML tags:

  • <form>
  • <input>
  • <textarea>

HTTP Forms

One of the earliest (and still widely used) mechanisms for transferring data from a browser (client) to the server is a form. The <form> is a specific HTML element that contains input fields and buttons the user can interact with.

The <input> Element

Perhaps the most important - and versatile - of these is the <input> element. By setting its type attribute, we can represent a wide range of possible inputs, as is demonstrated by this table taken from the MDN Web Docs:

TypeDescriptionBasic Examples
buttonA push button with no default behavior displaying the value of the value attribute, empty by default.
<input type="button" name="ExampleButton" value="Click Me!"/>
checkboxA check box allowing single values to be selected/deselected.
<label><input type="checkbox" name="ExampleCheckbox"/><label>
colorA control for specifying a color; opening a color picker when active in supporting browsers.
<input type="color" name="ExampleColor" style="width: 40px; height: 40px;"/>
dateA control for entering a date (year, month, and day, with no time). Opens a date picker or numeric wheels for year, month, day when active in supporting browsers.
<input type="date" name="ExampleDate"/>
datetime-localA control for entering a date and time, with no time zone. Opens a date picker or numeric wheels for date- and time-components when active in supporting browsers.
<input type="datetime-local" name="ExampleDatetimeLocal"/>
emailA field for editing an email address. Looks like a text input, but has validation parameters and relevant keyboard in supporting browsers and devices with dynamic keyboards.
<input type="email" name="ExampleEmail"/>
fileA control that lets the user select a file. Use the accept attribute to define the types of files that the control can select.
<input type="file" name="ExampleFile"/>
hiddenA control that is not displayed but whose value is submitted to the server. There is an example in the last column, but it's hidden!
<input type="hidden" name="ExampleHidden" value="f0321dc35"/>
← It’s here!
imageA graphical submit button. Displays an image defined by the src attribute. The alt attribute displays if the image src is missing.
<input type="image" name="ExampleImage" src="/images/button.png">}}"/>
}}"/>
numberA control for entering a number. Displays a spinner and adds default validation when supported. Displays a numeric keypad in some devices with dynamic keypads.
<input type="number" name="ExampleNumber" min=0 max=10 step=1/>
passwordA single-line text field whose value is obscured. Will alert user if site is not secure.
<input type="password">
radioA radio button, allowing a single value to be selected out of multiple choices with the same name value.

<label>
  <input type="radio" name="ExampleRadio" value="1"/>
  Choice One
</label>
<label>
  <input type="radio" name="ExampleRadio" value="2"/>
  Choice Two
</label>
<label>
  <input type="radio" name="ExampleRadio" value="3"/>
  Choice Three
</label>
      
rangeA control for entering a number whose exact value is not important. Displays as a range widget defaulting to the middle value. Used in conjunction min and max to define the range of acceptable values.
<input type="range" name="ExampleRange" min="0" max="25"/>
resetA button that resets the contents of the form to default values. Not recommended.
<input type="reset" name="ResetExample"/>
searchA single-line text field for entering search strings. Line-breaks are automatically removed from the input value. May include a delete icon in supporting browsers that can be used to clear the field. Displays a search icon instead of enter key on some devices with dynamic keypads.
<input type="search" name="ExampleSearch"/>
submitA button that submits the form.
<input type="submit" name="ExampleSubmit" value="Save Changes"/>
telA control for entering a telephone number. Displays a telephone keypad in some devices with dynamic keypads.
<input type="tel" name="ExampleTel"/>
textThe default value. A single-line text field. Line-breaks are automatically removed from the input value.
<input type="text" name="ExampleText"/>
timeA control for entering a time value with no time zone.
<input type="time" name="ExampleTime"/>
urlA field for entering a URL. Looks like a text input, but has validation parameters and relevant keyboard in supporting browsers and devices with dynamic keyboards.
<input type="url" name="ExampleUrl">

Regardless of the type, the <input> element also has a name and value property. The name is similar to a variable name, in that it is used to identify the input’s value when we serialize the form (more about that later), and the value is the value the input currently is (this starts as the value you specify in the HTML, but it changes when the user edits it).

Additionally, checkboxes and radio buttons have a boolean ischecked property. These indicate if the box/button is checked or not (and that the box/button’s value should be submitted).

The <textarea> Element

The <textarea> element represents a multi-line text input. Similar to terminal programs, this is represented by columns and rows, the numbers of which are set by the cols and rows attributes, respectively. Thus:

<textarea cols=40 rows=5></textarea>

Would look like:

As with inputs, a <textarea> has a name and value attribute.

The <select> Element

The <select> element, along with <option> and <optgroup> make drop-down selection boxes. The <select> takes a name attribute, while each <option> provides a different value. The <options> can further be nested in <optgroup>s with their own labels. The <select> also has a multiple attribute (to allow selecting multiple options), and size which determines how many options should be displayed at once (with scrolling if more are available).

For example:

<select id="dino-select">
    <optgroup label="Theropods">
        <option>Tyrannosaurus</option>
        <option>Velociraptor</option>
        <option>Deinonychus</option>
    </optgroup>
    <optgroup label="Sauropods">
        <option>Diplodocus</option>
        <option>Saltasaurus</option>
        <option>Apatosaurus</option>
    </optgroup>
</select>

Displays as:

The <label> Element

A <label> element represents a caption for an element in the form. It can be tied to a specific input using its for attribute, by setting its value to the id attribute of the associated input. This allows screen readers to identify the label as belonging to the input, and also allows browsers to give focus or activate the input element when the label is clicked.

For example, if you create a checkbox with a label:

<fieldset style="display:flex; align-items:center;">
  <input type="checkbox" id="example"/>
  <label for="example">Is Checked</label>
</fieldset>

Clicking the label will toggle the checkbox!

The <fieldset> Element

The <fieldset> element is used to group related form parts together, which can be captioned with a <legend>. It also has a for attribute which can be set to the id of a form on the page to associate with, so that the fieldset will be serialized with the form (this is not necessary if the fieldset is inside the form). Setting the fieldset’s disabled attribute will also disable all elements inside of it.

For example:

<fieldset>
  <legend>Who is your favorite muppet?</legend>
  <input type="radio" name="muppet" id="kermit">
    <label for="kermit">Kermit</label>
  </input>
  <input type="radio" name="muppet" id="animal">
    <label for="animal">Animal</label>
  </input>
  <input type="radio" name="muppet" id="piggy">
    <label for="piggy">Miss Piggy</label>
  </input>
  <input type="radio" name="muppet" id="gonzo">
    <label for="gonzo">Gonzo</label>
  </input>
</fieldset>

Would render:

Who is your favorite muppet?

The <form> Element

Finally, the <form> element wraps around all the <input>, <textarea>, and <select> elements, and gathers them along with any contained within associated <fieldset>s to submit in a serialized form. This is done when an <input type="submit"> is clicked within the form, when the enter key is pressed and the form has focus, or by calling the submit() method on the form with JavaScript.

There are a couple of special attributes we should know for the <form> element:

  • action - the URL this form should be submitted to. Defaults to the URL the form was served from.
  • enctype - the encoding strategy used, discussed in the next section. Possible values are:
    • application/x-www-form-urlencoded - the default
    • multipart/form-data - must be used to submit files
    • text/plain - useful for debugging
  • method - the HTTP method to submit the form using, most often GET or POST

When the form is submitted, the form is serialized using the enctype attribute and submitted using the HTTP method to the URL specified by the action attribute. Let’s take a deeper look at this process next.

Form Data

Form data is simply serialized key/value pairs pulled from a form and encoded using one of the three possible encoding strategies, and submitted using the specified method (usually GET or POST).

So when we submit a form containing two text inputs for first and last name:

<form method="post">
    <label for="First">First Name:</label>
    <input type="text" name="First"/>
    <label for="Last">Last Name:</label>
    <input type="text" name="Last"/>
    <input type="Submit" value="Save Name"/>
</form>

And enter the values “Frank” and “Jones”, the form is serialized as the key-value pairs:

{
    "First": "Frank",
    "Last": "Jones"
}
Info

Here we are displaying the key-value pairs as JSON for legibility, but how the pairs are encoded depends on the encoding strategy as discussed below.

If a form contains multiple inputs with the same name, they are serialized as an array, i.e. the form:

<form method="post">
    <label>Enter three numbers:</label>
    <input type="number" name="Number"/>
    <input type="number" name="Number"/>
    <input type="number" name="Number"/>
    <input type="Submit" value="Save Numbers"/>
</form>

Would be serialized as:

{
    "Number" : [2, 3, 4]
}

Finally, toggle inputs like checkboxes and radio buttons only submit a value when checked, i.e. given the following form:

<form method="post">
    <label>Do the thing:</label>
    <input type="checkbox" name="DoTheThing" value="thing"/>
    <input type="Submit" value="Save Numbers"/>
</form>

Would serialize as:

{}

When the checkbox is not checked, or:

{
    "DoTheThing": "thing"
}

When the checkbox is checked.

Now that we’ve discussed how inputs are serialized into key/value or key/array of value pairs, let’s turn our attention to the method used to submit the form, and then look at each encoding strategy in turn.

Method

The HTTP method determines if the form data is sent as part of the url of the request, or in the body of the request.

GET Requests

With a GET request, the serialized form data is appended to the url as a query or search parameter. This takes the form of a question mark: ? followed by the serialized form data. In addition, the serialized data must be url-encoded to ensure the URL remains valid, as it may contain reserved characters (i.e. the characters :,/,?,#, &, and =) have special meanings in URLs, so the encoded data can’t contain them).

For example, searching using Google uses a GET request, so when we type “Razor Pages” into Google and click search, it makes a request against the URL: https://www.google.com/search?q=razor+pages (Note it adds additional form data fields for a variety of purposes).

A GET request is appropriate when the data you are submitting is small, like search terms or filter values. Once you start submitting larger amounts of data (like parameters to build an object from), you’ll probably want to switch to POST requests. Also, remember the form data for GET requests are visible in the URL, so you’ll want to use POST requests when seeing those values might be confusing or annoying to users.

Finally, passwords should NEVER be sent using a GET request, as doing so makes them visible in the URL.

Tip

The default method for form submission is a GET request, so if you don’t specify the method parameter, the form will be submitted using this method.

POST Requests

A POST request is submitted as the body of the request. This is the most appropriate method for large submissions, submissions with data you don’t want visibly displayed in the browser, and it is the only way to submit form data including files (which must be encoded using “multipart/form-data” as described below).

Encoding Strategies

There are two primary strategies for submitting data to a server from HTML forms (note that you can submit data to servers using other strategies when submitting from a program - we’ll discuss some of these in the next chapter). These strategies are x-www-form-urlencoded and multipart/form-data. We’ll take a look at how these encode data next.

x-www-form-urlencoded

The default encoding method is application/x-www-form-urlencoded, which encodes the form data as a string consisting of key/value pairs. Each pair is joined by a = symbol, and pairs are in turn joined by & symbols. The key and value strings are further encoded using percent encoding (URL encoding), which replaces special characters with a code beginning with a percent sign (i.e. & is encoded to %26). This prevents misinterpretations of the key and value as additional pairs, etc. Percent encoding is also used to encode URL segments (hence the name URL encoding).

Thus, the form:

<form>
    <input type="text" name="Name" value="Grover"/>
    <select name="Color">
        <option value="Red">Red</option>
        <option selected="true" value="Blue">Blue</option>
        <option value="Green">Green</option>
    </select>
    <input type="number" name="Age" value="36"/>
</form>

Would be encoded as:

Name=Grover&Color=Blue&Age=36

The HTTPUtility class in the System.Web namespace contains helpful methods for encoding and decoding URL strings.

URL-Encoded form data can be submitted with either a GET or POST request. With a GET request, the form data is included in the URL’s query (search) string, i.e. our form above might be sent to:

www.sesamestreet.com/muppets/find?Name=Grover&Color=Blue&Age=36

Which helps explain why the entire seralized form data needs to be URL encoded - it is included as part of the url!

When submitted as a post request, the string of form data is the body of the request.

multipart/form-data

The encoding for multipart/form-data is a bit more involved, as it needs to deal with encoding both regular form values and binary file data. It deals with this challenge by separating each key/value pair by a sequence of bytes known as a boundary, which does not appear in any of the files. This boundary can then be used to split the body back into its constituent parts when parsing. Each part of the body consists of its own head and body sections, with the body of most elements simply their value, while the body of file inputs is the file data encoded in base64. Thus, the form:

<form>
    <input type="text" name="Name" value="Grover"/>
    <select name="Color">
        <option value="Red">Red</option>
        <option selected="true" value="Blue">Blue</option>
        <option value="Green">Green</option>
    </select>
    <input type="number" name="Age" value="36"/>
    <input type="file" name="Image" value="Grover.jpg" />
</form>

Would be encoded into a POST request as:

POST /test HTTP/1.1 
Host: foo.example
Content-Type: multipart/form-data;boundary="boundary" 

--boundary 
Content-Disposition: form-data; name="Name" 

Grover
--boundary 
Content-Disposition: form-data; name="Color" 

Blue
--boundary 
Content-Disposition: form-data; name="Age" 

36
--boundary 
Content-Disposition: form-data; name="Image"; filename="Grover.jpg" 

/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjI...
--boundary--

Files can only be submitted using multipart/form-data encoding. If you attempt to use application/x-www-form-urlencoded, only the file name will be submitted as the value. Also, as multipart/form-data is always submitted as the body of the request, it can only be submitted as part of a POST request, never a GET. So a form containing a file input should always specify:

<form enctype="multipart/form-data" method="POST">

Razor Pages and Form Data

While C# does provide utilities for parsing URL-encoded strings, the functionality of parsing incoming form data is built into the ASP.NET response handling. Thus, when writing a Razor page application, we don’t need to perform decoding on the form data - it has already been done for us. There are several strategies built into Razor Pages to access this information:

The Request Object

The first of these is the HttpRequest object, which is available as the Request property within a Page. This object provides access to the QueryString and Form, as well as Cookies, ServerVariables and more.

If the form was submitted as a GET request, then the Request.QueryString is a collection of key-value pairs used like a dictionary, i.e. to access the value of the input with name “Color”, we would use: Request.QueryString["Color"].

Similarly, the Form also exposes the form content as a collection of key-value pairs, so we could access a POST request’s input with the name “Color” value with Request.Form["Color"].

Finally, the request also allows for checking both collections using its own accessor property, i.e. Request["Color"] would provide the submitted value for the input “Color” if it was sent with either a GET or POST request.

Parameter Binding

A second approach to accessing form data in ASP.NET is Parameter Binding. You’ve already seen the OnGet() method of the PageModel class. This is invoked every time a GET request to our server matches the page it is associated with. We can also supply methods for other HTTP actions, i.e. POST, PUT, DELETE map to OnPost(), OnPut(), and OnDelete() respectively. For each of these methods, we can use parameter binding to automatically parse and convert form data into parameter variables.

In its simplest form, we simply declare parameters whose type and name match those of our form. Thus, for the form:

<form>
    <input type="text" name="Name" value="Grover"/>
    <select name="Color">
        <option value="Red">Red</option>
        <option selected="true" value="Blue">Blue</option>
        <option value="Green">Green</option>
    </select>
    <input type="number" name="Age" value="36"/>
</form>

We could add several parameters to our OnGet() corresponding the names and types of the form fields:

OnGet(string Name, string Color, int Age){
    // Name would be "Grover"
    // Color would be "Blue"
    // Number would be 36
}

The form values are automatically converted and bound to the corresponding parameters of the OnGet method. If the form does not contain the corresponding parameter, then it is assigned the default value (for value types) or null (for reference types).

Info

There are times you may not want to use default values for value types. For example, in the form above, if the Age property is not specified, it will default to 0. If we instead wanted it to be null, we could use the Nullable<T> type:

OnGet(string Name, string Color, Nullable<int> Age) {...}

This allows Age to be null, in addition to all its normal possible values. You can also specify a nullable type with the ? shorthand, i.e.:

OnGet(string Name, string Color, int? Age) {...}

Model Binding

A third approach is Model Binding, where decorators are used on public properties of the PageModel class to indicate that they should be bound to form data, i.e.:

public class Muppet : PageModel {

    /// <summary>The muppet's name</summary>
    [BindProperty]
    public string Name { get; set; }

    /// <summary>The muppet's color</summary>
    [BindProperty]
    public string Color { get; set; }

    ///<summary>The muppet's age</summary>
    [BindProperty]
    public int Age {get; set;}    
}

When set up this way, the properties will be populated with the corresponding form data on POST, PUT, and PATCH requests. By default, they will not be populated on GET requests, though you can override this behavior with SupportsGet:

    /// <summary>The muppet's name</summary>
    [BindProperty(SupportGet = true)]
    public string Name { get; set; }

Finally, we can indicate all properties of the model class should be bound with a single [BindsProperties] decorator on the class, i.e.:

[BindProperties(SupportsGet = true)]
public class Muppet : PageModel {

    /// <summary>The muppet's name</summary>
    public string Name { get; set; }

    /// <summary>The muppet's color</summary>
    public string Color { get; set; }

    ///<summary>The muppet's age</summary>
    public int Age {get; set;}    
}
Info

You might be wondering why ModelBinding does not work with GET requests by default. In the previous section, we discussed when to use GET or POST requests to submit form data - we use GET requests for smaller data we don’t mind displaying the URL, like search terms or filter values. We use POST requests for large data, especially data we will use to populate an object. So when using Model Binding, using the POST method is most appropriate. Microsoft chose to reinforce this practice through the choice to not bind GET requests by default.

Validation

Validation refers to the process of making sure the submitted data matches our expectations. Validation can be done client-side or server-side. For example, we can use the built-in HTML form validation properties to enforce rules, like a number that must be positive:

<input type="number" min="0" name="Age" required>

If a user attempts to submit a form containing this input, and the value is less than 0, the browser will display an error message instead of submitting. In addition, the psuedo-css class :invalid will be applied to the element.

We can also mark inputs as required using the required attribute. The browser will refuse to submit the form until all required inputs are completed. Inputs with a required attribute also receive the :required pseudo-class, allowing you to assign specific styles to them.

You can read more about HTML Form validation on MDN.

Client-side validation is a good idea, because is minimizes invalid requests against our web application. However, we cannot always depend on it, so we also need to implement server-side validation. We can write custom logic for doing this, but Razor Pages also supports special validation decorators for bound properties. For example, the corresponding validation for the input above would be:

[BindProperty]
[Required]
[Range(0, int.MaxValue)]
public int Age { get; set; }

The available validation decorators are:

  • [CreditCard]: Validates that the property has a credit card format. Requires jQuery Validation Additional Methods.
  • [Compare]: Validates that two properties in a model match.
  • [EmailAddress]: Validates that the property has an email format.
  • [Phone]: Validates that the property has a telephone number format.
  • [Range]: Validates that the property value falls within a specified range.
  • [RegularExpression]: Validates that the property value matches a specified regular expression.
  • [Required]: Validates that the field is not null. See [Required] attribute for details about this attribute’s behavior.
  • [StringLength]: Validates that a string property value doesn’t exceed a specified length limit.
  • [Url]: Validates that the property has a URL format.

If validation fails, then the PageModel’s IsValid attribute is false.

You can read more about server-side validation with Razor pages in the Microsoft Documentation.

Summary

In this chapter we looked at how data is handled in web applications. We saw how forms can be used to submit data to our server, and examined several common encoding strategies. We also saw how we can retrieve this data in our Razor Pages - through the Request object, or by parameter or model binding. Finally, we discussed validating submitted values, on both the client and server side of a HTTP request.

You should now be able to handle creating web forms and processing the submitted data.

Chapter 4

Web APIs

Making the Web Accessible - for Programs

Subsections of Web APIs

Introduction

Not all web applications are built to be viewed in a browser. Many are built to be used by other programs. We call these web applications Web APIs (Application Programming Interfaces). These also make HTTP or HTTPS requests against our apps, but usually instead of serving HTML, we serve some form of serialized data instead - most commonly XML or JSON.

Making Requests

Making a HTTP request is a multi-step process. First you must establish a connection with the server, then create the request data, then you must stream that data to your server through the connection. Once the server has received and processed your request data, it should stream a response back to you.

You can write code to handle each step, but most programming languages provide one or more libraries that provide a level of abstraction to this process. The C# language actually offers several options in its system libraries, and there are multiple open-source options as well.

WebRequest

The simplest of these is the WebRequest object. It represents and carries out a single HTTP request and provides the response. Let’s take a look at an example, which retrieves a “Joke of the Day” from a web API at https://jokes.one:

WebRequest request = WebRequest.Create("http://api.jokes.one/jod");

This one line of code creates the WebRequest object. Notice that we are not using a constructor. Instead, we invoke a Create() method. This is an example of the Factory Method Pattern, which you will learn more about in CIS 501. But to briefly introduce the concept, the WebRequest class is actually a base class for a multiple different classes, each representing a specific kind of web request (i.e. using HTTP, HTTPS, FTP and so on). Based on the URI supplied to WebRequest.Create(Uri uri), the method will determine the appropriate kind of request to make, and create and return the corresponding object.

Now that we have our request, we can send it and obtain a response with:

WebResponse response = request.GetResponse();

This opens the connection to the server, streams the request to it, and then captures the sever’s response. We can access this response as a stream (similar to how we would read a file):

using Stream responseStream = response.GetStream() 
{
  StreamReader reader = new StreamReader(responseStream);
  string responseText= reader.ReadToEnd();
  Console.WriteLine(responseText);
}

You likely are wondering what the using and curly braces {} are doing in this code. They are there because the Stream object implements the IDisposable interface. We’ll discuss this in detail in the next section. But for now, let’s focus on how we use the stream. First we create a StreamReader to read it:

  StreamReader reader = new StreamReader(responseStream);

Then read to the end of the stream:

  string responseFromServer = reader.ReadToEnd();

And write the response’s text to the console:

  Console.WriteLine(responseText);

Finally, we must close the WebResponse object’s connection to the server once we are done:

  response.Close();

This last step is important, as the open connection is actually managed by our operating system, and unless we close it, our system resources will be tied up, making our computer slower and potentially unable to make web requests for other programs (including your browser)!

Working With Disposables

In the previous section, we looked at a line of code that included the keyword using in a way you haven’t probably seen it before:

using Stream responseStream = response.GetStream() 
{
  // TODO: Use the responseStream
}

Let’s examine this statement in more detail. This use of using is a using statement, not to be confused with a using directive.

When you put a statement like using System.Text, you are using the using directive, which instructs the compiler to use types in the corresponding namespace without needing to provide the fully qualified namespace. You’ve been using this technique for some time, so it should be familiar to you.

In contrast, the using statement is used in the body of your code in conjunction with an object implementing the IDisposable interface. Objects that implement this interface have a Dispose() method, which needs to be called when you are done with them. These kinds of objects typically access some resource from outside of the program, which needs to be released when you are done with it.

Managed vs. Unmanaged Resources

To understand this better, let’s talk about managed vs. unmanaged resources. We say a resource is managed when obtaining and releasing it is handled by the language. Memory is a great example of this. In C#, we are using managed memory. When we invoke a constructor or declare an array, the interpreter automatically creates the memory we need to hold them.

In contrast, C uses unmanaged memory. When we want to create an array, we must allocate that memory with alloc(), calloc(), or malloc() function call.

This might not seem very different, until we are done with the array. In C#, we can simply let it fall out of scope, knowing the garbage collector should eventually free that memory. But in a C program, we must manually free the memory with a call to free().

Sometimes in C#, we need to access some resource in a way that is unmanaged - in which case, we must be sure to free the resource when we are done with it.

IDisposable

The IDisposable() interface provides a standard way of handling this kind of situation. It requires any class implementing it to define a Dispose() method that frees any unmanaged resources. A stream (the data being read in from a file, the network, or a terminal) is a good example of an unmanaged resource - the stream is actually created by the operating system, and the Stream object (a FileStream, BufferedStream, etc) is a C# object providing access to it.

Let’s focus on a FileStream for a moment. One is created every time you ask the operating system to open a file, i.e.:

FileStream fs = File.OpenRead("somefile.txt");

The File.OpenRead() method asks the operating system to provide a stream to the file named "somefile.txt". We can then read that stream until we reach the end of file marker (indicating we’ve read the entire file):

byte data = fs.ReadByte();
// Invariant: while there are bytes in the file to read
while(data != -1)
{
  // Write the current byte to the console
  System.Out.Write(data);
  // Read the next byte
  data = fs.ReadByte();
}

Once we’ve finished reading the file, we need to call Dispose() on the stream to tell the operating system that we’re done with it:

fs.Dispose();

If we don’t, then the operating system will assume we’re still working with the file, and refuse to let any other program read it. Including our own program, if we were to run it again.

But what happens if an error occurs while reading the file? We’ll never reach the call to Dispose(), so we’ll never free the file! In order to access it, we’d have to restart the computer. Not great.

We could manage this with a try/catch/finally, i.e.:

try 
{
  FileStream fs = File.OpenRead("somefile.txt");
  byte data = fs.ReadByte();
  // Invariant: while there are bytes in the file to read
  while(data != -1)
  {
    // Write the current byte to the console
    System.Out.Write(data);
    // Read the next byte
    data = fs.ReadByte();
  }
  fs.Dispose();
}
catch(Exception e) 
{
  // Do something with e
}
finally
{
  fs.Dispose();
}

But you have to catch all exceptions.

Using Statement

A using statement operates similarly, but takes far less typing:

using FileStream fs = File.OpenRead("somefile.txt")
{
  byte data = fs.ReadByte();
  // Invariant: while there are bytes in the file to read
  while(data != -1)
  {
    // Write the current byte to the console
    System.Out.Write(data);
    // Read the next byte
    data = fs.ReadByte();
  }
}

It also comes with some benefits. One, it creates a new scope (within the {} following the using statement). If for some reason the stream can’t be opened, this scope is skipped over. Similarly it jumps execution to the end of the scope if an error occurs. Finally, it automatically calls Dispose() when the scope ends.

Syntax Shorthand

As of C# 8.0, a shorthand for the using statement that omits the scope markers (the {}) is available. In this case, the scope is from the start of the using statement to the end of its containing scope (usually a method):

using FileStream fs = File.OpenRead("somefile.txt");
byte data = fs.ReadByte();
// Invariant: while there are bytes in the file to read
while(data != -1)
{
  // Write the current byte to the console
  System.Out.Write(data);
  // Read the next byte
  data = fs.ReadByte();
}

This format can be nice when you need to nest multiple using statements, but I would suggest sticking with the scoped version until you are comfortable with the concepts involved.

Parsing API Data

Web APIs typically provide their data in a structured format, i.e. XML or JSON. To use this within a C# program you’ll need to either parse it or convert it into an object or objects.

The Joke of the Day API can provide either - we just need to specify our preference with a Accept header in our HTTP request. This header lets the server know what format(s) of data we are ready to process. XML is signified by the MIME type application/xml and JSON by application/json.

To set this (or any other header) in our WebRequest object, we use the Header property’s Add() method:

WebRequest request = WebRequest.Create("http://api.jokes.one/jod");
request.Headers.Add("Accept", "application/json");

For JSON, or:

WebRequest request = WebRequest.Create("http://api.jokes.one/jod");
request.Headers.Add("Accept", "application/xml");

For XML.

Parsing XML

Let’s start by examining the older format, XML. Assuming you have set the Accept header as discussed above, you will receive a response similar to (but with a different joke):

<response>
  <success>
    <total>1</total>
  </success>
  <contents>
    <jokes>
      <description>Joke of the day </description>
      <language>en</language>
      <background/>
      <category>jod</category>
      <date>2021-11-29</date>
      <joke>
        <title>Signs for every job</title>
        <lang>en</lang>
        <length>1749</length>
        <clean>0</clean>
        <racial>0</racial>
        <date>2021-11-29</date>
        <id>HqJ1i9L1ujVCcZmS5C4nhAeF</id>
        <text>
        In the front yard of a funeral home, "Drive carefully, we'll wait." On an electrician's truck, "Let us remove your shorts." Outside a radiator repair shop, "Best place in town to take a leak." In a non-smoking area, "If we see you smoking, we will assume you are on fire and take appropriate action." On a maternity room door, "Push, Push, Push." On a front door, "Everyone on the premises is a vegetarian except the dog." At an optometrist's office, "If you don't see what you're looking for, you've come to the right place." On a taxidermist's window, "We really know our stuff." On a butcher's window, "Let me meat your needs." On a butcher's window, "You can beat our prices, but you can't beat our meat." On a fence, "Salesmen welcome. Dog food is expensive." At a car dealership, "The best way to get back on your feet - miss a car payment." Outside a muffler shop, "No appointment necessary. We'll hear you coming." In a dry cleaner's emporium, "Drop your pants here." On a desk in a reception room, "We shoot every 3rd salesman, and the 2nd one just left." In a veterinarian's waiting room, "Be back in 5 minutes. Sit! Stay!" At the electric company, "We would be delighted if you send in your bill. However, if you don't, you will be." In a Beauty Shop, "Dye now!" In a Beauty Shop, "We curl up and Dye for you." On the side of a garbage truck, "We've got what it takes to take what you've got." (Burglars please copy.) In a restaurant window, "Don't stand there and be hungry, come in and get fed up." Inside a bowling alley, "Please be quiet. We need to hear a pin drop." In a cafeteria, "Shoes are required to eat in the cafeteria. Socks can eat any place they want."
        </text>
      </joke>
    </jokes>
    <copyright>2019-20 https://jokes.one</copyright>
  </contents>
</response>

We can parse this response with C#’s XmlDocument Class from the System.Xml namespace. First, we create an instance of the class, using our response text. We can use one of the XmlDocument.Load() overrides, which takes a stream, to process our response stream directly:

using Stream responseStream = response.GetStream() 
{
  XmlDocument xDoc = new XmlDocument();
  xDoc.Load(responseStream);
  // TODO: get our joke!
}

Then we can query the XmlDocument for the tag we care about, i.e. response > contents > jokes > joke > text (the text of the joke). We use XPath syntax for this:

  var node = xDoc.SelectSingleNode("/response/contents/jokes/joke/text");

XPath is a query language, much like CSS selectors, which allow you to navigate a XML document in a lot of different ways. In this case, we are just finding the exact element based on its path. Then we can pull its value, and do something with it (such as logging it to the console):

  Console.WriteLine(node.InnerText);

Parsing JSON

JavaScript Object Notation (JSON) has become a popular format for web APIs, as it usually requires less characters than the corresponding XML, and is natively serializable from JavaScript making it extremely compatible with client-side web applications.

Assuming you have set the Accept header as discussed above, you will receive a response similar to (but with a different joke):

{
  "success":{
    "total":1
  },
  "contents":{
    "jokes":[
      {
        "description":"Joke of the day ",
        "language":"en",
        "background":"",
        "category":"jod",
        "date":"2021-11-30",
        "joke":{
          "title":"Class With Claus",
          "lang":"en",
          "length":"78",
          "clean":null,
          "racial":null,
          "date":"2021-11-30",
          "id":"LuVeRJsEIzCzvTnRmBTHXweF",
          "text":"Q: What do you say to Santa when he's taking attendance at school?\nA: Present."
        }
      }
    ],
    "copyright":"2019-20 https:\/\/jokes.one"
  }
}

The C# system libraries provide JSON support in the System.Text.Json namespace using the JsonSerializer class. The default behavior of the deserializer is to deserialize into a JsonDocument composed of nested JsonElement objects - essentially, dictionaries of dictionaries. As with the XDocument, we can deserialize JSON directly from a Stream:

using Stream responseStream = response.GetStream() 
{
  JsonDocument jDoc = JsonSerializer.Deserialize(responseStream);
  // TODO: get our joke!
}

Then we can navigate from the root element (a JsonElement instance) down the nested path of key/value pairs, by calling GetProperty() to access each successive property, and then print the joke text to the console:

  var contents = jDoc.RootElement.GetProperty("contents");
  var jokes = contents.GetProperty("jokes");
  var jokeData = jokes[0];
  var joke = jokeData.GetProperty("joke");
  var text = joke.GetProperty("text");
  Console.WriteLine(text);

Asynchronous Requests

Now that we’re more comfortable with using statements, let’s return to our request-making code:

WebRequest request = WebRequest.Create("http://api.jokes.one/jod");
using Stream responseStream = response.GetStream() 
{
  StreamReader reader = new StreamReader(responseStream);
  string responseText= reader.ReadToEnd();
  Console.WriteLine(responseText);
}
response.Close();

The response.GetStream() triggers the http request, which hits the API and returns its result. Remember a HTTP request is streamed across the internet, then processed by the server, and the response streamed back. That can take some time (at least to a computer). While the program waits on it to finish, it cannot do anything else. For some programs, like one that only displays jokes, this is fine. But what if our program needs to also be responding to the user’s events - like typing or moving the mouse? While the program is waiting, it is effectively paused, and nothing the user does will cause the program to change.

Asynchronous Methods

This is where asynchronous methods come in. An asynchronous method operates on a separate thread, allowing execution of the program to continue.

let’s revisit our WebRequest example:

WebRequest request = WebRequest.Create("http://api.jokes.one/jod");

We can then make the request asynchronously by calling the asynchronous version of GetResponse() - GetResponseAsync():

WebResponse response = await request.GetResponseAsync();

The await keyword effectively pauses this thread of execution until the response is received. Effectively, the subsequent code is set aside to be processed when the asynchronous method finishes or encounters an error. This allows the main thread of the program to continue responding to user input and other events. The rest of the process is handled exactly as before:

using Stream responseStream = response.GetStream() 
{
  StreamReader reader = new StreamReader(responseStream);
  string responseText= reader.ReadToEnd();
  Console.WriteLine(responseText);
}

Writing Asynchronous Methods

Normally we would wrap the asynchronous method calls within our own asynchronous method. Thus, we might define a method, GetJoke():

public string async GetJoke()
{
  WebRequest request = WebRequest.Create("http://api.jokes.one/jod");
  WebResponse response = await request.GetResponseAsync();
  using Stream responseStream = response.GetStream() 
  {
    XmlDocument xDoc = new XmlDocument();
    xDoc.Load(responseStream);
    var node = xDoc.SelectSingleNode("/response/contents/jokes/joke/text");
    return node.InnerText;
  }
  return "";
}

Asynchronous ASP Request/Response Methods

ASP.Net includes built-in support for asynchronous request handling. You just need to add the async keyword to your OnGet() or OnPost() method, and the ASP.NET server will process it asynchronously.

For example, we could invoke our GetJoke() method in a OnGet():

public class JokeModel : PageModel 
{
    public async IActionResult OnGet()
    {
        var joke = await GetJoke();
        return Content(joke);
    }
}

This will cause the text of the joke to be sent as the response, and allow other pages to be served while this one request is awaiting a response from the Joke API.

RESTful Routes

Many web applications deal with some kind of resource, i.e. people, widgets, records. Much like in object-orientation we have organized the program around objects, many web applications are organized around resources. And as we have specialized ways to construct, access, and destroy objects, web applications need to create, read, update, and destroy resource records (we call these CRUD operations).

In his 2000 PhD. dissertation, Roy Fielding defined Representational State Transfer (REST), a way of mapping HTTP routes to the CRUD operations for a specific resource. This practice came to be known as RESTful routing, and has become one common strategy for structuring a web application’s routes. Consider the case where we have an online directory of students. The students would be our resource, and we would define routes to create, read, update and destroy them by a combination of HTTP action and route:

CRUD OperationHTTP ActionRoute
CreatePOST/students
Read (all)GET/students
Read (one)GET/students/[ID]
UpdatePUT or POST/students/[ID]
DestroyDELETE/students/[ID]

Here the [ID] is a unique identifier for the individual student. Note too that we have two routes for reading - one for getting a list of all students, and one for getting the details of an individual student.

REST is a remarkably straightforward implementation of very common functionality, no doubt driving its wide adoption. Razor pages supports REST implicitly - the RESTful resource is the PageModel object. This is the reason model binding does not bind properties on GET requests by default - because a GET request is used to retrieve, not update a resource. However, properties derived from the route (such as :ID in our example, will be bound and available on GET requests).

With Razor Pages, you can specify a route parameter after the @page directive. I.e. to add our ID parameter, we would use:

@page "{ID?}"

We can use any parameter name; it doesn’t have to be ID (though this is common). The ? indicates that the ID parameter is optional - so we can still visit /students. Without it, we only can visit specific students, i.e. /students/2.

Route parameters can be bound using any of the binding methods we discussed previously - parameter binding, model binding, or be accessed directly from the @RouteData.Values dictionary. When we are using these parameters to create or update new resources, we often want to take an additional step - validating the supplied data.

Summary

In this chapter we explored using web APIs to retrieve data from remote servers. We saw how to use the WebRequest object to make this task approachable. We also revisited ideas you’ve seen in prior courses like the IDisposable interface and using statements to work with unmanaged objects. We saw how to consume XML data we receive as a response from a web API.

We also discussed using async methods to allow our programs to continue to respond to user input and incoming web requests while processing long-running tasks in parallel. Finally, we discussed RESTful routes, a standardized way of determining the routes for your web API.

Chapter 5

LINQ

Bridging the Gap Between Databases and Data Structures

Subsections of LINQ

Introduction

We saw in the last chapter how many web applications are built around the concept of resources. An online store has products to sell. A social media application has people with accounts. Resource-oriented web applications therefore need to store these resources in some fashion - possibly as objects in some kind of collection, or possibly with a database.

Key Terms

Some key terms to learn in this chapter are:

  • Database
  • Relational Database
  • Object-Oriented Database
  • Document-Based Database
  • Structured Query Language (SQL)
  • Language Integrated Query (LINQ)
  • Anonymous Types
  • Extension Methods

Key Skills

The key skills to develop in this chapter are:

  • Using LINQ to perform query operations on data sets
  • Writing extension methods to provide new functionality to existing classes

Databases

We use the term “Database” to describe a program primarily designed to efficiently store and retrieve data. These are often used in conjunction with other kinds of applications - such as web applications or desktop applications. Why use a separate program rather than building this storage and retrieval into the application we are building? There are a lot of possible reasons, but some common ones are:

  1. Centralized access. Consider a desktop application that tracks inventory for a large company. You might want to have the application installed on every computer in the sales department, and you want the inventory to stay up-to-date for all of these. If each desktop app connects to the same centralized database, they will all be kept up-to-date.

  2. Efficiency of specialization. By designing a database program for the sole purpose of storing and retrieving data, it can be designed by programmers who specialize in this area, and be designed to be as efficient as possible… often far more than we would be able to build into our own applications.

Relational Databases

The most common type of database in use today are relational databases. These are databases that organize data into structured tables consisting of columns with a specific data type. Each record is therefore a row in the database. Visually, such a database appears much like a spreadsheet. These databases are typically communicated with using Structured Query Language (SQL). This is a special programming language developed specifically to express the kinds of operations we need to carry out on these databases.

For example, to retrieve the first and last names of all students in the students table who are earning above a 3.0, we would use the query:

SELECT first, last FROM students WHERE gpa > 3.0;

And the results would be provided as a stream of text:

first | last
Mike | Rowe
Jan | Herting
Sam | Sprat

You’ll learn more about these databases in CIS 560.

Object-Oriented Databases

Relational databases were developed in the 60’s and 70’s to deal with the specific challenges of computer systems in those days. The way they organize data lends itself to large, flat files, and SQL and its results are handled as streams of data. When object-orientation became a popular programming language paradigm, object-oriented databases - databases that store data as objects, were also developed. However, while object-oriented programming languages became quite popular, the corresponding databases failed to. As a result, object-oriented databases remain quite niche.

Document-Based Databases

The popularity of JSON and XML with the web led to another category of databases being developed, document-based databases (sometimes called No-SQL databases). These databases store records in a serialized format like JSON or XML, and downplay relationships between records. Unlike relational databases, document-based databases can be flexible in what data exists for an individual record.

For example, if we were to create a Student table in a relational database, and wanted to hold the student’s degree plan, we’d need two columns to represent a dual-major. All students in the table would have two columns, even if they only had one degree plan. In contrast, with a document-based database most students would only have one degree plan, but dual major would have two, and if we had a rare triple major, they could have three!

Thus, document-based databases add flexibility at the cost of some efficiency in retrieval.

Object-Relational Mapping

Despite the growing popularity of Document-Based Databases, the vast majority of databases remain relational (MySql, MSSQL, PostgreSQL, Oracle, SQLite). Yet, most of the programs that utilize them are now object-oriented. This creates a bit of a disconnect for many programmers working with both. One of the solutions devised to help with this challenge are object-relational mappers (ORMs). These are libraries that are written for object-oriented languages that facilitate talking to the relational database. When a query is run against the database with an ORM, the results are provided as a collection of objects instead of the normal text (basically, the ORM parses the results and transforms them into an object representation). This simplifies working with databases for the programmer.

LINQ

One solution that Microsoft has developed to tackle the disconnect between relational databases and C# is Language Integrated Query (LINQ). This technology integrates querying directly into the C# language. It can act as a bridge to a MS SQL server, replacing the need for writing SQL queries and processing the results. But it can also be used to query collections that implement the IEnumerable interface, as well as XML files. As such, we can design the logic of our program to use LINQ, and change out the data source for any of these options with minimal refactoring.

LINQ queries are written as either query expressions or by using query operators. Let’s examine the two.

Query Expressions

LINQ query expressions appear much like SQL statements. For example, the query from the last section, selecting the names of students whose GPA is greater than 3.0 as a LINQ query expression would be:

var highGPAStudents = from student in students where student.GPA > 3.0 select new { First = student.First, Last = student.Last};

This format looks much like SQL, and is often more comfortable for programmers who come from a SQL background.

Query Operators

LINQ queries are actually implemented through C# operators. Query expressions, like the one above, are compiled into an equivalent series of query operators. Programmers can also use these operators directly. For example, the above query expressed using operators would be:

var highGPAStudents = students.Where(student => student.GPA > 3.0).Select(student => new {First = student.First, Last = student.Last});

Programmers from an object-oriented background often find the operators (which are essentially methods that take lambda expressions as parameters) more comfortable.

Query Execution

Queries are not actually executed until you start iterating over their results. This allows you to chain additional queries on an existing query, and allows the compiler to optimize queries by grouping query expressions together. Consider this compound query to select half of low-performing students to assign to an advisor:

var strugglingStudents = from student in students where student.GPA < 2.0 select student;
var strugglingStudentsAtoN = from strugglingStudents where student.Last.CharAt(0) >= 'A' && student.Last.CharAt(0) < 'N' select student;

If we wrote this as C# algorithm, we might do something like:

var strugglingStudents = new List<Student>();
foreach(var student in students) {
    if(student.GPA < 2.0) strugglingStudents.Add(student);
}
var strugglingStudentsAtoN = new List<Student>();
foreach(var student in strugglingStudents) {
    if(student.Last.CharAt(0) >= 'A' && student.Last.CharAt(0) < 'N') strugglingStudentsAtoN.Add(student);
}

As you can see, this results in two iterations over lists of students. In the worst case (when every student is struggling) this requires 2*n operations.

On the other hand, by delaying the execution of the query until the first time its values are used, LINQ can refactor the query into a form like:

var strugglingStudentsAtoN = new List<Student>();
foreach(var student in students) {
    if(student.GPA < 2.0 && student.Last.CharAt(0) >= 'A' && student.Last.CharAt(0) < 'N') 
        strugglingStudents.Add(student);
}

With this refactoring, we only need one iteration over our list - our query would run twice as fast! Also, if we never use strugglingStudentsAtoN, the query is never executed, so the cost is constant time. This might seem nonsensical, but consider if we have some kind of conditional, i.e.:

switch(advisor.Number) {
    case 1:
        ReportStudents(strugglingStudentsAtoN);
        break;
    case 2: 
        ReportStudents(strugglingStudentsNtoZ);
        break;
}

We only end up executing the query necessary for the logged-in advisor.

Info

LINQ uses a programming pattern known as method chaining, where each query method returns an object upon which additional query operations can be performed. Thus, it is perfectly legal to write a query like:

var query = students.Where(s => s.GPA > 2.0).Where(s => s.Age > 25).Where(s => s.Last.CharAt(0) == 'C');

While this may seem silly (as we could have expressed this with one where clause), it makes more sense when we have user interface filters that may or may not have a value, i.e.:

var query = students.All();
if(minGPA != null) query = query.Where(s => s.GPA >= minGPA);
if(maxGPA != null) query = query.Where(s => s.GPA <= maxGPA);
if(first != null) query = query.Where(s => s.First == first);
if(last != null) query = query.Where(s => s.Last == last);

Query Results

The result of the query is therefore a specialized object created by LINQ that implements the IEnumerable<T> interface. The type of T depends on the query (queries are always strongly typed, though the type can be inferred). For example, in our strugglingStudents query, the result type is IEnumerable<Student>:

var strugglingStudents = from student in students where student.GPA < 2.0 select student;

In contrast, the highGPAStudents result uses an anonymous type:

var highGPAStudents = from student in students where student.GPA > 3.0 select new { First = student.First, Last = student.Last};

The anonymous type is created by the expression new { First = student.First, Last = student.Last}. Basically, it’s an object with a First and Last property (and no methods or other properties). Anonymous types are created by the interpreter at runtime (much like an auto-Property’s backing field). As such, we aren’t able to use its type in our code.

If we want the query to return a specific type, we can instead declare a struct or object to return, i.e.:

class StudentName {
    public string First;
    public string Last;
    public StudentName(string first, string last) {
        First = first;
        Last = last;
    }
}

And then set this as the projection type:

var highGPAStudents = from student in students where student.GPA > 3.0 select new StudentName(student.First, student.Last);

Let’s take a deeper look at LINQ syntax next.

LINQ Syntax

Let’s discuss some of the more common operations we might want to perform with LINQ. Please note that this is not an exhaustive list - just some of the most common operations you will be encountering at this time.

Data Source

For these examples, we’ll be using a data source consisting of Student objects. These are defined by the class:

public class Student {
    public string EID;
    public string First;
    public string Last;
    public double GPA;
    public int Age;    
    public string Major;
}

And the variable students is a List<Student> that we can assume is populated with initialized student objects.

We select this as our data source with the LINQ from operator. In query syntax, this would be:

var query = from student in students ...

And with method syntax, we would simply use the students list:

var query = students.SomeQueryMethod(student => ...)

To create a query from a data source using method syntax without applying any query methods (useful for chaining optional queries), we can invoke All() on the collection:

var query = students.All();

To use a different data source, we would just swap students for that source, an object that supports either the IEnumerable (usually data structures) or IQueryable (typically SQL or XML data sources) interface.

Projecting

Projecting refers to selecting specific data from a data source. For example, if we wanted to select the full name of every Student, we could do so with this query syntax:

var studentNames = from student in students select $"{student.First} {student.Last}";

Or with method syntax:

var studentNames = students.Select(student => $"{student.First} {student.Last}");

As the name is simply a string, the select above simply constructs the string, and the type of studentNames is inferred to be IEnumerable<string>.

We can also project an anonymous type. This is a special kind of object whose type is created at runtime. Anonymous types are basically collections of properties and nothing more (they cannot have methods). For example, if we wanted just the student’s full name and age, we would use this query syntax:

var studentInfo = from student in students select new {FullName = $"{student.First} {student.Last}", Age = student.Age};

or this method syntax:

var studentInfo = students.Select(student => new {FullName = $"{student.First} {student.Last}", Age = student.Age});

Finally, we could also define a new data type (i.e. class) and create an instance of it as our projection:

class StudentInfo {
    public string FullName {get; set;}
    public int Age {get; set;}
    public StudentInfo(string fullName, int age)
    {
        FullName = fullName;
        Age = age;
    }
}

Using query syntax:

var studentInfo = from student in students select new StudentInfo($"{student.First} {student.Last}", student.Age);

or this method syntax:

var studentInfo = students.Select(student => new StudentInfo($"{student.First} {student.Last}", student.Age));

Filtering

One of the most common operations you will do with a query is filter the data, so the results contain only part of the original data. This is done with the where operator takes a statement that resolves to a boolean. If this boolean expression resolves to true, then the data is included in the results; if it is false, it is excluded. For example, to find all students older than 25, we would use this query syntax:

var olderStudents = from student in students where student.Age > 25 select student;

or this method syntax:

var olderStudents = students.Where(student => student.Age > 25);

Filtering By Type

If we have a list that contains multiple types, we can filter for specific types with the where operator or the OfType operator (this is an instance where query and operator syntax vary more greatly). Consider the case where our Student class is a base class to GraduateStudent and UndergraduateStudent classes. If we wanted to get a list of only the undergraduates, we could use a where query syntax combined with an is casting test:

var undergraduates = from student in students where student is UndergraduateStudent select student;

In this case, the result would be an IEnumerable<UndergraduateStudent>. But the corresponding where in operator syntax would result in an IEnumerable<Student> that contained only UndergraduateStudent objects. To perform a cast as part of the filtering, we would instead use the OfType<T>() method:

var undergraduates = students.OfType<UndergraduateStudent>();

Ordering

Often we want to apply some form of sorting to our results, i.e. we might want to sort students by GPA. This can be done with an orderby operator. In query syntax it would be:

var studentsByGPA = from student in students orderby student.GPA select student;

And in method syntax:

var studentsByGPA = students.OrderBy(student => student.GPA);

The orderby operator sorts in ascending order (so students with the lowest grades would come first in the list). If we wanted to sort in descending order, we would need to specify descending order in our query syntax:

var studentsByGPA = from student in students orderby student.GPA descending select student;
Tip

There is also an ascending keyword you can use. This is helpful if you can’t remember the default or want to make it clear to other programmers that the list will be sorted in ascending order:

var studentsByGPA = from student in students orderby student.GPA ascending select student;

However, in method syntax this is accomplished by a separate operator, the OrderByDescending() method:

var studentsByGPA = students.OrderByDescending(student => student.GPA);

If we need to order by multiple properties, i.e. first and last names, this is accomplished by a comma-separated list in query syntax:

var studentsByName = from student in students orderby student.Last, student.First select student;

But in method syntax, we need to use a ThenBy() operator for subsequent sorting options:

var studentsByName = students.OrderBy(student => student.Last).ThenBy(student => student.First);

We can mix and match ascending and descending sorting as well - for example, to sort students by descending GPA, then by names in alphabetical order we would use the query syntax:

var studentsByGPAAndName = from student in students orderby student.GPA descending, student.Last, student.First select student;

The corresponding method syntax would need separate operators for each sorting:

var studentsByGPAAndName = students.OrderByDescending(student => student.GPA).ThenBy(student => student.Last).ThenBy(student => student.First);

There is also a ThenByDescending() operator for chaining descending sorts.

Finally, there is also a Reverse() operator which simply reverses the order of items in the collection without sorting.

Grouping

We often want to split our results into groups, which can be accomplished with the group by operator. Consider the case where we want to split our students by the value of their Major field. We can accomplish this with query syntax:

var studentsByMajor = from student in students group student by student.Major select student;

or using method syntax:

var studentsByMajor = students.GroupBy(student => student.Major);

The result type of a grouping operation is an IEnumerable<IGrouping<TKey, TSource>>; the IGrouping is essentially a key/value pair with the key being the type we were grouping by. In the example it would be IEnumerable<IGrouping<string, Student>> (Seeing this, you can probably begin to appreciate why we normally use var for query variables).

To print out each student in each category, we’d need to iterate over this collection, and then over the groupings:

foreach(var group in studentsByMajor) {
    Console.WriteLine($"{group.Key} Students");
    foreach(var student in group) {
        Console.WriteLine($"{student.First} {student.Last}");
    }
}

Paging

A common strategy with large data sets is to separate them into pages, i.e. the first 20 items might appear on page 1, and by clicking the page 2 link, the user could view the next twenty items, and so on. This paging functionality is implemented in LINQ using the Skip() and Take() operators. The Skip() operator specifies how many records to skip over, while the Take() operator indicates how many records to include. Thus, to take the second page of students when each page displays twenty students, we would use:

var pagedStudents = students.Skip(20).Take(20);

Note that there is no query syntax corresponding to the Skip() and Take() operations, so to use them with query syntax, we wrap the query in parenthesis and invoke the methods on the result. I.e. sorting students alphabetically and then taking the third page of twenty would be:

var pagedSortedStudents = (from student in students orderby last, first select student).Skip(40).Take(20);

Existence Checks

Sometimes we want to know if a particular record exists in our data source. The Any() operator can be used to perform such a check. It evaluates to true if the query has any results, or false if it does not. Like the Skip() and Take(), it does not have a query syntax form, so it must be invoked using the method syntax. For example, to determine if we have at least one student named Bob Smith, we could use:

var bobSmithExists = (from student in students where student.First == "Bob" && student.Last == "Smith" select student).Any();

Or, in method syntax:

var bobSmithExists = students.Any(student => student.First == "Bob" && student.Last == "Smith");

Alternatively, if we wanted to retrieve Bob Smith’s record instead of simply determining if we had one, we could use First():

var bobSmith = (from student in students where student.First == "Bob" && student.Last == "Smith" select student).First();

or in method syntax:

var bobSmith = students.First(student => student.First == "Bob" && student.Last == "Smith");

This evaluates to the first matching result of the query (if we have multiple Bob Smiths, we’ll only get the first one). If there is no matching record, an InvalidOperationException is thrown. Alternatively, we can use FirstOrDefault() which returns a default value corresponding to the query data type. For classes, this would be null, so given this query:

var bobSmith = students.FirstOrDefault(student => student.First == "Bob" && student.Last == "Smith");

The value of bobSmith would be his record (if he is in the collection) or null (if he was not).

Aggregating

Sometimes we want to perform aggregate operations upon a data source, i.e. counting, summing, or averaging. As with paging, these are accomplished via method-only operators. For example, to count all students in our data source, we could use:

var studentCount = students.Count();

This can be combined with any LINQ query, i.e. the count of students with a GPA above 3.0 in query syntax would be:

var studentsAbove3GPA = (from student in students where student.GPA > 3.0 select student).Count();

or in method syntax:

var studentsAbove3GPA = students.Where(student => student.GPA > 3.0).Count();

Similarly, to compute the average GPA we would use the Average() method in conjunction with a projection. In query syntax:

var averageGPA = (from student in students select student.GPA).Average();

or in method syntax:

var averageGPA = students.Select(student => student.GPA).Average();

or we can move the selector Predicate into the Average() directly:

var averageGPA = students.Average(student => student.GPA);

We can create more complex queries to address specific questions. For example, with a group by we could compute the average GPA by major:

var gpaByMajor = from student in students group student by student.Major into majorGroup select new 
    { 
        Major = majorGroup.Key,
        AverageGPA = majorGroup.Average(student => student.GPA);
    }

The Sum() operator works similarly, summing a value. To sum the ages of all students, we could use:

var sumOfAges = (from student in students select student.Age).Sum();

or

var sumOfAges = students.Select(student => student.Age).Sum();

or

var sumOfAges = students.Sum(student => student.Age);

There are also Min() and Max() aggregate operators which select the minimum and maximum values, respectively. For example, we could find the maximum and minimum earned GPAs with:

var minGPA = students.Min(student => student.GPA);
var maxGPA = students.Max(student => student.GPA);

Finally, there is a generic Aggregate() method which provides an aggregator variable that we can use to build any kind of aggregate function. Let’s first see how it can be used to duplicate the functionality of the Sum() method:

var sumOfAges = students.Aggregate((sum, student) => sum + student.Age);

Here, sum is inferred to be an int, as student.Age is an int. So it starts at 0, and each time the Aggregate method processes a student, it adds that student’s Age into the sum.

Now let’s use this method for something new - generating a string of email addresses for a bulk mailing. Assume our email application needs a list of semicolon-separated email addresses. In that case, we could generate the emails for all students from:

var emails = students.Aggregate((emails, student) => emails + $"; {student.EID}@k-state.edu");

If we had students with EIDs “mary”, “brb30”, and “stan”, the resulting string would be:

mary@k-state.edu; brb30@k-state.edu; stan@ksu.edu
Info

You may have heard the term map/reduce in the context of functional programming or big data. This is an algorithmic approach to processing data. This pattern can be duplicated in LINQ using a query as the mapping function, and Aggregate() as the reduce function.

Extension Methods

In order to use LINQ with your IEnumerable collections, you must include this using statement:

using System.LINQ 

Without it, the LINQ collection methods will not be available. You might be wondering why, as your collections are mostly defined in the System.Collections or System.Collections.Generic namespaces.

The answer is that LINQ on collections is implemented using extension methods. This is a C# feature that allows you to add methods to any class, even a sealed class. But how can we add methods to a sealed class? Isn’t the point of sealing a class to prevent altering it?

The answer is that extension methods don’t actually modify the class itself. Instead, they make available additional methods that the compiler “pretends” are a part of the class. But these are defined separate from the class, and cannot modify it, nor access private or protected members of the class. As the LINQ extension methods are defined in the System.LINQ namespace, we must make them available with a using statement before the compiler and intellisense will let us use them.

Let’s see an example of creating our own extension methods. As programmers, we often use class names in Pascal case, that might be useful to convert into human-readable strings. Let’s write an extension method to do this transformation:

using System.Text;

namespace StringExtensions {
    
    /// <summary>
    /// Converts a camel-case or pascal case string into a human-readable one 
    /// </summary>
    public static string Humanize(this String s)
    {
        StringBuilder sb = new StringBuilder();
        int start = 0;
        for(int i = 1; i < s.Length; i++) 
        {
            // An upper case character is the start of a new word
            if(Char.IsUpper(s[i]))
            {
                // So we'll add the last word to the StringBuilder
                string word = s.Substring(start, i - start);
                sb.Append(word);
                // Since that wasn't the last word, add a space 
                sb.Append(" ");
                // Mark the start of the new word 
                start = i;
            }
        } 
        // We should have one more word left 
        sb.Append(s.Substring(start));
        return sb.ToString();
    }

}

Notice a couple of important features. First, the method is defined as static. All extension methods are static methods. Second, note the use of this in the first parameter, this string s. The use of the this keyword is what tells C# the method is an extension method. Moreover, it indicates the class that is being extended - String (which is equivalent to string).

Other than these two details, the rest of the method looks much like any other method. But any time this method is in scope (i.e. within the StringExtensions namespace, or in any file with a using StringExtensions statement), there will be an additional method available on string, Humanize().

That’s all there is to writing an extension method. Go ahead and try writing your own to convert human-readable strings into Pascal or Camel case!

Summary

In this chapter we learned about LINQ, Microsoft’s query technology for querying collections, relational databases, and XML files (along with community implementations for many other queryable things). We saw how to use both query and method syntax provided by LINQ to perform common query operations. Finally, we examined the mechanism LINQ uses to provide new functionality to IEnumerable collections - extension methods. We even implemented a few extension methods of our own!

Now you should be ready to use LINQ in your own projects, as well as define your own custom extension methods.

Chapter 6

Deployment

Putting your work out there

Subsections of Deployment

Introduction

Now that we’ve explored building web applications and writing web servers, we’ll explore in more detail how to make them available on the web, a process known as deployment.

Key Terms

Some key terms to learn in this chapter are:

  • Deployment
  • IP Address
  • IPV4
  • IPV6
  • Domain Name
  • Ports
  • Virtual Machine

Key Skills

The key skills to develop in this chapter are:

  • Setting up your web applications to deploy to a virtual machine

Web Addresses

IP Addresses

As we discussed previously, when we visit web pages in our browser, the browser makes a HTTP or HTTPS request against the web server for the resource. For this request to be sent to the right server, we must first determine the server’s address. This address takes the form of an Internet Protocol (IP) address. There are currently two ways these IP addresses are specified, IPv4 and IPv6. An IPv4 address consists of 32 bits split into 8 bit chunks, and is typically expressed as four integers between 0 and 255 separated by periods. For example, the IP address for Kansas State University is:

129.130.200.56

Consider that an IPv4 address consists of 32 bits. This means we can represent 2^{32} unique values, or 4,294,967,296 different addresses. You can see how many websites are currently online at https://www.internetlivestats.com/watch/websites/, as of this writing it was nearly 2 million. Combine that with the fact that every computer connecting to the interent, not just those hosting, must have an ip address, and you can see that we are running out of possible addresses.

This is the issue that IPv6 was created to counter. IPv6 addresses are 128 bits, so we can represent 2^{128} different values, or 340,282,366,920,938,463,463,374,607,431,768,211,456 possible addresses (quite a few more than IPv4)! An IPv6 address is typically expressed as eight groups of four hexidecimal digits, each representing 16 bits. For example, Google’s IPv6 address is:

2607:f8b0:4000:813::200e

IPv6 adoption requires changes to the internet infrastructure as well as all connected machines, and is typically done on a volunteer basis. Thus, it is a gradual process. The United States is at rougly a 47% adoption level; you can see current adoption statistics for all countries at Akamai’s IPv6 Adoption Tool.

Ports

In addition to the address, requests are sent to a port, a specific communication endpoint for the computer. Different applications “listen” at specific ports on your computer - this allows your computer to have multiple applications communicating across the network or interent at the same time. You might think of your computer as an apartment complex; the IP address is its street address, and the ports are individual apartment numbers. Different applications then live in different apartments. Many of these ports are unused (the apartments are vacant), as most computers are only running a few network-facing applications at any given time.

Many applications and communication services have a “default” port they are expected to use. This makes it easy to connect to a remote computer. For example, a webserver uses port 80 by default for HTTP requests, and port 433 for HTTPS requests. If your server is listening at these ports, there is no need to specify the port number in HTTP/HTTPS requests targeting your IP address. You can also run a program on a non-standard port. For example, with our Razor Page applications, we have typically been running our debug version on port 44344 (with IIS Express) or 5001 (when running as a console application). If you know the IP address of your computer, you can visit your running debug server by using the address http://[ip address]:[port] from another computer on your network.

Some of the most common services and thier associated default ports are:

PortProtocolUse
22Secure Shell (ssh)Used to communicate between computers via a comand line interface
25Simple Mail Transfer Protocol (SMTP)A common email routing protocol
53Domain Name System (DNS) ServiceIP lookup by domain name
80Hyper-Text Transfer Protocol (HTTP)Servicing WWW requests
143Post Office Protocol (POP)Retrieving email
143Interent Message Access Protocol (IMAP)Managing email
193Interent Relay Chat (IRC)Chat messages
443Secure Hyper-Text Transfer Protocol (HTTPS)Servicing secure WWW requests

Domain Names

Of course, you don’t normally type 129.130.200.56 into your browser to visit K-State’s website. Instead, you type k-state.edu or ksu.edu, which are domain names owned by the university. A Domain Name is simply a human-readable name that maps to an IP address. This mapping is accomplished by the Domain Name System (DNS), a lookup service composed of a hierarchial network of servers, with the root servers maintained by the Internet Corporation for Assigned Names and Numbers (ICANN).

When you enter a domain name into your browser, it requests the corresponding IP address from its local DNS server (typically maintained by your ISP). If it does not know it, this server can make a request against the broader DNS system. If a matching IP address exists, it is returned to the local DNS server and through that server, your browser. Then your browser makes the request using the supplied IP address. Your browser, as well as your local DNS server, cache these mappings to minimize the amount of requests made against the DNS system.

To get a domain name, you must purchase it from a domain name reseller. These resellers work with ICANN to establish ownership of specific domain names - each domain name can have only one owner, and map to an IP address. Once you’ve purchased your domain name (which is actually leased on an annual basis), you work with the reseller to target the domain name at your preferred IP address(es). This entire process is typically handled through a web application.

Info

The localhost domain name is a special loopback domain. It always points at IP address 127.0.0.1, which is always the local machine (i.e. the computer you are using).

Secure HTTP

We have talked several times about HTTP and HTTPS, without really discussing what is different about these two approaches other than that HTTPS is “secure”. Essentially, HTTPS uses the same protocol as HTTP, but requests and responses are encrypted rather than being sent as plain text. This encryption is handled at a level below HTTP, in the communication layer (currently this uses TLS - Transport Layer Security). This encryption is done through symmetric crypography using a shared secret. You may remember studying this approach in CIS 115. Remember this Computerphile video demonstrating a Diffie-Hellman key exchange by mixing paint colors?

By using this encryption scheme, we make it impossible (or at least very difficult) for third parties intercepting our HTTP requests and responses to determine exactly what they contain. Hence, credit card information, passwords, and other personal information is protected, as are search terms, etc. However, this is only half of the process of HTTPS. The second half involves establishing that the web server you are making requests to is the one you want, and not an impersonator. This requires an authentication process, to ensure you are communicating to the correct server.

This authentication aspect of TLS is managed through security certificates. These are built around public/private key encryption and the X.509 certificate standard. A certificate provides proof the server serving the certificate is the one of the domain address in question. Think of it as a driver’s license or your student ID card - it lists identifying information, and you carry it with you to prove you are who you say you are. And much like a drivers’ license or a student ID card, it is issued by an authoritative source - one of several “trusted” certificate authorites, or an authority whose own certificate is signed by one of these authorities.

Anyone can issue a security certificate, but only one with a chain of signed certificates that goes back to a root trusted certificate authority will be considered “trusted” by your browser. This is why you may have had issues running your web applications using HTTPS - when launching the project in debug mode, it uses a self-signed certificate (i.e. your application creates its own certificate), which the browser reports as untrustworthy. Depending on your browser, you may be able to allow this “untrusted” site to be served, or it may be disallowed completely. Visual Studio and ASP.NET projects typically offer to install a “dev certificate” that allows your localhost communications to treated as trusted.

Info

Traditionally, security certificates are issued from a trusted authority using annual fees, and often accompanied by insurance that pays for legal issues if the certificate is ever violated. However, the Let’s Encrypt Security Group, launched in April 2016, offers free security certificates with the goal of making HTTPS ubiquitious on the web. This easy availablility means there is no reason to not host your websites using HTTPS instead of vulnerable HTTP. You can visit the Let’s Encrypt website at letsencrypt.org.

Web Hosting

To host your website, you will need:

  1. A computer connected to the Internet
  2. A web server program listening at port 80 and/or 433 (depending on if you are using secure communications)
  3. If you are using secure communications, a security certificate issued by a valid authority. Let’s Encrypt offers free certificates, and is a good source for starting web developers
  4. A domain name (while this is technically optional, if you expect users to visit your website you’ll probably need one)

While you have been running your website in debug mode on your development computer, you probably won’t use it to host your actual website on the Internet. First, your machine would need to be running the web server application constantly. Any time your computer was turned off, or the web server was not running, your website would be inaccessible.

Also, in most residental setups, you probably won’t be able to access the running program across the Internet anyway. This is especially if you have multiple computers connected through a router. In that case, only your router has a unique IP address, and all communications are routed through it using that address. The router also assigns “internal” addresses to the computers networked to it, and handles distributing request results to those computers that made them (kind of like a mailroom for a very large institution). To make your website available, you would probably need to set up port forwarding or a similar technique with your router to have it forward requests to the computer running your web server. You probably would also need to modify your firewall settings (firewalls prevent connections against ports that you don’t mean to have open to the Internet).

A third challenge is that you most likely do not have a static IP address, i.e. one that will always point to your computer. Most Internet Service Providers instead typically assign a dynamic IP address to your router or modem when you estabish a connection, drawn from a pool of IP addresses they maintain for this purpose. This address is yours until the next time you connect (i.e. after a power loss or rebooting your router), at which point the ISP assigns you a different IP address from the pool. This simplifies the configuration for their network, and allows them to share IP addresses amongst infrequently-connecting users. Further, most residential ISP plans specifically forbid hosting web applications using thier connection - instead you must sign up for a commercial plan that includes a static IP address.

Remote Machines

Instead, you will probably host your websites on a remote machine running in a server farm (similar to how the CIS Linux servers that you put your personal web pages on in CIS 115). There are several benefits to using a machine in a server farm: typically they are very good machines, optimized for hosting web applications, the server farm has ideal operating conditions (good air conditioning, a backup power system), better uptime, and on-staff IT professionals that keep the equipment in good repair. Of course, you will have to pay fees to have access to a machine. There are typically several options offered by these service providers:

  1. A machine you own (in this case, you would purchase your server hardware, and you would own it). The service maintains your machine and its connection to the internet. This is typically the most expensive option, but is sometimes used when information security is especially paramount.
  2. A machine you rent. In this case, it is owned by the service but made available to you. You get the entire machine for your use.
  3. A shared machine - in this case you are granted access to only part of the computer, where you can store files. In this approach, web hosting is often acccomplished with an Apache server, and each user has a special file (i.e. public_html where they can place files to be served statically). This is the approach our department uses for your access to the CS Linux server.
  4. A virtual machine - this is also a shared access to a computer, but mediated through a virtual machine - a virtual computer running in the real computer. A computer can host an arbitrary number of virtual machines. In this case, you have complete access to your virtual machine.
  5. A containerized approach - this approach separates your application from the environment in which it runs. In essence, you package up everything your application needs to run in a single image file, which can then be run from within a container environment (like Docker). The beauty of this approach is that the same container can be deployed multiple times on different kinds of machines running a container environment. This is also a virtual machine approach, only the virtual machine is different.

We’ll take a deeper look at virtual machines next.

Virtual Machines

A virtual machine is a simulated computing device. Let’s start with a simple example. You can play classic NES and Super NES games using the Switch. How is this done? Does the Switch contain the same hardware as the original NES or SNES? No. It uses a NVIDA Tegra processor, which utilizes the 64-bit ARM architecture. The NES used an 8-bit CPU, and the SNES used a 16-bit CPU. These are very different architectures. Of course, you may note the service I referenced is an online service. So do they hook up thousands of NES and SNES units to a web server processing input and output? Also no, but also yes.

What they actually do is emulate that hardware with software. Thus, they have a program that pretends to be the NES hardware, and the load into that program the actual NES game (the software) you wish to play. This emulator is a part of a web server that streams the resulting game images to the Switch, and the Switch likewise streams the player’s input back to the server. This is essentially the same approach used by any video game console emulator (sans the use of a web server).

In the same way, we can use a virtual machine to emulate another kind of computer within our own computers. For example, the VMWare platform you have access to as a CS student allows you to install VMWare on your computer. Once installed, you can create virtual Linux or Windows PCs that run within the program. Essentially, VMWare pretends to be a separate computer and shares your computer’s real resources through interfaces that make it seem to be the hardware the program emulates. Another approach to running a Linux VM within a Windows environment is the Windows Subsystem for Linux (WSL). This is essentailly a virtual machine that runs a Linux kernel (of your choice) within Windows. It offers better performance than VMWare as the VM plugs directly into Windows OS procedures.

Most Internet hosting services offer Virtual Machines that you can rent. Typically you specify the amount of RAM and storage, as well as the number of CPUs you wish to utilize. These represent a portion of the real machine’s resources that are assigned to your specific VM. You then are granted root access to the VM, and can install whatever software you need; for example, installing Linux, Dotnet Core, and a relational database to run your web application. Once you have your application set up, you launch it as a constantly running service, and it begins to listen for HTTP and HTTPS requests on port 80 and 443. The service usually provides you with a static IP address, which you can then hook up to your domain name.

The computing device being emulated does not have to equate to hardware analogues. In CIS 200 you worked with a VM extensively that does not correspond to physical hardware - the Java Virtual Machine (JVM). The JVM is a virtual machine that does not correspond to any real-world hardware, but provides a similar computing environment that can process Java Byte Code (which is similar to the assembly code run by hardware-based processors). A different JVM exists for every hardware platform that Java can be run on, mapping the virtual system procedures to the specific platform’s hardware and operating system.

Info

You may be asking yourself “is .NET also a virtual machine?” In one sense, yes, as it works from a similar concept as Java, but the reality is a bit more complex. The DOTNET languages are compiled into Intermediate Langauge, a format similar to Java’s Bytecode. And the DOTNET runtime has specific builds for different operating systems and hardware platforms, much like the JVM. But rather than the DOTNET runtime running the IL in a virutal environment, when you execute a DOTNET program, it is further compiled by the DOTNET runtime into assembly for the specific platform using a Just-in-Time compiler. So ultimately, DOTNET code is run in actual machine, not a virtual machine.

In recent years, containerized applications have become increasingly popular, especially for web-based deployments. This is a variant on the Virtual Machine idea, where the application and the execution environment are separated into two parts - the container (an image file containing the binary code and resources of an application) and an execution environment (a virtual machine, often emulating a Linux or Windows machine, but possibly more specialized). The containerized application is then run within this execution environment. The chief benefits of containerized approaches are portability and scaleability. As the execution environment can be installed on a variety of platforms, much like the JVM, the container can be run on different hardware - a development PC, a development Mac, a production server, or someting more esoteric like a cloud server.

As the emulated execution environment is the same across each of these platforms, the same containerized application should run identically on each. Moreover, to create additional instances of a containerized application, you just launch another copy of the container - a process that takes milliseconds. Compare this to adding another traditonal server or VM - you have to intialize the VM, install the necessary supporting environment, libraries, and other dependencies, and then install your application and launch it. This process can take minutes to hours, and often requires an IT professional to work through the steps. Cloud services like Amazon Web Services, Microsoft’s Azure, and Google Cloud all utilize container-based approaches to allow rapid upscaling and downscaling of web servers to meet the actual demand for a web application. These platforms typically handle load balancing (directing traffic evenly amongst the containers running instances of your web server) automatically, making the process of hosting large-volume web traffic much easier. And for small, limited-demand web applications, many of these cloud services offer a free tier with a limited amount of bandwith a month. The speed at which a containerized application can be spun up means that these free tier applications can be shut down most of the time, and only run when requests are coming in.

Summary

In this chapter we discussed what is necessary for hosting a web application: a computer running your web server, a static Internet Protocol address, a domain name that maps to your IP addresss, and a security certificate for using HTTPS. Each of these concepts we explored in some detail. We saw how using HTTPS allows encrypted end-to-end communication between the web client and server, as well as providing assurance that the server we are communicating with is the one we intended to make requests from. We also discussed the differences between self-signed certificates and those signed by a certificate authority, and introduced the nonprofit letsencrypt.org which provides free certificates to help secure the web.

We also described several options for the computing environment running your web server: a dedicated machine in your home or office, a dedicated machine in a server farm, a virtual machine, or a containerized cloud service. We discussed the benefits and drawbacks of each approach. As you prepare to host your own applications, you’ll want to consider these, along with the costs of the various services, to decide how you want to deploy your production application.

Chapter A

Learning Programming

The Science of Learning Programming

Get schooled

Subsections of Learning Programming

Introduction

You may not have given much thought to how you are learning to program. Very likely, you’ve just been engrossed in doing so. But understanding the learning process can help you improve how quickly, and how well you learn. As we tackle the subject of object-oriented programming, it will greatly benefit you to have a better grasp of exactly what’s going on upstairs. Especially as we embark on our first semester-long programming project. In the immortal words of Marty McFly, things are about to get heavy! 1

Doc Brown asking about heavy problems Doc Brown asking about heavy problems


  1. This is a reference to the classic 1985 film “Back to the Future,” in which Marty McFly (played by Michael J. Fox) travels back in time to save his scientist friend Doc Brown (played by Christopher Loyd). An advance warning: Many of my pop-culture references are going to be grounded in the 80’s and 90’s. You may be more familiar with the animated show “Rick and Morty,” which was inspired by Doc and Marty’s relationship in the film. ↩︎

Natural Born Programmers

There is a prevalent myth that some people are “natural born programmers” to whom programming comes easily. This is a dangerous idea, because of its corollary - the idea that “some people simply aren’t able to learn programming.” If you embrace these related ideas, you will find yourself wondering to which set you actually belong.

The truth is that in learning to program, we are learning to solve problems in a way that can be performed by a computing machine. Think back to your introductory computer science course - you may have been asked to give instructions to the professor or a peer to perform a task. Shuffling cards, for example, or making a peanut butter sandwich, as in the following video from the Darnit family:

The point of this exercise is to understand the exactitude or precision with which programs must be written. But it also reveals just how different people are from computers. The simple truth is that to become good programmers, we must learn to write programs by developing an understanding of how the computers work, as well as how to express instructions in a form they can use.

The rest of this chapter is devoted to understanding that learning process.

Mindsets

Carol Dweck is a researcher who has been developing a theory on “Mindsets.” She describes her research in the following Ted Talk:

The New Science of Learning

Terry Doyle and Todd Zakrajsek explore the implications of mindsets and other understandings of the learning process emerging from cognitive science in their book The New Science of Learning: How to Learn in Harmony with your Brain, which they specifically wrote for college-age learners. I would encourage you to read this book. But I’ll offer a brief summary here:

Sleep

Perhaps the most impactful thing you can do to improve your learning is to get high-quality sleep. It is during sleep that your brain consolidates the memories of the day (including what you were learning) into long-term storage. Insufficient sleep has a large negative impact on this process, resulting in lost or inaccessible memories.

The three stages of memory processing are encoding, storage, and retrieval. All three are affected in different ways by the amount of sleep you get. It is difficult to encode new learning when you are tired and unable to pay attention to the information. In fact, when you are sleep deprived, it becomes more difficult to learn new information the longer you are awake. Similarly, without the proper amount of sleep, storage of new memories will be disrupted. The third stage of memory processing is the recall phase (retrieval). During retrieval, the memory is accessed and re-edited. This is often the most important stage, as learned information is of limited value if it can't be recalled when needed, for example, for an exam. [...] Converging scientific evidence, from the molecular to the phenomenological, leaves little doubt that memory reprocessing "offline," that is, during sleep is an important component of how our memories are formed, shaped, and remembered.

Dolye and Zakrajsek, 2013, pp. 21-22

The NIH recommends 7.5 to 9 hours of sleep nightly. Individuals can vary greatly in their sleep needs, and may have different sleep patterns known as “chronotypes” which are the basis for stereotypes like ‘morning people’ or ’night owls’. It is important to understand your own sleep patterns to be able to adapt your study and sleep habits to maximize your learning effectiveness. Doyle and Zakrajsek offer advice to tailor your sleep and study patterns, as well as how to deal with ‘sleep debt’ - the learning penalty incurred when you were unable to get enough rest.

Info

Caffeine and alcohol, especially when consumed in large quantities, disrupt your ability to sleep effectively, and therefore your ability to learn.

Exercise

Exercise plays a surprisingly important role in learning. When you exercise - especially aerobic exercise (the kind that gets your heart rate up), your body releases extra neurochemicals and proteins that are used by the brain cells to communicate. This rush of extra resources improves your ability to learn new concepts and skills.

How Much Physical Exercise do you need? How Much Physical Exercise do you need?

In addition to getting this regular exercise, you should periodically get up from your computer and walk around, or even better, adopt a standing desk. The human body is not meant to sit for extended periods of time. Doing so can lead to blood pooling in the buttocks and thighs and increases dramatically your risk of heart attack and stroke.

The Senses

As memories are encoded, they draw upon all the senses involved in the experience. Research is starting to show that involving multiple senses in a learning activity can improve the learning. This is one reason I sprinkle these readings with images and videos. It is also one of the reasons I like using Cognitive Apprenticeship in my teaching. By showing you the code I am writing (seeing), and talking about what and why I am doing it (hearing), and having you write the same code (touching), we are engaging multiple senses for a stronger learning experience.

You can also leverage this in your own learning efforts. Creating concept maps connecting the ideas you are learning in this and other CS courses engages you visually with the learning, and also encourages your brain to recognize and utilize those connections to strengthen your reasoning about them. Annotating your textbooks engages you in both reading (seeing) and touching (writing), and helps you reflect more strongly on what you are studying. Try printing out the pages of this textbook as you read them, and make your own annotations in the margins and put it into a three-ring binder. It will improve your learning and give you a physical copy of the text when you’re done!

Patterns

The human brain is built to recognize and process patterns. Explicitly seeking patterns in what you’re doing can therefore help your learning. And programming is chock full of patterns! Consider if I asked you to write some code to add one to each element in an array. What would you do? Possibly a for loop like this:

for(int i = 0; i < array.Length; i++) {
    array[i] += 1;
}

But what if I asked you to halve the value instead? perhaps:

for(int i = 0; i < array.Length; i++) {
    array[i] /= 2;
}

Notice the parts that stay the same? We call this pattern iteration, and use it constantly. Moreover, the syntax of the for loop itself is a pattern. In fact, for loops were developed because of another pattern programmers found themselves using regularly:

int i = 0;
while(i < array.Length) {
    // DO SOMETHING WITH ARRAY
    i++;
}

Notice the three statements int i = 0;, i < array.Length, and i++;? These are the three parts of a for loop! When programmers found themselves using this pattern over and over again, they added new syntax to programming languages to simplify writing it1. In fact, the reason we use the semicolon to separate the three parts of a for loop is because they were originally separate statements in this while loop construction.

We also can apply this pattern recognition on similar, but unfamiliar formulations. Consider this code:

for(int i = array.Length-1; i >= 1; i--) {
    array[i] += array[i-1];
}

What does it do?

You likely noticed that it iterates through an array, but not forward through the array but rather backwards. And at each step we increase the value of an item by the value of the item before it. Could we rewrite this code as a forward iteration?

Memory

We already talked about the role of sleep in memory formation, but Doyle and Zakrajsek go deeper into how memories form and how that can be enhanced in the context of college life. For example, cognitive science has shown that parts of the brain responsible for memory formation remain active up to an hour after the learning experience (i.e. a lecture) has ended. So the common practice of scheduling your classes back-to-back may be impacting your ability to learn in the earlier classes!

Research has also shown that distributed practice is crucial to reinforcing and making available memories involved in skills as well as conceptual reasoning. The process of using the skill, or recalling the knowledge at regular intervals helps ‘settle’ the learning into our minds. In contrast, the practice of cramming may help us recall answers for an exam, but shortly after those answers will be lost to us, as the memories we formed were short-term. This is also why this class, as well as your math and physics courses emphasize solving problem after problem. It isn’t that we want to torture you - it’s that this continual practice is what helps you to learn the skills you’re developing.

Elaboration is another key to strengthening memory and recall. This involves adding more nuanced understandings to what you already know. For example, in this class we’re revisiting programming syntax that you’ve already learned, and I’ll be introducing new ideas and concepts that tie into that. Just like the example of how a for loop is nothing more than syntactic sugar for a specific while loop formulation. Do you need to know that? Probably not. But learning about it helps reinforce your understandings of how both kinds of loops operate, and that can make you a more proficient programmer.

Do you study while watching YouTube or conversing with friends? You might be interested to know that multitasking has a significant impact on memory formation:

When you shift task while working on something that requires thinking, such as texting your friend and listening to a lecture in class, your brain goes through a four-step process that allows you to switch your attention: (a) shift alert, (b) rule activation for task 1, (c) disengagement, (d) rule activation for task 2. This process is repeated every time you switch tasks that involve thinking, and you never get better or faster at it. You may have noticed that when you try to do two thinking tasks at the same time, you cannot complete both simultaneously, as the brain must shut down one task before working on the other. [...R]esearch demonstrates that individuals who shift tasks make 50% more errors and spend at least 50% more time on both tasks.

Dolye and Zakrajsek, 2013, p. 79

Stress also has an impact on memory formation - even minor stressful events can cause the release of hormones that disrupt the brain’s learning process. Exercise is the best counter to this issue, both helping repair the damage from stress and prevent it from occurring.


  1. We call this common practice of adding new syntax to a language to simplify writing commonly used patterns syntactic sugar, as it’s not strictly necessary, but makes the process of writing programs so much sweeter. ↩︎

Jean Piaget

Jean Piaget was a biologist and psychologist who performed some of the earliest studies on knowledge acquisition in children. His work is the foundation of Constructivism, one of the more influential philosophies of education.

Jean Piaget Jean Piaget

Genetic Epistemology

Of especial interest to us is his theory of genetic epistemology. Epistemology is the study of human knowledge, and genetic in this sense refers to origins i.e. the genesis, so his theory concerns how knowledge is created by humans.

Piaget’s genetic epistemology was inspired by studies he conducted of snails of the genus Lymnea native to the lakes of his home, Switzerland. He was able to establish that what had previously been considered different species of snails based on the shape of their shells were actually one species. He showed that when the snails of one lake were placed in a different lake, the way their shells grew changed to match those of the snails living in the second lake. In other words, the traits the snails displayed altered in response to their environment.

Examples of Lymnea Examples of Lymnea

Piaget suspected a similar mechanism was at work in human learning. He proposed that the human brain, much like the bodies of the snails, sought to exist in equilibrium with its environment. For the brain, this environment meant the ideas it was exposed to. He proposed two different mechanisms for learning, accommodation and assimilation, which related to how structures of knowledge were formed in the brain.

Assimilation referred to the process of adding new knowledge into existing mental structures. For example, once you know of and understand colors, you might encounter a new one - say periwinkle, which falls between blue and violet. Since you already know blue and violet, adding the knowledge of periwinkle is a straightforward task for the brain of assigning it to a slot between the two in the mental structure of ‘colors’.

In contrast, accommodation refers to the process by which knowledge for which you have no mental structures to represent are learned. This process involves building these mental structures, and is far more work. Modern cognitive science equates this process to the formation and reinforcement of new connections between neurons - a significant biological investment. Thus, triggering accommodation requires significant stimulus, in the form of wrestling with concepts that are currently beyond your grasp - a struggle that creates disequilibrium for your brain, eventually leading to it creating the new structure to accommodate the new knowledge.

This is the basis of the ’eureka’ moment, when a difficult concept has finally become clear. No doubt you have experienced this in a subject such as mathematics or programming, where a skill or idea you’ve been struggling with suddenly snaps into place, and becomes far easier to work with. This is also why your math and programming courses put so much emphasis on homework - this work helps create the disequilibrium you need to trigger accommodation. This is also why mindsets are so powerful - a fixed mindset provides a different mechanism for managing cognitive disequilibrium - by denying your ability to learn a subject, you provide justification for not engaging in the learning process.

Stage Theory

In addition to the mechanisms of accommodation and assimilation he outlined in his Genetic Epistemology theories, Piaget identified four stages children progress through as they learn to reason more abstractly. Those stages are:

  • Sensorimotor - where the learner uses their senses to interact with their surroundings. This is the hallmark of babies and toddlers who gaze wide-eyed at, touch, and taste the objects in their surroundings.
  • Preoperational - in this stage the learner begins to think symbolically, using words and pictures to represent objects and actions.
  • Concrete Operational - In this stage, the learner begins to think logically, but only about concrete events. Inductive logic - the ability to reason from specific information to a general principle also appears.
  • Formal Operational - This final stage marks the ability to grasp and work with abstractions, and is marked by hypothetico-deductive reasoning (i.e. formulating and testing hypotheses)

Neo-Piagetian Theory

While Piaget focused his study on children, many of the researchers who followed him also looked at how adults learn. Their findings suggest that all learners progress through the four stages with any new learning. That is, when you begin to learn a novel concept or skill, you are building the cognitive structures necessary to support it, and that your progress through this process corresponds to these four stages. Moreover, they have found that the divisions between stages are not rigid and clearly delineated; learners can exist in multiple stages at once (which they call the overlapping waves model).

The Overlapping Waves Model The Overlapping Waves Model

Developmental Epistemology of Computer Programming

Among these neo-Piagetian researchers is a group including Raymond Lister and Donna M. Teague whom applied these theories to the learning of computer science, formulating a theory Lister calls The Developmental Epistemology of Computer Programming. This theory describes the traits of programmers at each of the stages of development. In particular, they use a student’s ability to trace code (explain line-by-line what it does) as a demarcation between stages.

StageTraits
Sensorimotor
  • Cannot trace code with >= 50% accuracy
  • Dominant problem-solving strategy is trial and error
Preoperational
  • Can trace code with >= 50% accuracy
  • Traces without abstracting any meaning from the code
  • Cannot see relationships between lines of code
  • Struggles to make effective use of diagrammatic abstractions of code
  • Dominant problem-solving strategy is quasi-random code changes and copious trial runs
Concrete Operational
  • Dominant problem-solving strategy is hasty design, futile patching
  • Can establish purpose of code by working backwards from execution results
  • Tends to reduce levels of abstraction to make concepts more understandable
Formal Operational
  • Uses hypothetico-deductive reasoning
  • Reads code rather than traces to deduce purpose

These stages reflect the progress the learner is making through accommodation, creating the mental structures needed to reason about programming. An expert has developed these structures, which reflect patterns in how code is written - that is why an expert no longer traces code - they can see the patterns in the code and immediately grasp its action and purpose. In contrast, the novice must deduce the result of each line of code, put those understandings together, and then deduce what it is doing overall.

Writing a program is similar, the expert begins with a clear picture of the patterns she must employ, and focuses on fleshing those out, while a novice must create the program ‘from whole cloth’, reasoning out each step of the process. They are not yet capable of reasoning about the program they are writing in the abstract.

This also helps explain why learning to program can be so hard. Abstraction is considered a central tool in programming; we use abstractions constantly to simplify and make programs more understandable to other programmers. Consider a higher-level programing language, like C#. Each syntax element is an abstraction for a more complex machine-level process. The statement:

x += 2;

Stands in for machine instructions along the lines of:

PUSH REG5 TO REG1
PUSH 2 TO REG2
ADD REG1 AND REG2
PUSH REG3 TO REG5

Which are in turn, simplifications and abstractions of the actual process of adding the two binary values in register 1 and register 2 (remember studying binary math in CIS 115)?

Also, many of the productivity tools created to support expert programmers (i.e. automatic code completion) may actually hamper your learning, as they alleviate the need to carry out part of the process you are learning. Consider turning these features off in your development environment until you have developed fluency as a programmer.

Info

To turn off autocomplete in Visual Studio:

  • From Visual Studio, select “Tools” > “Options”.
  • Select “Text Editor” in the left pane.
  • Select the language you are using (C#, C++, Basic, etc.).
  • For C# and Basic, choose “IntelliSense”. For C or C++, choose “Advanced”, then scroll to the “IntelliSense” section.
  • For C# and Basic, check the “Show completion list after a character is typed” to disable it. For C/C++, you will have a few options, such as “Disable Auto Updating”, “Disable Squiggles”, and “Disable #include Auto Complete”. Set any of these to “True” to turn them off.

Summary

So what does all of this mean in the context of your learning?

  • Developing into an expert programmer is going to take hard work
  • It will require a lot of writing and reading code
  • There is no shortcut in this learning process, because you must create disequilibrium in your brain in order for the necessary cognitive structures to form through the process of accommodation
  • This process can be very frustrating
  • You can do this, just as many students before you have

What can you do to improve your learning process?

  • Get enough sleep, both in quantity and regularity
  • Exercise regularly
  • Engage with the readings and activities.
  • Take notes, annotate your text, draw concept diagrams
  • Don’t try to multitask while working on your classwork
  • Turn off autocompletion in your development environment
  • Don’t copy/paste code directly into your program. Instead, type it - it will take longer, but it will also give you the time to study each line and develop and understanding of what it is doing and how it interacts with the rest of the program.
  • Read programs written by other people, and try to understand what they are doing - GitHub is a great source of examples, and you can filter it by programming language
Chapter B

Git and GitHub

Be a Better Version

Subsections of Git and GitHub

Introduction

Code version control is a staple of modern software development. So it’s a good idea to learn and practice it now, so it becomes a core element of your software development practice. This appendix covers one of the most popular distributed version control software in use today - Git. It also covers one of the most popular online platforms for hosting remote Git repositories - GitHub. (Hint: Despite the tendency for many programmers to use the names interchangeably, they aren’t the same thing!)

Over the next few sections we’ll take you through the basic concepts of how Git actually works, and then show you the most common workflows you’ll find yourself using with Git.

Key Terms

Some key terms to learn in this chapter are:

  • Version Control
  • Git
  • GitHub
  • Repository
  • Commit
  • Branch
  • Remote
  • Clone
  • Origin
  • Push
  • Pull

Version Control Software

Have you ever been working on a paper for a class, and stopped every now and then to save it under a slightly different name, i.e. “Paper draft 1.docx”, “Paper draft 2.docx”, “Paper final draft.docx”, “Paper final draft with Merge suggestions.docx”, and so on?

Effectively what you were doing was version control - keeping old copies of a project around. This can be a lifesaver if your current file gets corrupted and becomes unusable. It can also be helpful to go back and see older versions, perhaps to see what a section looked like before your last set of changes. It might also be handy if that last major revision just isn’t working, and you want to go back to what the paper looked like before you started making changes.

Now think about programming projects, which involve multiple files. You could copy your project directory and rename it… but it’s a lot of effort, and also chews up memory on your computer. And have you ever found those multiple folders/files become difficult to navigate and sort through? Also, what happens if your entire computer gets trashed? Or stolen? Where are you with your multiple copies of files/directories then?

Version control software was invented to help solve these problems, along with one more pressing issue - working with others and sharing those code files between everyone on the team. No doubt you probably have or have heard some horror stories from CIS 115 or other courses were one member of the team accidentally overwrote the content that the rest of the team had painstakingly added to the group’s Wiki page…

Ideal version control software therefore:

  • Provides a mechanism for saving incremental changes to a project
  • Allows you to easily revert back to an earlier version of the project
  • Can be used to back up the project to a separate machine/location (preferably with some geographic distance, so if your workplace is destroyed by fire, flood, or other disaster your work isn’t forever lost)
  • Allows different team members to contribute to a shared project without overwriting your teammates’ work

Git

Git is one of the many version control programs that has been developed to tackle these challenges, and is currently one of the most popular. In part, this is because it does a very good job at tackling each of those issues we just discussed. Of course, it can only do this if you are using it as it was intended to be used… so it’s a good idea to spend a bit of time learning those details (though, as the authors of xkcd suggest, many people don’t):

xkcd’s take on Git xkcd’s take on Git

First, to use Git you need to install a program known as a Git client on your computer. Most people use the open-source command-line git client available from https://git-scm.com/downloads, but there are other clients that provide GUI experiences and the like. Here we’ll focus on the command-line version.

You can also learn more from the official Git Documentation or the free online Pro Git Book. These are great resources for expanding your Git knowledge, as this appendix is only going to hit the conceptual high points of Git and focus on the workflows you’ll be using for this class.

Git Initialization

YouTube Video

Git converts an ordinary directory (folder) on our computer into a git repository, allowing you to save different versions of the directory’s contents as you make changes to that directory. Invoking the git init command within the top directory of your project starts this process:

$ git init 

The data describing these changes and how to switch to them is stored in a subdirectory the Git client creates in the top project directory named .git. This folder is normally hidden from the user on most operating systems, though you can reveal it by tweaking your OS settings. All the git commands modify the contents of that folder. This approach has one really great benefit - if you copy your project folder into a new location, your repository information goes with it!

If you’re curious about the structure of the .git folder, Pierre DeWulf has a good post discussing it on his blog. 1 Essentially, every time you commit (save your current changes), Git creates a new entry representing the state of your files at that point, including an identifying hash (to identify the commit), the previous (parent) commit’s hash, a comment describing the commit, the date and time of the commit, and the identity of the user making the commit. We can use this information to restore the project directory to any one of the commits we’ve made.

Warning

Because Git places all of its repository information in the .git folder, deleting it will make the directory no longer be a repository. All committed changes will be lost, and you will no longer be able to revert your project files to earlier versions.

Staging and Committing

YouTube Video

It is important to understand that Git doesn’t save the changes to every file in the directory when you create a commit - it only saves those files you have staged to be committed. This extra step often confuses new Git users, but it exists to give you full control over what gets committed into your repository.

Staging Diagram Staging Diagram

It may help to understand how Git thinks about files. Files in your repository directory fall into one of five categories - untracked, unstaged, staged, committed, and ignored.

Untracked files are those that have never been added to the repository. As far as the repository is concerned, they don’t exist. If you were to delete one, you cannot restore it, as the repository has no saved version of it. Mostly these are files that have recently been created.

Unstaged files are those that are tracked, but have at least some changes that have not been committed. These are either new files that have just been added to the repository’s index with a git add command, or files that have been altered since the last commit.

Staged files are those that will be included in the next commit. They are added to the list of staged files with the git add command, and will be committed with the git commit command.

Committed files are those whose current state has been saved as a commit. In other words, they are “safe” as they can be restored from that commit with a git checkout command.

Ignored files are those whose path matches the pattern in the .gitignore file. We’ll come back to this idea shortly.

You can check for the status of your files in the repository at any time with the git status command:

$ git status 

This will print the status of all uncommitted files:

The output of git status The output of git status

There are four files in the ff directory: example0.txt, example1.txt, example2.txt, and example3.txt. In the output above, we see:

  • example0.txt was committed at some point, but now has changes. Git helpfully lets us know we can undo those changes and restore the committed version with the command $ git checkout -- example0.txt, or add this file to those staged with $ git add example0.txt.
  • example1.txt does not appear in the status message, as it is already committed and has no changes.
  • example2.txt is a new file (it has never been committed), and is staged to be committed.
  • example3.txt is also a new file, but is not staged to be committed.

Interestingly, you can still change a staged file. If you do so, Git keeps track of the staged but not committed changes, and the new, unstaged modifications. For example, if we change example2.txt and run git status again, we’ll see:

The output of git status The output of git status

Notice example2.txt now has two statuses - corresponding to the staged and unstaged changes!

Committing Changes

With this understanding in mind, the standard way of committing changes is to combine a git add command and a git commit command:

$ git add .
$ git commit -m "<a message about the commit>"

The git add . adds all untracked and unstaged files (making them staged), and git commit commits our staged files.

Ignoring Files

YouTube Video

Typically there are some files in a project that we never want to commit. For example, compilers often create temporary or intermediate files during the compilation process, and these will be recreated every time we re-compile. We also usually don’t want to commit the compiled binary files either, as we can always compile our code to get a fresh copy. Not saving these files means our repository takes up less memory, and Git operations are faster.

And if our project involves some configuration files with sensitive information (passwords, shared secrets, etc), we don’t want to commit these to our repository either - especially if it will be publicly visible on GitHub.

We can specify the patterns of files Git should ignore with a special text file named .gitignore. Inside that file, we specify file path patterns. Any file matching one of these patterns is effectively ignored by Git. However, if we have already committed a file to the repository, and then added our .gitignore file, the committed file remains in the repository. For this reason, we always want to add our .gitignore as we create the repository.

Info

While it is technically possible to completely remove a file accidentally committed to a Git repository, the process is not easy to complete correctly, and a mistake often means the file is still accessible to a skilled adversary. In those situations, it may be best to delete the .git folder and create a new repository.

GitHub provides a helpful repository of .gitignore files for specific programming languages and platforms. An easy trick is to find the one for the language you are interested in, open it in its raw form, and copy/paste its text into your .gitignore file. For this class, you’ll want to use the Visual Studio .gitignore.

Reverting Changes

As we suggested earlier in the chapter, one of the most important uses of a version control system is to allow you to revert to an earlier version of your code. To ask Git to list the available commits, you can use the git log command:

$ git log

This should print a list of the commits and their details, with the newest commit first:

git log output git log output

Notice each commit is identified by a hash, date, and commit message. This is why a good commit message is important - it helps to let us know what we changed (and therefore what changes we would be undoing if we reverted to that commit). If we wanted to revert to an earlier version, we would use the git checkout command:

$ git checkout [hash]

Where [hash] is the hash of the commit, i.e. cec94d9078c036b6ebd374cde0d7e400a8a94ebd for the initial commit in the example.

This reverts your files to that point, and reports you are in a ‘detached HEAD’ state, i.e. the commit loaded is not the latest one on this branch. Carlos Schults has an excellent post describing this condition. If you want to start working from this point (leaving your later changes out), best practice is to create a new branch to hold this commit. We’ll look at branches next.

Branches

YouTube Video

Branches are a powerful mechanisms for working on different versions of your code. The name “branch” is derived from visualizing a repository as a tree structure, with each commit being a node in the tree. For a simple repository, this tree structure is pretty boring - just a straight line as each node has only one child:

Simple repository with one branch Simple repository with one branch

This default branch was historically named “master”, though recent practice has shifted to using the term “main”. GitHub provides guidance and support for renaming existing project branches.

At any node in the commit tree, we could create a new branching point with the command git branch [branchname] where we supply the branch name. The branch starts with exactly the same code as the current commit to main (or whatever branch we are branching from). Then we can check out the branch with git checkout [branchname], using the name we supplied. Let’s create and check out a branch named “experiment” in our above example:

$ git branch experiment
$ git checkout experiment

We now have a new branch, experiment, branching from commit 573ed9f:

The same repository with a new branch The same repository with a new branch

While “experiment” is the checked out branch, any commits we make are placed on it instead of the main branch. Let’s assume we create two commits on the experiment branch; our tree will now look like:

Two commits on the experimental branch Two commits on the experimental branch

We can switch back to the main branch at any point with git checkout main. When we do so, our code will be reverted back to how it was in the last commit to the main branch (commit 573ed9f). If it turns out our experiment was a flop, we can forget about the experiment branch and the changes we made to it - we’re back to a clean working build at the point before we started the experiment.

Info

It is important to understand how commits and branches interact. When you check out a branch, the code in the repository is reverted to the last commit on that branch. And any new commits you make are saved to the currently checked out branch.

If you have uncommitted or staged changes in files, git will refuse to check out a branch until these are committed or stashed, as checking out the branch will overwrite those changes and they would be lost forever. In contrast, unstaged and ignored files are fine (as there is no committed version that will overwrite the file). Best practice is to commit your changes before switching branches, unless you want to throw the changes away.

If, on the other hand, we like the changes from the experiment, and want to add them to the main branch, we can merge those changes with the git merge [branchname] command:

$ git merge experiment

This merges the specified branch with the currently checked-out branch. Git accomplishes merging through a recursive strategy, which works very well. However, if both branches have had changes committed since the last shared commit, there is a possibility that some of those changes will overlap, and Git will not be able to determine which to use. This is called a merge conflict and must be resolved by you. See the merge conflict section later for more details.

There are a number of reasons we might want to create a branch; let’s examine some common use cases.

  • Prototype branches - Let’s say we wanted to try making some changes to our code that we aren’t sure will work - basically, we are creating an experimental prototype. If this experiment doesn’t end up succeeding, we would like to return to our current version of the project. This is exactly the scenario we walked through above.

  • Feature branches - Let’s assume you have a working program you need to add a new feature to, but you still want to be able to access the working code. In this case, you can create a branch to work on that feature. That way, when your feature is only partially done, you can still switch back to your main branch and fix a bug, etc., without needing to remove or comment out your half-written feature code.

  • Personal branches - Let’s say you’re working with a team. You want to make sure that the main branch is always clean, ready-to-go code, and you don’t want to have to deal with your teammate’s half-written code (nor they with yours). Each team member can create their own branch to do their work on, and when it is tested and ready, merge that code into the main branch.

Each of these approaches can (and usually is) used in conjunction with remote repositories. We’ll take a look at that next.

Remote Repositories

YouTube Video

Git bills itself as a distributed version control system. This means it has no central server. Instead, we can create copies of the repository we call remote repositories with the git clone command. These copies can be placed anywhere - in another directory on your computer, or on a different computer on your network, or a computer accessed via the internet.

GitHub is a web service that specifically hosts remote git repositories and allows you to access them through both your git client and through a web (HTML/CSS/JS) interface. It was created primarily to provide a place to host publicly-accessible, open-source projects, though you can also use it to create private repositories. It is not the only such service available; BitBucket is a similar website more focused on closed-source projects, and the popular GitLab is an open-source server for hosting Git projects you can install on your own systems. The Computer Science department at Kansas State University runs its own GitLab server to host projects developed as part of our research and extension mission.

At this point in your learning, you will likely be using a repository hosted on GitHub (usually created by GitHub classroom when you accept an assignment) as a remote repository you are cloning to one or more local repositories. For example, you’ll likely clone your project on both your home computer and a lab computer so you can work in both locations.

Common remote repository setup diagram Common remote repository setup diagram

A clone is a copy of the project in its current state, including the hidden .git folder. This means it is also a complete git repository! The code will be in the same state as that of the currently active branch of the repo it was cloned from (for a project cloned from GitHub, this would be the main/master branch).

Warning

While the cloned repository is a copy of an existing repository, it will not contain unstaged or ignored files or directories, as these are not tracked by Git.

Thus, in our diagram above, the home, lab, and GitHub copies of our repository all start exactly the same, with commit ba4. But if we make and commit changes to one of those repositories, that repository will be ahead of the other repositories, which will not have that commit. We can see this in the diagram below, where we have added commit a4e to the repository on our Lab PC:

Lab PC with extra commit Lab PC with extra commit

To get this same commit on our other remote repositories, we’ll use push and pull commands.

Pushing

We can push commits from one repository to another one with a git push command. To use this command, we need to know the location of the remote repository, and what branch we want to push our changes to. When we clone an existing repository, Git automatically saves the location of that repository and gives it the name origin. Thus, we can copy our commit a4e from our Lab PC repository to GitHub with the git push command:

$ git push origin main

This pushes our new commit to the GitHub repository, so it now also has that commit:

GitHub with extra commit GitHub with extra commit

Because GitHub does not have a reference to our Home PC’s repository, we can’t push the commit there. Instead, we’ll need to pull it directly from our home computer.

Pulling

As the repository on our home PC is also a clone of the GitHub repository, it kept track of the location of it using the name origin as well. So we can pull commits from that location (GitHub) using the git pull command:

$ git pull origin main

This copies any commits on the GitHub repository into the Home PC repository:

Home PC with extra commit Home PC with extra commit

Warning

If you push or pull changes to a repository that has extra commits, Git merges the extra commits with the pushed ones (as with the merge command). This can introduce the possibility of merge conflicts when Git is uncertain how to best combine two changes. These must be resolved by you as described in the merge conflict section. For this reason, it is always best practice to pull changes into your local branch, fix any merge conflicts, create a new commit, and only then push it to the remote repository. This ensures that the main branch code is always in good shape.

Remote Repositories

You can actually set up as many remote repositories as you want. In the diagram above, it would be possible to push or pull from the Lab PC to the Home PC directly, provided you had a publicly accessible URL for both (as we normally don’t have static IP addresses for home networks, this is unlikely). You can add an additional remote repository with git remote add [name] [url].

This can be helpful if you have a project you’ve started on your home machine and want to push to GitHub. Create an empty project on GitHub (it must be completely empty, so don’t create a default readme or license file). Then copy the clone URL and use it in your local Git command:

git remote add origin [remote url]

where [remote url] is the GitHub clone url. After you’ve done this, you can push your project to GitHub normally.

You can also list all remote repositories in a repo with:

git remote -v

Remote Branches

You probably noticed in our push and pull examples above, we specified the main branch. You can also pull or push from other branches, i.e. if you had an experiment branch on your remote repository origin you could pull it with:

git pull origin experiment

That would merge the experiment branch into the branch you currently have checked out.

Most often, we want to have our remote and local branches correspond to one another. In that case, we can first fetch all remote changes (without merging them into our local repository), which will also fetch any new remote branches. Then, we can create a new local branch that is synchronized to a new remote upstream branch. For example, suppose there is an experiment branch in our remote repository and we wish to create a new local experiment branch to track it. We can do:

git fetch
git checkout -b experiment origin/experiment

After these commands, we have created the new local experiment branch which is currently synchronized with the remote experiment branch, and have checked out that new branch. As we make changes, we can use the git push and git pull shorthands, which will push and pull between the local experiment and remote experiment branches.

Feature Branches

This section summarizes the git commands you will need when creating feature branches for your semester-long project.

1. Create and check out a local branch for the current milestone

When you start a new milestone, you need to create a local branch to hold your work. For example, if you wanted to create a feature branch for Milestone 0, you would do:

$ git branch ms0

Next, check out your new branch. For our Milestone 0, we would do:

$ git checkout ms0

2. Work on the new branch

As you make progress on the current milestone, it is a good idea to add your changes to the remote repository. First, make sure you are on your milestone branch by doing:

$ git branch

You will see a list of all local branches, with a * next to the currently checked out branch. You should see that the branch for the current milestone has a *. Then, add, commit, and push the changes for your branch to the remote repository:

$ git add .
$ git commit -m "description of changes"
$ git push

The first time you do this, it will automatically create a remote branch with the same name.

Info

Depending on your git configuration, you may get this error when you git push or git pull on a local branch that has no remote counterpart:

fatal: The current branch <branchName> has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin <branchName>

To have this happen automatically for branches without a tracking
upstream, see 'push.autoSetupRemote' in 'git help config'.

If you get this error, you can update your git configuration as follows:

git config --global push.autoSetupRemote true

At that point you should be able to use git push or git pull and have it go automatically to/from the corresponding remote branch.

(If you still have errors, you will first need to update your version of git to get a version that is at least 2.37 – you can check the version number with git --version.)

3. Continuing work on a different computer

Suppose you followed the steps above to start a milestone on your home computer (including pushing the latest changes for your milestone branch) and wanted to continue working on a lab computer.

First time working with the repository

If this was your FIRST time working on this repository on the new computer, you would need to clone the repository to the new local machine. You can do this with Visual Studio’s File->Clone Repository or from the terminal with git clone [repoURL].

First time working with current branch

If you have already cloned this repository to your current local computer but have not yet created a branch on this computer for the current milestone, you can use the git checkout option to both create a new local branch with the same name as a remote branch and switch to that new branch:

$ git checkout -b ms0 origin/ms0

Replacing ms0 with the current milestone branch.

Subsequent times working with a branch

If you have previously worked with the current milestone branch on your local computer, you need to first checkout that branch:

$ git checkout ms0

Again, replacing ms0 with the current milestone branch. Then, pull the latest changes for that branch from the remote repository to the local repository. If you do:

$ git pull origin ms0

It will fetch updates from the remote ms0 branch and merge them into the local ms0 branch.

Merging your feature branch into main

Finally, when you have finished the milestone, you’ll want to merge your new changes from the feature branch into the main branch:

$ git checkout main 
$ git merge ms0

(Again, replacing ms0 with the current milestone branch name). Next, push the newly expanded main branch to GitHub:

$ git push origin main

After that, you’ll need to create a release to turn in.

Merge Conflicts

YouTube Video

When git merges commits from two different branches or remote repositories, it applies the committed changes from both. In many cases, this works seamlessly, but sometimes it results in merge conflicts. A conflict occurs when the same line(s) in a file were changed in both branches, and git is unsure of which to use.

Git will do several things in this scenario:

  1. It will report as output from that command that caused the conflict which file(s) in the repository contain conflicts, and

  2. It will mark the conflicted sections of those files using a special format that shows the two versions of the code.

An example of such a marking is:

public void PrintSomething() {  
<<<<<<< HEAD
  if(testValue) {
=======
  if(otherTestValue) {
>>>>>>> some_branch
  Console.log("Something...");
}

Here, we see two conflicting versions of one line: if(testValue) { and if(otherTestValue){. Additionally, we see markers delimiting the conflicting sections: <<<<<<< HEAD, =======, and >>>>>>> some_branch. We need to replace all of the code and delimiters with one final version of the code. This could be the first option, the second option, or a combination of the two:

public void PrintSomething() {  
  if(testValue && otherTestValue) {
  Console.log("Something...");
}

We need to do this for all conflicts in all conflicting files. Once they have all been resolved, we need to commit the changes with the commands:

$ git add .
$ git commit -m "Fixed merge conflicts"

Create a Release

When you are ready to turn in an assignment, you will need to create a release tag. A tag is nothing more than a specially named commit, and a release is a special tag created on GitHub to mark a specific version of the software.

Step 1 - Make sure All your code is on GitHub

Since releases are created on GitHub, it is important to make sure you’ve committed your changes and have pushed them to GitHub before you create the release. You can check that all changes have been committed and pushed with the command:

$ git status

If you see these messages:

Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

Then you are good to go. On the other hand, if you get the message Your branch is # commits ahead of 'origin/main', then you need to push to master, and if any files are listed as uncommitted, you first need to commit them.

Step 2 - Navigate to the Releases on GitHub

Next, open your repository on Github. Towards the right side of the page you should see a link labeled “Create a new release”. Click it.

Releases Link Releases Link

This will load the releases page for your repository.

Step 3 - Complete the Release Form

You will need to fill out the release form, specifically the version and title, and then click the “Publish Release” button.

New Release Form New Release Form

Releases use semantic versioning, a numbering system that uses three numbers separated by periods (i.e. version 3.4.2). The first number is the major version - a change in this number indicates a major change in the associated software, i.e. a redesigned interface, a change in what methods are available, etc. The second number is the minor version. It indicates small feature additions to the software. Finally, the third number is the patch version, and this one indicates a change that is typically a bug fix or security fix. Each number rolls over like the seconds and minutes on a clock when the next version number is increased, i.e. you would go from version 2.7.23 to 3.0.0, or 4.3.12 to 4.4.0. For this project, each milestone should be treated as a minor release, and each new project as a major one.

Releases also get a human-readable name. For this class, you should use the assignment name as the release name, i.e. Milestone 0.

Step 4 - Submit your Release URL on K-State Online

Once you have finished creating the release, GitHub should take you to the release page. You can also navigate there by clicking the specific release under the “Releases” heading on the right-hand side of your repository lannding page. Copy the URL of this page; it is what you will submit on Canvas.

Release URL Release URL

Adding Documentation Files

When adding existing documentation files (i.e. UML documents) to your project, you may think adding them through Visual Studio’s Solution Explorer would be the way to go. However, this can lead to an unexpected issue. Look closely at the example below:

Bad Documentation Example Bad Documentation Example

We can see the PDF has been added to the solution file in the Solution Explorer, and we can see its raw data open in the editing pane. But take a close look at the Properties. The file is located in the Downloads folder! Since the file is not present in a folder managed by Git, IT WILL NOT BE COMMITTED!

The ONLY way to get Git to track a file is to PUT IT INTO A DIRECTORY TRACKED BY GIT. Visual Studio’s Solution Explorer does NOT copy existing PDF files, it simply creates a virtual representation of the file within the solution that points to where that file exists on your filesystem.

Open the Solution in File Explorer

To summarize, you must move or copy the file you want to share into the solution directory using your operating system’s file system, NOT VISUAL STUDIO. Visual Studio typically places your projects in the directory C:/Users/%username%/source/repos/%solutionname%/ where %username% is your Windows username and %solutionname% is your solution’s name. If you asked Visual Studio to save your files in another location, you need to look there.

However, there is a quick way to open the exact solution folder from within Visual Studio. Right-click the solution in the Solution Explorer and choose Open Folder in File Explorer from the context menu:

Open Folder in File Explorer Open Folder in File Explorer

Then you can use File Explorer to create your Documentation folder and place your documents:

Copy File into Documentation Folder Copy File into Documentation Folder

Once the document is in place, you will need to commit your changes and push them to GitHub

Chapter C

Exercises

Doing the Work

Subsections of Exercises

Encapsulation

Part 1

Follow the tutorial in the following video:

YouTube Video

Part 2

Then add the remaining static methods to the VectorMath class (Scale, DotProduct, CrossProduct, Magnitude, Normalize) following the structure laid out in this UML class diagram:

The Encapsulation Exercise Class Diagram The Encapsulation Exercise Class Diagram

The vector operations these methods should compute are:

$$\vec{a}+\vec{b}= \langle a_x + b_x, a_y + b_y, a_z + b_z \rangle \tag{Vector Addition}$$ $$\vec{a}-\vec{b}= \langle a_x - b_x, a_y - b_y, a_z - b_z \rangle \tag{Vector Subtraction}$$ $$\vec{a} \cdot s= \langle a_x \cdot s, a_y \cdot s, a_z \cdot s \rangle \tag{Vector Scaling}$$ $$\vec{a} \cdot \vec{b} = \langle a_x b_x + a_y b_y + a_z b_z \rangle \tag{Vector Dot Product}$$ $$\vec{a} \times \vec{b} = \langle a_y b_z - a_z b_y, a_z b_x - a_x b_z, a_x b_y - a_y b_x \rangle \tag{Vector Cross Product}$$ $$||\vec{a}|| = \sqrt{a_x^2 + a_y^2 + a_z^2} \tag{Vector Magnitude}$$ $$\hat{u} = \frac{\vec{u}}{||\vec{u}||} \tag{Vector Normalization}$$

You can uncomment the tests in the VectorMathUnitTests class and then run them to verify your work.

Submit A Release

Create a release of your project code on GitHub and submit its URL.

Razor Pages Exercise

YouTube Video

Now that you know how to create Razor pages, let’s see what makes them useful for creating dynamic web pages.

Initial Project

We’ll start with a simple ASP.NET web app using Razor Pages to display a database of movies. This app consists of a single page, Index that will be used to display the details of the movies in the database. It also contains classes representing an individual movie (Movie) and the movie database (MovieDatabase).

You can clone the starting project from the GitHub Classroom url provided in the Canvas Assignment (for students in the CIS 400 course), or directly from the GitHub repo (for other readers).

Movie Class

Here is the starting point of a class representing a movie:

    /// <summary>
    /// A class representing a Movie
    /// </summary>
    public class Movie
    {
        /// <summary>
        /// Gets and sets the title of the movie
        /// </summary>
        public string Title { get; set; }

        /// <summary>
        /// Gets and sets the Motion Picture Association of America Rating
        /// </summary>
        public string MPAARating { get; set; }

        /// <summary>
        /// Gets and sets the primary genre of the movie
        /// </summary>
        public string MajorGenre { get; set; }

        /// <summary>
        /// Gets and sets the Internet Movie Database rating of the movie
        /// </summary>
        public float? IMDBRating { get; set; }

        /// <summary>
        /// Gets and sets the Rotten Tomatoes rating of the movie
        /// </summary>
        public float? RottenTomatoesRating { get; set; }
    }

As you can see, it’s a pretty simple data class.

However, we do have one new thing we haven’t seen before - a new use of the question mark symbol (?) in float?. This indicates a nullable type, i.e. in addition to the possible float values, the variable can also be null.

Remember, a float is a value type, and normally you cannot set a value type to null. By making it a nullable type, we effectively have wrapped it in an object (technically, a Nullable<T> object). So instead of RottenTomatoesRating referencing the memory where the value of the float is stored, it is now storing a reference to that location in memory. This reference can itself be null if it isn’t pointing anywhere.

We need all of these properties to have the possibility to be null, as the data we are working with does not have values for all of them. Let’s look at that data next.

Serialized Movie Data

The movies.json file contains data in a JSON format. JSON stands for Javascript Serialization Object Notation. As the name suggests, it is a serialization format - a way of expressing the state of a data object in text. While it originates with JavaScript, JSON has become a popular format for exchanging data in many programming languages. Let’s take a closer look at this file’s structure.

The first movie in the file is The Land Girls:

[
  {
    "Title": "The Land Girls",
    "USGross": 146083,
    "WorldWideGross": 146083,
    "USDVDSales": null,
    "ProductionBudget": 8000000,
    "ReleaseDate": "Jun 12 1998",
    "MPAARating": "R",
    "RunningTime": null,
    "Distributor": "Gramercy",
    "Source": null,
    "MajorGenre": null,
    "CreativeType": null,
    "Director": null,
    "RottenTomatoesRating": null,
    "IMDBRating": 6.1,
    "IMDBVotes": 1071
  },
  ...
]

The outer square brackets ([, ]) indicate that the file contents represent an array. The curly braces ({, }) indicate an object - thus the file represents an array of objects. Each object consists of key/value pairs, i.e. "Title": "The Land Girls" indicates the title of the film. We’re using a library to deserialize these JSON objects into our C# Movie object structure, so we need the keys to match the property names in that structure.

As you can see with this entry, many of the values are null. This is why we needed to introduce nullables into our data object - otherwise when we deserialized this object in our C# code, our program would crash when it tried to set one of the value properties to null.

The MovieDatabase Class

Now let’s look at the MovieDatabase class:

    /// <summary>
    /// A class representing a database of movies
    /// </summary>
    public static class MovieDatabase
    {
        private static List<Movie> movies = new List<Movie>();

        /// <summary>
        /// Loads the movie database from the JSON file
        /// </summary>
        static MovieDatabase() {
            
            using (StreamReader file = System.IO.File.OpenText("movies.json"))
            {
                string json = file.ReadToEnd();
                movies = JsonConvert.DeserializeObject<List<Movie>>(json);
            }
        }

        /// <summary>
        /// Gets all movies in the database
        /// </summary>
        public static IEnumerable<Movie> All { get { return movies; } }
    }
}

There are a couple of interesting features. First, the class is static, which means we cannot construct an instance of it - there will only ever be one instance, and we’ll reference it directly from its class name, MovieDatabase. As a static class, all of its fields, methods, and properties must likewise be declared static. If these are public, then we can access them directly from the class name, i.e. to access the All property, we would invoke MovieDatabase.All.

Notice that we have declared a static constructor for the class. This will be invoked the first time the class is used in the program, and only that one time (as after that, the one static instance of the class exists). Since we cannot construct a static class directly, there is no reason to add an access modifier (public, protected, or private).

The usual reason to have a static constructor is to do some kind of initialization, and that is what we are doing here. We are loading the JSON file, and using the JsonConvert.DeserializeObject<T>() method to convert the JSON into a List<Movie>. This method is part of the JSON.net library from Newtonsoft - which is provided to us through a Nuget package. If you look under the Dependencies entry in the solution explorer, you can find a Packages list that contains Newtonsoft.JSON, this library.

Nuget is a package manager that allows developers to publish .NET libraries they have created for other developers to use. It is a source of many useful libraries, and if you become a professional .NET developer, it is probably a resource you will find yourself using often.

Displaying a list of Movie Titles

Okay, now that we’re familiar with the starter code, let’s turn our attention to the task at hand - we’d like to display all the movies in our database on the Index razor page.

Refactoring Index.cshtml

Let’s start by looking at our Page class, Index.cshtml:

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<h1>Hello World</h1>

Let’s change the header Hello World to say Movies. And below that, let’s declare an unordered list of movie titles, and put the first few titles in list items:

<h1>Movies</h1>
<ul>
    <li>The Land Girls</li>
    <li>First Love, Last Rites</li>
    <li>I Married a Strange Person</li>
</ul>

Go ahead and run your program. Your page should look like:

The refactored index page The refactored index page

This approach would work fine, but there are 3,201 entries in our database - do you really want to do that by hand?

Instead, let’s leverage the power of Razor templates, and use some C# code to iterate through each entry in the database. We can do this with a foreach loop, just like you might do in a regular C# class:

<h1>Movies</h1>
<ul>
    @foreach(Movie movie in MovieDatabase.All)
    {
        <li>@movie.Title</li>
    }
</ul>

Notice that inside the body of the foreach loop, we use regular HTML to declare a list item element (<li>). But for the content of that element, we are using the movie.Title property. As this is prefaced with an at symbol (@), the Razor template engine evaluates it as a C# expression, and concatenates the result (the movie’s title) into the list item. Thus, for the first item in the database, <li>The Land Girls</li>.

Each of these is in turn concatenated into the page as the foreach loop is processed, resulting in a list of all the movie titles in the database. Run the program and see for yourself:

The full list of movies The full list of movies

They’re all there. You can scroll all the way to the bottom.

Adding Some Detail

It might be interesting to see more information about the movies than just the title. Let’s take advantage of the details in our Movie class by expanding what is shown:

<h1>Movies</h1>
<ul>
    @foreach(Movie movie in MovieDatabase.All)
    {
        <li>
            <h3>@movie.Title</h3>
            <div>@movie.MPAARating</div>
            <div>@movie.MajorGenre</div>
        </li>
    }
</ul>

Notice that unlike our WPF XAML, we can nest as many children in an HTML element as we want! If we run the program now:

The detailed movie list The detailed movie list

Well, it works, but it’s also underwhelming (and a bit difficult to interpret). Notice that our first few movies don’t have all the rating properties, so there are large blank spaces.

Let’s take advantage of Razor’s ability to use conditionals to leave those blanks out:

<h1>Movies</h1>
<ul class="movie-list">
    @foreach(Movie movie in MovieDatabase.All)
    {
        <li>
            <h3 class="title">@movie.Title</h3>
            @if (movie.MPAARating != null)
            {
                <div class="mpaa">
                    Rated @movie.MPAARating
                </div>
            }
            @if (movie.MajorGenre != null)
            {
                <div class="genre">
                    @movie.MajorGenre
                </div>
            }
        </li>
    }
</ul>

We’ve also added the text “Rated” before our MPAARating, so the entry will now read “Rated R” for an R-rated movie, “Rated G” for a g-rated movie, and so on.

We also added class attributes to the <h3> and each <div>, as well as the movie list itself. We’ll use these to style our elements.

Adding Some Style

We can find our CSS rules for the project in wwwroot/css/site.js.

Let’s start with the unordered list itself. We can select it with the ul.movie-list selector. We’ll remove any padding and margins, and add a solid line above it:

ul.movie-list {
    padding: 0;
    margin: 0;
    border-top: 1px solid gray;
}

We’ll then select each list item that is a child of that list with ul.movie-list > li. We’ll remove the bullet, add a lighter border at the bottom to separate our items, and put a 10-pixel margin all the way around:

ul.movie-list > li {
    list-style-type: none;
    border-bottom: 1px solid lightgray;
    margin: 10px;
}

You might wonder why we put the list in an unordered list at all, if we’re just going to change all its default styles. Remember, HTML provides the structure as well as the content. By putting the items in a list, we’re signifying that the items are a list. We are conveying semantic meaning with the structure we use.

Remember, it’s not just humans that read the internet. Many bots and algorithms do as well, and they typically won’t use the lens of CSS styling - they’ll be reading the raw HTML.

We’ll make our title headers a dark slate gray, have a slightly larger-then-normal text, and remove the margins so that there are no large space between the header and the text is directly above and beneath them:

.title {
    color: darkslategray;
    font-size: 1.2rem;
    margin: 0;
}

Finally, let’s lighten the color of the MPAA rating and genre:

.mpaa {
    color: slategray;
}

.genre {
    color: lightslategray;
}

Adding Some Ratings

While the MPAA ratings convey the age-appropriateness of a movie, the IMDB and Rotten Tomatoes ratings provide a sense of how much people enjoy the films. Since this probably information our readers might want to see to help them judge what films to look at, it might be nice to call attention to them in some way.

What if we put them into their own boxes, and position them on the right side of the screen, opposite the title? Something like:

Mockup of styled list item Mockup of styled list item

There are many ways of accomplishing this look, including the float property or using a <table> element. But let’s turn to one of the newer and more powerful css layout features, the flexbox layout.

We’ll start by refactoring our HTML slightly, to divide our <li> into two <div>s, one containing our current details for the movie, and one for the viewer ratings:

      <li>
        <div class="details">
            <h3>@movie.Title</h3>
            @if (movie.MPAARating != null)
            {
                <div class="mpaa">
                    Rated @movie.MPAARating
                </div>
            }
            @if (movie.MajorGenre != null)
            {
                <div class="genre">
                    @movie.MajorGenre
                </div>
            }
        </div>
        <div class="ratings">
            @if (movie.IMDBRating != null)
            {
                <div class="imdb">
                    @movie.IMDBRating
                </div>
            }
            @if (movie.RottenTomatoesRating != null)
            {
                <div class="rotten-tomatoes">
                    @movie.RottenTomatoesRating
                </div>
            }
        </div>
    </li>

Now we’ll apply the flexbox properties and a minimum height to the list item:

ul.movie-list > li {
    display: flex;
    flex-direction: row;
    align-items: flex-start;
    justify-content: space-between;
    min-height: 50px;
}

These can be combined with our earlier rule block with the same selector, or they can be declared separately.

We’ll also use flexbox to make our ratings appear side-by-side:

.ratings {
    display: flex;
    flex-direction: row;
}

And use some styles to add the border, center the text, and use gray for the text and border colors:

.imdb, .rotten-tomatoes {
    color: gray;
    border: 1px solid gray;
    width: 60px;
    text-align: center;
    font-size: 1.2rem;
}

Notice that we can use the comma to allow more than one selector to share a rule.

It might be nice to label the two ratings, as Rotten Tomatoes is on a 100-point scale, and IMDB uses a 10-point scale. We could go back and apply this in the HTML, but it is a good opportunity to show off the ::before pseduo-selector, which allows us to create HTML elements using css:

.imdb::before {
    content: "IMDB";
    display: block;
    font-size: 1rem;
}

.rotten-tomatoes::before {
    content: "Rotten";
    display: block;
    font-size: 1rem;
}

If you run your code at this point, you may notice your <h3> styles have stopped applying. If we look at the selector, we’ll see why. It is currently: ul.movie-list > li > h3, which indicates the <h3> it applies to should be a direct child of the <li> tag. We could swap to using h3 instead, but this would apply to all <h3> tags on our page. Instead, let’s swap the > for a space , which indicates a descendant instead of a direct child. In fact, we could drop the li as well:

ul.movie-list  h3 {
    font-size: 1.2rem;
    margin-bottom: 0;
    color: darkslategray;
}

The end result is very close to our sketch:

The end result The end result

Clearly, CSS is a powerful tool. It can be challenging to learn, but if you are going to be involved in web development, it is time well spent.

The MDN CSS documentation and the CSS-Tricks site are both excellent references for learning CSS.

Chapter 11

Responsive Design

Fitting the Screen

Subsections of Responsive Design

Media @ Rule

The @media rule was originally introduced in CSS version 2. It was used to differentiate between different kinds of devices, i.e. a computer screen, printer, etc. Only the print type saw broad adoption, but it remains useful. Let’s see how we can use it.

If we look at our Pages/Index.cshtml page, we can find an <aside> element that contains a banner advertisement for Bernie’s. The <aside> element is a semantic element - one that implies a specific meaning for what it contains. The meaning it implies is something ’extra’ to the page that isn’t part of its main thrust - in this case, an advertisement.

Beyond implying a meaning, it’s equivalent to a <div> element, it is simply a block-level element that divides up the page and can be used to attach CSS rules.

Let’s assume that we don’t want this to appear on the page when we print it. Let’s add a class of "advertisement" to it:

<aside class="advertisement">
    <img src="~/img/ad.png" alt="Eat at Bernies!"/>
</aside>

And in our wwwroot/css/site.css, let’s create an @media rule for printing:

@media print {
    .advertisement {
        display: none;
    }
}

Any CSS enclosed in the curly braces following @media print will only be applied when the page is printed. In that case, we set any element with the class advertisement to not display. Try running the program, and in the browser, select print preview.

The printed webpage The printed webpage

The advertisement does not appear! But it still shows up in the browser.

This simple technique can be used to easily create printer-friendly versions of your webpage by changing colors to gray tones, replacing fonts, and removing elements that will require a lot of ink to print.

But the media @ rule is also the basis for an even more powerful mechanism, media queries, which we’ll look at next.

Media Queries

In CSS3, the @media rule was extended to include the concept of media queries, a technique that allows us to conditionally apply CSS rules using a broader range of values. Some of the most commonly used are:

  • The width and the height of the viewport (the area on-screen)
  • The width and height of the device (the monitor, phone, or other screen)
  • The orientation (landscape or portrait)
  • The resolution

Media queries are primarily used to enable responsive design, which refers to your page re-arranging itself for display on different devices.

A media query expands the @media rule that we used previously. It consists of the @media keyword followed by an optional media type (all, print, screen, or speech), and then any media features we want to query for within parenthesis.

Let’s start with a simple example. Let’s change the color of our h1 elements based on the orientation of the screen. Add these two rules to your wwwroot/css/site.css:

@media (orientation: landscape) {
    h1 { color: green; }
}

@media (orientation: portrait) {
    h1 { color: orange; }
}

Now if you run your server, the <h1> elements will probably be green. Resize your window until it is taller than it is wide, and they will turn orange.

Using Chrome Developer Tools

While we can definitely stretch our browser window to try different sizes, there are far better tools available. Let’s see how Chrome’s Developer Tools can make our lives as a developer easier.

First, set Visual Studio to use Chrome as the browser it launches your website with. From the build target dropdown, select the ‘Web Browser’ option, and select ‘Google Chrome’:

Setting Chrome as the build target Setting Chrome as the build target

Now run your program. When Chrome loads, turn on developer tools by either pressing CTRL + SHIFT + I or right-clicking on the page and selecting ‘Inspect’. This launches the developer tools in their own pane in the window. At the top of the developer pane is an icon that resembles a cellphone in front of a screen.

The Device Toolbar toggle button The Device Toolbar toggle button

Clicking it will toggle the device toolbar, which allows you to emulate different devices, choosing from several common phones and tables, or use the ‘responsive’ category to set a custom size:

The device selection dropdown The device selection dropdown

In addition, you can change the orientation of the device with the rotate button:

The device selection dropdown The device selection dropdown

Try selecting a mobile phone and then using the rotate button to change the orientation. Watch how your <h1> elements change color!

Next we’ll look at how we use media queries in responsive design.

Note

Developer tools are actually a part of all modern web browsers, so if you prefer to use a different browser you can learn its tools instead. Here are links to the documentation for the major browsers:

Float-based Responsive Layout

Now that we have seen the concept of responsive breakpoints, let’s put them to use creating a layout for our web page. Currently we have three <div> elements, each with a header and placeholder text. We’d like to arrange these into a row of three columns if the screen is large enough, and collapse them into a single column on smaller screens, such as a mobile phone.

We’re going to explore several strategies for accomplishing this. First, we’ll look at float-based layouts. This is an older strategy for creating columns, and it is based on the float css property which is traditionally used to float images and figures to the right or left of a body of text.

We can instead leverage it to create columns by setting each <div> we intend to behave as a column to have a float: left; property. This makes each column float to the left of the one after it. Let’s give it a try.

First, in your Pages/Index.cshtml, we’ll add a class to the outermost div, just under the <aside> we manipulated previously:

<aside class="advertisement">
    <img src="~/img/ad.png" alt="Eat at Bernies!"/>
</aside>
<div class="float-columns">
    <div>
        <h1>Column One</h1>
        ...

Then we’ll add setting the float property in our wwwroot/css/site.css:

.float-columns > div {
    float: left;
    width: 33%;
}

We use the child selection operator > to apply the css rules to all <div> elements that are a direct child of the <div> we gave the class of float-columns to. We’ll make each of those children <div> elements behave like columns by making them float to the left of the column declared after them, and set them each to be roughly 1/3 the width of the page with width: 33%.

If we run the page now, we’ll see that we have our columns:

The three-column float layout The three-column float layout

But when you scroll down, you’ll also see a problem:

A float error A float error

Because the use of float takes elements out of the usual flow layout algorithm, the elements that follow them often end up in unexpected positions. We have to explicitly turn the normal flow layout back on when we’re done with our floating elements with a clear property, which can be left, right, or both.

This normally is done by adding an empty div with the clear: both rule applied after the last column; a technique called a clearfix. Let’s go ahead and declare our clearfix class in wwwroot/css/site.css:

.clearfix {
    clear: both;
}

And then in our Pages/Index.cshtml, we’ll add a <div> element with that class, just after our containing <div> (it will be the last element in the file):

<div class="clearfix"></div>

Now when we render the page, we’ll see the columns behave as expected:

!The float error fixed](/images/3.3.5.3.png)

However, our page is not responsive at this point. So we’ll need to add a media query to establish a responsive breakpoint for smaller screens. Let’s use 490 pixels as our breakpoint. We’ll need to add this new rule below the ones we created for our .float-columns in wwwroot/css/site.css, as we will be overriding those when the media query is true:

@media (max-width: 490px) {
    /* Reset the columns to render with the flow of the page */
    .float-columns > div {
        float: none;
        width: 100%;
    }
}

We simply reverse the rules we applied to the columns by setting the properties back to their defaults. Now the columns will change to their stacked appearance when we look at the site on a smaller screen.

Responsive Setup

In web development, Responsive Design means making your webpages adjust to the device they are displayed on. This is especially important today, where a user might be browsing the web from a desktop, a tablet, a phone, or even a console built into a refrigerator, a car, or an airplane seat!

Before we go farther, there are a couple of concepts we need to understand. The first is the device width and device height, which are the actual size of the device’s screen. Next is the viewport, which is the area of the screen that the web browser actually gets to render into, and has its own width and height. The viewport width and height can be smaller than the device width and height, as is the case when the browser is displayed in a window that is not maximized. In that case, only the area of the browser window (less the toolbars) is the viewport.

A viewport can also be larger than the device width and height. When mobile phones first started allowing users to browse the web, they had tiny screens. Phone manufacturers had to decide how to display webpages that were never designed for these devices. They settled on an approach of setting the viewport to a size consistent with computer monitors of the day, and then scaling the entire page down to fit onto the actual screen. The result was you could see the entire webpage, but it was tiny.

Viewports on Mobile Devices Viewports on Mobile Devices

This remains the default behavior of mobile browsers to this day - and because the viewport is not aligned with the device size, media queries targeting the viewport also do not correctly account for device size.

The Viewport Meta Tag

However, this can be overridden through the use of a <meta> element. The <meta> element must be placed in the <head> of a webpage, and applies some kind of metadata to the webpage. Historically, it was used to supply key words to search engines, but this practice was routinely abused and search engines have ceased to rely on it. However, other kinds of metadata can be added with a <meta> element, and one of the most important for responsive design overrides the default viewport settings. Thus, a responsive webpage should always declare the following <meta> tag:

<meta name="viewport" content="width=device-width, initial-scale=1">

This tells the browser to set the width of the viewport to the width of the device, and to not scale the final rendering of the webpage (scale is on a range of 0 to 1, where 1 is 100%). This ensures that the viewport and the device sizes are the same.

If you look in your project’s _Pages/Shared/Layout.cshtml file, you will notice that this <meta> tag is already defined. In fact, it is included as biolerplate in all ASP.NET projects generated by Visual Studio that include Razor pages. But if you find yourself writing pages from scratch that you want to be responsive, you’ll need to add this. You can read more about it on MDN.

Responsive Breakpoints

Once we know our viewport is set up to be the same size as our device, we can turn our attention to using media queries to respond to different widths of screens. For example, we can define all the CSS rules we would normally use, and then a media query that would limit those for a smaller screen - say under 750 pixels. Let’s try this out by adding a border to our aside, and changing its appearance in a smaller screen:

aside { border: 5px solid gray; }

@media (max-width: 750px) {
    /* applies when the screen is under 750 pixels wide */
    aside { border: 3px dashed gray; }
}

Now when the viewport is wider than 750 pixels, you’ll see a solid gray border around the banner ad:

Added Border on the Banner Ad Added Border on the Banner Ad

And if you size down your viewport, you’ll see that banner go to a smaller dashed gray:

Border at medium viewport size Border at medium viewport size

We can extend this approach by adding another media query for even smaller screens, say those under 490 pixels:

@media (max-width: 490px) {
    /* applies when the screen is under 490 pixels wide */
    aside { border: 2px dotted gray; }
}

As this rule is declared after the first two border rules, it will override them both, replacing the values for the border (note, any previously declared rules that have not been overridden will continue to apply). Now if you size your screen even smaller:

Border at small viewport size Border at small viewport size

This strategy of using successively smaller media queries to override CSS styles at different screen widths is known as responsive breakpoints, because at those widths you specify, the appearance of your page changes.

It is a good idea to have the starting point of your page (when no media queries are applied) be a normal desktop resolution so that your site appears normal in very old browsers.

Flex-based Responsive Layout

Now, the float-based layout is really a bit of a hack, as the intent of the float property is really for the embedding of images and callouts to the sides of text content. Unfortunately, floats and tables were the only mechanisms in earlier versions of CSS to allow for more nuanced layouts.

However, that has changed with CSS3, which has introduced several new layout algorithms to supplement the traditional flow and table ones. One of these is flexbox, which we will explore now.

Flexbox provides an alternative means for laying out HTML elements within a containing element. You set the containing element’s display property to flex, and choose a flex-direction (row, column, row-reverse, or column-reverse).

It is very easy to set up our three-column layout with flexbox. Let’s do so now.

First, we’ll change the class we are applying to our containing div element in Pages/Index.cshtml. Let’s use "flex-columns" instead of our previous "float-columns":

<aside class="advertisement">
    <img src="~/img/ad.png" alt="Eat at Bernies!"/>
</aside>
<div class="flex-columns">
    <div>
        <h1>Column One</h1>
        ...

We can then add additional CSS rules to our wwwroot/css/site.css to apply the flex layout:

.flex-columns {
    display: flex;
    flex-direction: row;
}

Now if we run our program and look at our page, we’ll see that the flex algorithm has automatically arranged the children <div> elements into three equally-sized columns for us!

Flex-based columns layout Flex-based columns layout

Moreover, if we were to add or remove columns, the layout would automatically change to keep them balanced. You can also apply a number of additional properties to provide more fine-grained control of the layout; the CSS Tricks A Complete Guide to Flexbox offers a great guide to the details.

Now to make our layout responsive, we just need to switch the direction of our container:

@media (max-width: 490px) {
    .flex-columns {
        flex-direction: column;
    }
}

Now when we view our page on a smaller screen, it will use columns instead of rows!

Single Column Flex Layout Single Column Flex Layout

We also no longer need to bother with a clearfix <div>, though leaving the one we created in the prior example in place won’t cause any problems.

Grid-based

TODO: float based responsive layouts

Summary

TODO: float based responsive layouts

Web Data

Now that we’ve seen how to dynamically create the content of a Razor Page on the server, we should turn our attention to how we can do so based on our user’s needs. Consider the example application we have been developing - it exposes a database of movie information to the user. How might a user want to use this database? They might want to find the details for a specific movie - who directed it, when was it released, etc. They might be looking for a movie they want to watch in a favorite genre. They might be looking to find all the movies directed by a favorite director… there are a lot of possibilities.

As software developers, we need to anticipate how our users will likely want to use the software we are developing. And with that in mind, we need to develop user interfaces that allow the user to communicate those needs to the software we are writing as concisely as possible. Remember that in HTTP, all communication between the client (and its user) and the server (our web application), is mediated through the request-response mechanism:

Request-Response Mechanism Request-Response Mechanism

Thus, any information the user wants to supply the server is typically communicated through a request. HTML provides a mechanism for enabling this communication in a structured way - forms. A <form> element, when submitted, serializes the values of all <input> elements contained within it and submits them to the server in the request.

The values in the form are serialized (converted into text) and submitted with the request. Exactly how this serialization happens depends on the kind of request. For GET requests the serialized data is added to the url as the query string. For POST requests, the serialized data is sent as the body of the request.

You can usually use either form (except when uploading files, which must be POSTed). However, you might think about the semantics of each action. A GET request is for retrieving something from the server, so any data your sending is about modifying that request. Thus, search and filter requests make sense as a GET request. A POST request is about sending something to the server, thus account creation, blog posts, and so on make sense as a POST request.

Info

When sent using secure HTTP (HTTPS), both the query string portion of the URL and the request body are encrypted, so there is no difference in the in-transit security. However, browsers typically store the full URL in the history, so if sensitive information is being sent, you might want to use POST requests.

Subsections of Web Data

Adding Search to the Movie Site

Let’s add a search form and functionality to our movie website. We’ll add this to our Index.cshtml page, just above the <h1> element:

    <form>
        <input type="text" name="SearchTerms"/>
        <input type="submit" value="Search">
    </form>
    <h1>Movie Results</h1>

We’ll also change the <h1> contents to “Movie Results”.

Try typing a search term into the search box, and click the search button. Has anything changed?

The Request Object

When you click the search button, the browser serializes your form, and makes a request against your server including the search terms. By default this request is a GET request, and the contents of the form are serialized using urlencoding (aka percent encoding), a special string format. This string is then appended to the requested url as the query string (aka search string) - a series of key-value pairs proceeded by the question mark symbol(?) and separated by the ampersand (&).

This data is made available to us in our PageModel Index.cshtml.cs by ASP.NET. Let’s take a look at it now. Notice the method public void OnGet()? This method is invoked every time the page is requested using a GET request. Thus, if we need to do some initialization and/or processing, this would be the place to do it.

Inside the PageModel, we can access the request data using the Request object. The exact string can be accessed with Request.QueryString, or the parsed and deserialized results can be accessed from Request.Query. Let’s use the latter to pull out the search terms:

    public void OnGet() 
    {
        String terms = Request.Query["SearchTerms"];
    }

We can store that value, and make it available to the page itself, by creating a public property. Let’s create one named SearchTerms:

    public string SearchTerms { get; set; }

And we’ll refactor our OnGet() to store the search terms coming in from the request:

    public void OnGet() 
    {
        SearchTerms = Request.Query["SearchTerms"];
    }

Now we can refactor our input element to use that public property from our model as its default value:

    <input type="text" name="SearchTerms" value="@Model.SearchTerms"/>

The first time we visit the index page, the SearchTerms value will be null, so our input would have value="". The browser interprets this as empty. If we add a search term and click the search button, we’ll see the page reload. And since @Model.SearchTerms has a value this time, we’ll see that string appear in search box!

Now we just need to search for those terms…

Adding Search to the Database

We’ll start by defining a new static method in our MovieDatabase.cs file to search for movies using the search terms:

    /// <summary>
    /// Searches the database for matching movies
    /// </summary>
    /// <param name="terms">The terms to search for</param>
    /// <returns>A collection of movies</returns>
    public static IEnumerable<Movie> Search(string terms)
    {
        // TODO: Search database
    }

We’ll need a collection of results that implements the IEnumerable<T> interface. Let’s use the familiar List<T>:

    List<Movie> results = new List<Movie>();

Now, there is a chance that the search terms we receive are null. If that’s the case, we would either 1) return all the movies, or 2) return no movies. You can choose either option, but for now, I’ll return all movies

    // Return all movies if there are no search terms
    if(terms == null) return All;

If we do have search terms, we need to add any movies from our database that include those terms in the title. This requires us to check each movie in our database:

    // return each movie in the database containing the terms substring
    foreach(Movie movie in All)
    {
        if(movie.Title.Contains(terms, StringComparison.InvariantCultureIgnoreCase)) 
        {
            results.Add(movie);
        }
    }   

We’ll use String.Contains() to determine if our terms are a substring within the title, ignoring case differences. If we find it, we’ll add the movie to our results list.

Finally, we’ll return that list:

    return results;

Now, we can refactor our Index.cshtml.cs to use this new search method:

    /// <summary>
    /// The movies to display on the index page 
    /// </summary>
    public IEnumerable<Movie> Movies { get; protected set; }

    /// <summary>
    /// The current search terms 
    /// </summary>
    public string SearchTerms { get; set; }

    /// <summary>
    /// Gets the search results for display on the page
    /// </summary>
    public void OnGet() 
    {
        SearchTerms = Request.Query["SearchTerms"];
        Movies = MovieDatabase.Search(SearchTerms);
    }

We’ll also need ot refactor our Index.cshtml.cs to use the search results, instead of the entire database:

<ul class="movie-list">
    @foreach(Movie movie in @Model.Movies)
    {
        <li>
            <div class="details">
                <h3 class="title">@movie.Title</h3>
                <div class="mpaa">@movie.MPAARating</div>
                <div class="genre">@movie.MajorGenre</div>
            </div>
            <div class="ratings">
                @if(movie.IMDBRating != null)
                {
                    <div class="imdb">
                        @movie.IMDBRating
                    </div>
                }
                @if(movie.RottenTomatoesRating != null)
                {
                    <div class="rotten-tomatoes">
                        @movie.RottenTomatoesRating
                    </div>
                }
            </div>
        </li>
    }
</ul>

If we try running the project again, and searching for the term “Love”… it crashes? What is going on?

The Encountered Exception The Encountered Exception

Notice that the error is a NullReferenceException, and occurs in our if statement checking the title.

Bad Data

If we think about what variables are involved in the line if(movie.Title.Contains(terms, StringComparison.InvariantCultureIgnoreCase)), we have:

  • movie
  • movie.Title
  • terms

Which of these three values can be null? We know for certain terms is not, as we test for the null value and return if it exists just before this portion of our code. Similarly, movie cannot be null, as it is an entry in the list provided by All, and if it were null, our page would have crashed before we added searching. That leaves movie.Title as a possibility.

If we comb through the data in movies.json, we find on line 54957 a movie with null for a title:

  {
    "Title": null,
    "USGross": 26403,
    "WorldwideGross": 3080493,
    "USDVDSales": null,
    "ProductionBudget": 3700000,
    "ReleaseDate": "Nov 03 2006",
    "MPAARating": "Not Rated",
    "RunningTime": 85,
    "Distributor": "IFC Films",
    "Source": "Original Screenplay",
    "MajorGenre": "Thriller/Suspense",
    "CreativeType": "Contemporary Fiction",
    "Director": null,
    "RottenTomatoesRating": 39,
    "IMDBRating": 6.6,
    "IMDBVotes": 11986
  },

Working from the provided metadata, we can eventually identify the film as one titled Unknown. It would seem that whomever wrote the script to create this JSON file interpreted “Unknown” to mean the title was unknown (hence null), rather than the literal word “Unknown”.

If we dig deeper into the JSON file, we can find other issues. For example, the JSON identifies the controversial film Birth of a Nation as being released in 2015, when it was actually the first full-length theatrical film ever released, in 1915! Most likely the original database from which these entries were derived only used two digits for the year, i.e. 15, and the scripter who converted it to JSON chose a threshold date to determine if it was released in the 20 or 21st century, i.e.:

if(date < 28) 
{
    date += 2000;
}
else {
    date += 1900;
}

The earliest movie release date in the JSON is 1928, for “The Broadway Melody”, which suggests that all the movies released between 1915 and 1928 have been mislabeled as being released in the 21st century!

Unfortunately, these kinds of errors are rampant in databases, so as software developers we must be aware that our data may well be dirty - containing erroneous values, and anticipate these errors much like we do with user input. It is a good idea to clean up and fix these errors in our database so that it will be more reliable, but we also need to check for potential errors in our own code, as the database could be updated with more junk data in the future. Thus, we’ll add a null check to our if statement in MovieDatabase.cs:

if(movie.Title != null && movie.Title.Contains(terms, StringComparison.InvariantCultureIgnoreCase)) 

This will protect us against a NullReferenceException when our movie titles are null. Now if you try the search again, you should see the results:

The Search Results for “Love” The Search Results for “Love”

Adding Categorical Filters to the Movie Site

Let’s add some filters to the page as well. We’ll start with categorical filters, i.e. filtering by category. Let’s start by filtering for MPAA Rating. We know that there are only a handful of ratings issued by the Motion Picture Association of America - G, PG, PG-13, R, and NC-17. We might be tempted to use an enumeration to represent these values, but in C# an enumeration cannot have strings for values. Nor can we use the hyphen (-) in an enumeration name.

Defining the MPAA Ratings

So let’s define a string array with our MPAA values, and make it accessible from our MovieDatabase class:

    /// <summary>
    /// Gets the possible MPAARatings
    /// </summary>
    public static string[] MPAARatings
    {
        get => new string[]
        {
            "G",
            "PG",
            "PG-13",
            "R",
            "NC-17"
        };
    }

Now in our <form> in Index.cshtml we can add a checkbox for each of these possible values:

<form>
   <input type="text" name="SearchTerms" value="@Model.SearchTerms"/>
    <input type="submit" value="Search">
    @foreach  (string rating in MovieDatabase.MPAARating) 
    {
        <label>
            <input type="checkbox" name="MPAARatings" value="@rating"/>
            @rating
        </label>
    }
</form>

If you try running the project now, and check a few boxes, you’ll see the query string results look something like:

?SearchTerms=&MPAARatings=G&MPAARatings=PG-13

Notice how the key MPAARatings is repeated twice? What would that look like in our PageModel? We can find out; declare a var to hold the value in the OnGet() method of Index.cshtml.cs:

    var MPAARatings = Request.Query["MPAARatings"];

If we add a breakpoint on this line, and run our code, then check several boxes (you’ll have to continue the first time you hit the breakpoint), then step over the line, we’ll see that the var MPAA rating is set to a string collection. We could therefore store it in an array property in Index.cshtml.cs, much like we did with our SearchTerms:

    /// <summary>
    /// The filtered MPAA Ratings
    /// </summary>
    public string[] MPAARatings { get; set; }

And we can refactor the line we just added to OnGet() to use this new property:

    MPAARatings = Request.Query["MPAARatings"];

Then, in our Index.cshtml Razor Page, we can refactor the checkbox to be checked if we filtered against this rating in our last request:

    <input type="checkbox" name="MPAARatings" value="@rating" checked="@Model.MPAARatings.Contains(rating)"/>

Now our filters stick around when we submit the search request. That just leaves making the filters actually work.

Applying MPAA Rating Filters

Let’s add another method to our MovieDatabase class, FilterByMPAARating():

    /// <summary>
    /// Filters the provided collection of movies
    /// </summary>
    /// <param name="movies">The collection of movies to filter</param>
    /// <param name="ratings">The ratings to include</param>
    /// <returns>A collection containing only movies that match the filter</returns>
    public static IEnumerable<Movie> FilterByMPAARating(IEnumerable<Movie> movies, IEnumerable<string> ratings)
    {
        // TODO: Filter the list

    }

Notice that in this method, we accept an IEnumerable<Movie> parameter. This is the list of movies we want to filter. We use this, instead of the All() we did in the Search() method, as we would want to filter the results of a search.

Let’s do a null/empty check, and just return this shortlist if no filters are specified:

    // If no filter is specified, just return the provided collection
    if (ratings == null || ratings.Count() == 0) return movies;

Otherwise, we’ll use the same process we did before. Start with an empty list of movies, and iterate over the collection seeing if any match. However, as we have two collections (the movies and the ratings), we’ll see if the ratings collection contains the supplied movie’s rating.

    // Filter the supplied collection of movies
    List<Movie> results = new List<Movie>();
    foreach(Movie movie in movies)
    {
        if(movie.MPAARating != null && ratings.Contains(movie.MPAARating))
        {
            results.Add(movie);
        }
    }

Finally, we’ll return our results:

    return results;

Now, back in our PageModel Index.cshtml.cs, we’ll apply our filter to the results of our search. The refactored OnGet() should then be:

    public void OnGet()
    {
        SearchTerms = Request.Query["SearchTerms"];
        MPAARatings = Request.Query["MPAARatings"];
        Movies = MovieDatabase.Search(SearchTerms);
        Movies = MovieDatabase.FilterByMPAARating(Movies, MPAARatings);        
    }

Now we can run a search with filters applied. For example, searching for the word “Love” and movies that are PG or PG-13 yields:

Filtered Search Results Filtered Search Results

You might be wondering why Cloverfield is listed. But remember, we’re searching by substring, and C LOVE rfield contains love!

Filtering by Genre

Let’s add filters for genre next. But what genres should be included? This is not as clear-cut as our MPAA rating, as there is no standards organization that says “these are the only offical genres that exist.” In fact, new genres emerge from time to time. So a better source of this info might just be to see what Genres are defined in our data, i.e.:

    HashSet<string> genres = new HashSet<string>();
    foreach(Movie movie in All) {
        if(movie.MajorGenre != null) 
        {
            genres.Add(movie.MajorGenre);
        }
    }

Here we use a HashSet instead of a list, as it only adds each unique item once. Duplicates are ignored.

But where would this code go? We could place it in a getter for MovieDatabase.Genres:

    public IEnumerable<string> Genres 
    {
        get 
        {
            HashSet<string> genres = new HashSet<string>();
            foreach(Movie movie in All) {
                if(movie.MajorGenre != null) 
                {
                    genres.Add(movie.MajorGenre);
                }
            }
        }
    }

But this means that every time we want to access it, we’ll search through all the movies… This is an O(n) operation, and will make our website slower.

Instead, let’s create a private static variable in the MovieDatabase class to cache this collection as an array of strings:

    // The genres represented in the database
    private static string[] _genres;

And expose it with a public static property:

    /// <summary>
    /// Gets the movie genres represented in the database 
    /// </summary>
    public static string[] Genres => _genres;

And finally, we’ll populate this array in the static constructor of MovieDatabase, after the JSON file has been processed:

    HashSet<string> genreSet = new HashSet<string>();
    foreach(Movie movie in _movies) {
        if(movie.MajorGenre != null) 
        {
            genreSet.Add(movie.MajorGenre);
        }
    }
    _genres = genreSet.ToArray();

This approach means the finding of genres only happens once, and getting the Genre property is a constant-time O(1) operation.

We can implement the filters for the genres in the same way as we did for the MPAA filters; I’ll leave that as an exercise for the reader.

Adding Categorical Filters to the Movie Site

Let’s add some filters to the page as well. We’ll start with categorical filters, i.e. filtering by category. Let’s start by filtering for MPAA Rating. We know that there are only a handful of ratings issued by the Motion Picture Association of America - G, PG, PG-13, R, and NC-17. We might be tempted to use an enumeration to represent these values, but in C# an enumeration cannot have strings for values. Nor can we use the hyphen (-) in an enumeration name.

Defining the MPAA Ratings

So let’s define a string array with our MPAA values, and make it accessible from our MovieDatabase class:

    /// <summary>
    /// Gets the possible MPAARatings
    /// </summary>
    public static string[] MPAARatings
    {
        get => new string[]
        {
            "G",
            "PG",
            "PG-13",
            "R",
            "NC-17"
        };
    }

Now in our <form> in Index.cshtml we can add a checkbox for each of these possible values:

<form>
   <input type="text" name="SearchTerms" value="@Model.SearchTerms"/>
    <input type="submit" value="Search">
    @foreach  (string rating in MovieDatabase.MPAARating) 
    {
        <label>
            <input type="checkbox" name="MPAARatings" value="@rating"/>
            @rating
        </label>
    }
</form>

If you try running the project now, and check a few boxes, you’ll see the query string results look something like:

?SearchTerms=&MPAARatings=G&MPAARatings=PG-13

Notice how the key MPAARatings is repeated twice? What would that look like in our PageModel? We can find out; declare a var to hold the value in the OnGet() method of Index.cshtml.cs:

    var MPAARatings = Request.Query["MPAARatings"];

If we add a breakpoint on this line, and run our code, then check several boxes (you’ll have to continue the first time you hit the breakpoint), then step over the line, we’ll see that the var MPAA rating is set to a string collection. We could therefore store it in an array property in Index.cshtml.cs, much like we did with our SearchTerms:

    /// <summary>
    /// The filtered MPAA Ratings
    /// </summary>
    public string[] MPAARatings { get; set; }

And we can refactor the line we just added to OnGet() to use this new property:

    MPAARatings = Request.Query["MPAARatings"];

Then, in our Index.cshtml Razor Page, we can refactor the checkbox to be checked if we filtered against this rating in our last request:

    <input type="checkbox" name="MPAARatings" value="@rating" checked="@Model.MPAARatings.Contains(rating)"/>

Now our filters stick around when we submit the search request. That just leaves making the filters actually work.

Applying MPAA Rating Filters

Let’s add another method to our MovieDatabase class, FilterByMPAARating():

    /// <summary>
    /// Filters the provided collection of movies
    /// </summary>
    /// <param name="movies">The collection of movies to filter</param>
    /// <param name="ratings">The ratings to include</param>
    /// <returns>A collection containing only movies that match the filter</returns>
    public static IEnumerable<Movie> FilterByMPAARating(IEnumerable<Movie> movies, IEnumerable<string> ratings)
    {
        // TODO: Filter the list

    }

Notice that in this method, we accept an IEnumerable<Movie> parameter. This is the list of movies we want to filter. We use this, instead of the All() we did in the Search() method, as we would want to filter the results of a search.

Let’s do a null/empty check, and just return this shortlist if no filters are specified:

    // If no filter is specified, just return the provided collection
    if (ratings == null || ratings.Count() == 0) return movies;

Otherwise, we’ll use the same process we did before. Start with an empty list of movies, and iterate over the collection seeing if any match. However, as we have two collections (the movies and the ratings), we’ll see if the ratings collection contains the supplied movie’s rating.

    // Filter the supplied collection of movies
    List<Movie> results = new List<Movie>();
    foreach(Movie movie in movies)
    {
        if(movie.MPAARating != null && ratings.Contains(movie.MPAARating))
        {
            results.Add(movie);
        }
    }

Finally, we’ll return our results:

    return results;

Now, back in our PageModel Index.cshtml.cs, we’ll apply our filter to the results of our search. The refactored OnGet() should then be:

    public void OnGet()
    {
        SearchTerms = Request.Query["SearchTerms"];
        MPAARatings = Request.Query["MPAARatings"];
        Movies = MovieDatabase.Search(SearchTerms);
        Movies = MovieDatabase.FilterByMPAARating(Movies, MPAARatings);        
    }

Now we can run a search with filters applied. For example, searching for the word “Love” and movies that are PG or PG-13 yields:

Filtered Search Results Filtered Search Results

You might be wondering why Cloverfield is listed. But remember, we’re searching by substring, and C LOVE rfield contains love!

Filtering by Genre

Let’s add filters for genre next. But what genres should be included? This is not as clear-cut as our MPAA rating, as there is no standards organization that says “these are the only offical genres that exist.” In fact, new genres emerge from time to time. So a better source of this info might just be to see what Genres are defined in our data, i.e.:

    HashSet<string> genres = new HashSet<string>();
    foreach(Movie movie in All) {
        if(movie.MajorGenre != null) 
        {
            genres.Add(movie.MajorGenre);
        }
    }

Here we use a HashSet instead of a list, as it only adds each unique item once. Duplicates are ignored.

But where would this code go? We could place it in a getter for MovieDatabase.Genres:

    public IEnumerable<string> Genres 
    {
        get 
        {
            HashSet<string> genres = new HashSet<string>();
            foreach(Movie movie in All) {
                if(movie.MajorGenre != null) 
                {
                    genres.Add(movie.MajorGenre);
                }
            }
        }
    }

But this means that every time we want to access it, we’ll search through all the movies… This is an O(n) operation, and will make our website slower.

Instead, let’s create a private static variable in the MovieDatabase class to cache this collection as an array of strings:

    // The genres represented in the database
    private static string[] _genres;

And expose it with a public static property:

    /// <summary>
    /// Gets the movie genres represented in the database 
    /// </summary>
    public static string[] Genres => _genres;

And finally, we’ll populate this array in the static constructor of MovieDatabase, after the JSON file has been processed:

    HashSet<string> genreSet = new HashSet<string>();
    foreach(Movie movie in _movies) {
        if(movie.MajorGenre != null) 
        {
            genreSet.Add(movie.MajorGenre);
        }
    }
    _genres = genreSet.ToArray();

This approach means the finding of genres only happens once, and getting the Genre property is a constant-time O(1) operation.

We can implement the filters for the genres in the same way as we did for the MPAA filters; I’ll leave that as an exercise for the reader.

Adding Categorical Filters to the Movie Site

Let’s add some filters to the page as well. We’ll start with categorical filters, i.e. filtering by category. Let’s start by filtering for MPAA Rating. We know that there are only a handful of ratings issued by the Motion Picture Association of America - G, PG, PG-13, R, and NC-17. We might be tempted to use an enumeration to represent these values, but in C# an enumeration cannot have strings for values. Nor can we use the hyphen (-) in an enumeration name.

Defining the MPAA Ratings

So let’s define a string array with our MPAA values, and make it accessible from our MovieDatabase class:

    /// <summary>
    /// Gets the possible MPAARatings
    /// </summary>
    public static string[] MPAARatings
    {
        get => new string[]
        {
            "G",
            "PG",
            "PG-13",
            "R",
            "NC-17"
        };
    }

Now in our <form> in Index.cshtml we can add a checkbox for each of these possible values:

<form>
   <input type="text" name="SearchTerms" value="@Model.SearchTerms"/>
    <input type="submit" value="Search">
    @foreach  (string rating in MovieDatabase.MPAARatings) 
    {
        <label>
            <input type="checkbox" name="MPAARatings" value="@rating"/>
            @rating
        </label>
    }
</form>

If you try running the project now, and check a few boxes, you’ll see the query string results look something like:

?SearchTerms=&MPAARatings=G&MPAARatings=PG-13

Notice how the key MPAARatings is repeated twice? What would that look like in our PageModel? We can find out; declare a var to hold the value in the OnGet() method of Index.cshtml.cs:

    var MPAARatings = Request.Query["MPAARatings"];

If we add a breakpoint on this line, and run our code, then check several boxes (you’ll have to continue the first time you hit the breakpoint), then step over the line, we’ll see that the var MPAA rating is set to a string collection. We could therefore store it in an array property in Index.cshtml.cs, much like we did with our SearchTerms:

    /// <summary>
    /// The filtered MPAA Ratings
    /// </summary>
    public string[] MPAARatings { get; set; }

And we can refactor the line we just added to OnGet() to use this new property:

    MPAARatings = Request.Query["MPAARatings"];

Then, in our Index.cshtml Razor Page, we can refactor the checkbox to be checked if we filtered against this rating in our last request:

    <input type="checkbox" name="MPAARatings" value="@rating" checked="@Model.MPAARatings.Contains(rating)"/>

Now our filters stick around when we submit the search request. That just leaves making the filters actually work.

Applying MPAA Rating Filters

Let’s add another method to our MovieDatabase class, FilterByMPAARating():

    /// <summary>
    /// Filters the provided collection of movies
    /// </summary>
    /// <param name="movies">The collection of movies to filter</param>
    /// <param name="ratings">The ratings to include</param>
    /// <returns>A collection containing only movies that match the filter</returns>
    public static IEnumerable<Movie> FilterByMPAARating(IEnumerable<Movie> movies, IEnumerable<string> ratings)
    {
        // TODO: Filter the list

    }

Notice that in this method, we accept an IEnumerable<Movie> parameter. This is the list of movies we want to filter. We use this, instead of the All() we did in the Search() method, as we would want to filter the results of a search.

Let’s do a null/empty check, and just return this shortlist if no filters are specified:

    // If no filter is specified, just return the provided collection
    if (ratings == null || ratings.Count() == 0) return movies;

Otherwise, we’ll use the same process we did before. Start with an empty list of movies, and iterate over the collection seeing if any match. However, as we have two collections (the movies and the ratings), we’ll see if the ratings collection contains the supplied movie’s rating.

    // Filter the supplied collection of movies
    List<Movie> results = new List<Movie>();
    foreach(Movie movie in movies)
    {
        if(movie.MPAARating != null && ratings.Contains(movie.MPAARating))
        {
            results.Add(movie);
        }
    }

Finally, we’ll return our results:

    return results;

Now, back in our PageModel Index.cshtml.cs, we’ll apply our filter to the results of our search. The refactored OnGet() should then be:

    public void OnGet()
    {
        SearchTerms = Request.Query["SearchTerms"];
        MPAARatings = Request.Query["MPAARatings"];
        Movies = MovieDatabase.Search(SearchTerms);
        Movies = MovieDatabase.FilterByMPAARating(Movies, MPAARatings);        
    }

Now we can run a search with filters applied. For example, searching for the word “Love” and movies that are PG or PG-13 yields:

Filtered Search Results Filtered Search Results

You might be wondering why Cloverfield is listed. But remember, we’re searching by substring, and C LOVE rfield contains love!

Filtering by Genre

Let’s add filters for genre next. But what genres should be included? This is not as clear-cut as our MPAA rating, as there is no standards organization that says “these are the only offical genres that exist.” In fact, new genres emerge from time to time. So a better source of this info might just be to see what Genres are defined in our data, i.e.:

    HashSet<string> genres = new HashSet<string>();
    foreach(Movie movie in All) {
        if(movie.MajorGenre != null) 
        {
            genres.Add(movie.MajorGenre);
        }
    }

Here we use a HashSet instead of a list, as it only adds each unique item once. Duplicates are ignored.

But where would this code go? We could place it in a getter for MovieDatabase.Genres:

    public IEnumerable<string> Genres 
    {
        get 
        {
            HashSet<string> genres = new HashSet<string>();
            foreach(Movie movie in All) {
                if(movie.MajorGenre != null) 
                {
                    genres.Add(movie.MajorGenre);
                }
            }
        }
    }

But this means that every time we want to access it, we’ll search through all the movies… This is an O(n) operation, and will make our website slower.

Instead, let’s create a private static variable in the MovieDatabase class to cache this collection as an array of strings:

    // The genres represented in the database
    private static string[] _genres;

And expose it with a public static property:

    /// <summary>
    /// Gets the movie genres represented in the database 
    /// </summary>
    public static string[] Genres => _genres;

And finally, we’ll populate this array in the static constructor of MovieDatabase, after the JSON file has been processed:

    HashSet<string> genreSet = new HashSet<string>();
    foreach(Movie movie in _movies) {
        if(movie.MajorGenre != null) 
        {
            genreSet.Add(movie.MajorGenre);
        }
    }
    _genres = genreSet.ToArray();

This approach means the finding of genres only happens once, and getting the Genre property is a constant-time O(1) operation.

We can implement the filters for the genres in the same way as we did for the MPAA filters; I’ll leave that as an exercise for the reader.

Adding Categorical Filters to the Movie Site

Let’s add some filters to the page as well. We’ll start with categorical filters, i.e. filtering by category. Let’s start by filtering for MPAA Rating. We know that there are only a handful of ratings issued by the Motion Picture Association of America - G, PG, PG-13, R, and NC-17. We might be tempted to use an enumeration to represent these values, but in C# an enumeration cannot have strings for values. Nor can we use the hyphen (-) in an enumeration name.

Defining the MPAA Ratings

So let’s define a string array with our MPAA values, and make it accessible from our MovieDatabase class:

    /// <summary>
    /// Gets the possible MPAARatings
    /// </summary>
    public static string[] MPAARatings
    {
        get => new string[]
        {
            "G",
            "PG",
            "PG-13",
            "R",
            "NC-17"
        };
    }

Now in our <form> in Index.cshtml we can add a checkbox for each of these possible values:

<form>
   <input type="text" name="SearchTerms" value="@Model.SearchTerms"/>
    <input type="submit" value="Search">
    @foreach  (string rating in MovieDatabase.MPAARatings) 
    {
        <label>
            <input type="checkbox" name="MPAARatings" value="@rating"/>
            @rating
        </label>
    }
</form>

If you try running the project now, and check a few boxes, you’ll see the query string results look something like:

?SearchTerms=&MPAARatings=G&MPAARatings=PG-13

Notice how the key MPAARatings is repeated twice? What would that look like in our PageModel? We can find out; declare a var to hold the value in the OnGet() method of Index.cshtml.cs:

    var MPAARatings = Request.Query["MPAARatings"];

If we add a breakpoint on this line, and run our code, then check several boxes (you’ll have to continue the first time you hit the breakpoint), then step over the line, we’ll see that the var MPAA rating is set to a string collection. We could therefore store it in an array property in Index.cshtml.cs, much like we did with our SearchTerms:

    /// <summary>
    /// The filtered MPAA Ratings
    /// </summary>
    public string[] MPAARatings { get; set; }

And we can refactor the line we just added to OnGet() to use this new property:

    MPAARatings = Request.Query["MPAARatings"];

Then, in our Index.cshtml Razor Page, we can refactor the checkbox to be checked if we filtered against this rating in our last request:

    <input type="checkbox" name="MPAARatings" value="@rating" checked="@Model.MPAARatings.Contains(rating)"/>

Now our filters stick around when we submit the search request. That just leaves making the filters actually work.

Applying MPAA Rating Filters

Let’s add another method to our MovieDatabase class, FilterByMPAARating():

    /// <summary>
    /// Filters the provided collection of movies
    /// </summary>
    /// <param name="movies">The collection of movies to filter</param>
    /// <param name="ratings">The ratings to include</param>
    /// <returns>A collection containing only movies that match the filter</returns>
    public static IEnumerable<Movie> FilterByMPAARating(IEnumerable<Movie> movies, IEnumerable<string> ratings)
    {
        // TODO: Filter the list

    }

Notice that in this method, we accept an IEnumerable<Movie> parameter. This is the list of movies we want to filter. We use this, instead of the All() we did in the Search() method, as we would want to filter the results of a search.

Let’s do a null/empty check, and just return this shortlist if no filters are specified:

    // If no filter is specified, just return the provided collection
    if (ratings == null || ratings.Count() == 0) return movies;

Otherwise, we’ll use the same process we did before. Start with an empty list of movies, and iterate over the collection seeing if any match. However, as we have two collections (the movies and the ratings), we’ll see if the ratings collection contains the supplied movie’s rating.

    // Filter the supplied collection of movies
    List<Movie> results = new List<Movie>();
    foreach(Movie movie in movies)
    {
        if(movie.MPAARating != null && ratings.Contains(movie.MPAARating))
        {
            results.Add(movie);
        }
    }

Finally, we’ll return our results:

    return results;

Now, back in our PageModel Index.cshtml.cs, we’ll apply our filter to the results of our search. The refactored OnGet() should then be:

    public void OnGet()
    {
        SearchTerms = Request.Query["SearchTerms"];
        MPAARatings = Request.Query["MPAARatings"];
        Movies = MovieDatabase.Search(SearchTerms);
        Movies = MovieDatabase.FilterByMPAARating(Movies, MPAARatings);        
    }

Now we can run a search with filters applied. For example, searching for the word “Love” and movies that are PG or PG-13 yields:

Filtered Search Results Filtered Search Results

You might be wondering why Cloverfield is listed. But remember, we’re searching by substring, and C LOVE rfield contains love!

Filtering by Genre

Let’s add filters for genre next. But what genres should be included? This is not as clear-cut as our MPAA rating, as there is no standards organization that says “these are the only offical genres that exist.” In fact, new genres emerge from time to time. So a better source of this info might just be to see what Genres are defined in our data, i.e.:

    HashSet<string> genres = new HashSet<string>();
    foreach(Movie movie in All) {
        if(movie.MajorGenre != null) 
        {
            genres.Add(movie.MajorGenre);
        }
    }

Here we use a HashSet instead of a list, as it only adds each unique item once. Duplicates are ignored.

But where would this code go? We could place it in a getter for MovieDatabase.Genres:

    public IEnumerable<string> Genres 
    {
        get 
        {
            HashSet<string> genres = new HashSet<string>();
            foreach(Movie movie in All) {
                if(movie.MajorGenre != null) 
                {
                    genres.Add(movie.MajorGenre);
                }
            }

            return genres;
        }
    }

But this means that every time we want to access it, we’ll search through all the movies… This is an O(n) operation, and will make our website slower.

Instead, let’s create a private static variable in the MovieDatabase class to cache this collection as an array of strings:

    // The genres represented in the database
    private static string[] _genres;

And expose it with a public static property:

    /// <summary>
    /// Gets the movie genres represented in the database 
    /// </summary>
    public static string[] Genres => _genres;

And finally, we’ll populate this array in the static constructor of MovieDatabase, after the JSON file has been processed:

    HashSet<string> genreSet = new HashSet<string>();
    foreach(Movie movie in _movies) {
        if(movie.MajorGenre != null) 
        {
            genreSet.Add(movie.MajorGenre);
        }
    }
    _genres = genreSet.ToArray();

This approach means the finding of genres only happens once, and getting the Genre property is a constant-time O(1) operation.

We can implement the filters for the genres in the same way as we did for the MPAA filters; I’ll leave that as an exercise for the reader.

Adding Categorical Filters to the Movie Site

Let’s add some filters to the page as well. We’ll start with categorical filters, i.e. filtering by category. Let’s start by filtering for MPAA Rating. We know that there are only a handful of ratings issued by the Motion Picture Association of America - G, PG, PG-13, R, and NC-17. We might be tempted to use an enumeration to represent these values, but in C# an enumeration cannot have strings for values. Nor can we use the hyphen (-) in an enumeration name.

Defining the MPAA Ratings

So let’s define a string array with our MPAA values, and make it accessible from our MovieDatabase class:

    /// <summary>
    /// Gets the possible MPAARatings
    /// </summary>
    public static string[] MPAARatings
    {
        get => new string[]
        {
            "G",
            "PG",
            "PG-13",
            "R",
            "NC-17"
        };
    }

Now in our <form> in Index.cshtml we can add a checkbox for each of these possible values:

<form>
   <input type="text" name="SearchTerms" value="@Model.SearchTerms"/>
    <input type="submit" value="Search">
    @foreach  (string rating in MovieDatabase.MPAARatings) 
    {
        <label>
            <input type="checkbox" name="MPAARatings" value="@rating"/>
            @rating
        </label>
    }
</form>

If you try running the project now, and check a few boxes, you’ll see the query string results look something like:

?SearchTerms=&MPAARatings=G&MPAARatings=PG-13

Notice how the key MPAARatings is repeated twice? What would that look like in our PageModel? We can find out; declare a var to hold the value in the OnGet() method of Index.cshtml.cs:

    var MPAARatings = Request.Query["MPAARatings"];

If we add a breakpoint on this line, and run our code, then check several boxes (you’ll have to continue the first time you hit the breakpoint), then step over the line, we’ll see that the var MPAA rating is set to a string collection. We could therefore store it in an array property in Index.cshtml.cs, much like we did with our SearchTerms:

    /// <summary>
    /// The filtered MPAA Ratings
    /// </summary>
    public string[] MPAARatings { get; set; }

And we can refactor the line we just added to OnGet() to use this new property:

    MPAARatings = Request.Query["MPAARatings"];

Then, in our Index.cshtml Razor Page, we can refactor the checkbox to be checked if we filtered against this rating in our last request:

    <input type="checkbox" name="MPAARatings" value="@rating" checked="@Model.MPAARatings.Contains(rating)"/>

Now our filters stick around when we submit the search request. That just leaves making the filters actually work.

Applying MPAA Rating Filters

Let’s add another method to our MovieDatabase class, FilterByMPAARating():

    /// <summary>
    /// Filters the provided collection of movies
    /// </summary>
    /// <param name="movies">The collection of movies to filter</param>
    /// <param name="ratings">The ratings to include</param>
    /// <returns>A collection containing only movies that match the filter</returns>
    public static IEnumerable<Movie> FilterByMPAARating(IEnumerable<Movie> movies, IEnumerable<string> ratings)
    {
        // TODO: Filter the list

    }

Notice that in this method, we accept an IEnumerable<Movie> parameter. This is the list of movies we want to filter. We use this, instead of the All() we did in the Search() method, as we would want to filter the results of a search.

Let’s do a null/empty check, and just return this shortlist if no filters are specified:

    // If no filter is specified, just return the provided collection
    if (ratings == null || ratings.Count() == 0) return movies;

Otherwise, we’ll use the same process we did before. Start with an empty list of movies, and iterate over the collection seeing if any match. However, as we have two collections (the movies and the ratings), we’ll see if the ratings collection contains the supplied movie’s rating.

    // Filter the supplied collection of movies
    List<Movie> results = new List<Movie>();
    foreach(Movie movie in movies)
    {
        if(movie.MPAARating != null && ratings.Contains(movie.MPAARating))
        {
            results.Add(movie);
        }
    }

Finally, we’ll return our results:

    return results;

Now, back in our PageModel Index.cshtml.cs, we’ll apply our filter to the results of our search. The refactored OnGet() should then be:

    public void OnGet()
    {
        SearchTerms = Request.Query["SearchTerms"];
        MPAARatings = Request.Query["MPAARatings"];
        Movies = MovieDatabase.Search(SearchTerms);
        Movies = MovieDatabase.FilterByMPAARating(Movies, MPAARatings);        
    }

Now we can run a search with filters applied. For example, searching for the word “Love” and movies that are PG or PG-13 yields:

Filtered Search Results Filtered Search Results

You might be wondering why Cloverfield is listed. But remember, we’re searching by substring, and C LOVE rfield contains love!

Filtering by Genre

Let’s add filters for genre next. But what genres should be included? This is not as clear-cut as our MPAA rating, as there is no standards organization that says “these are the only offical genres that exist.” In fact, new genres emerge from time to time. So a better source of this info might just be to see what Genres are defined in our data, i.e.:

    HashSet<string> genres = new HashSet<string>();
    foreach(Movie movie in All) {
        if(movie.MajorGenre != null) 
        {
            genres.Add(movie.MajorGenre);
        }
    }

Here we use a HashSet instead of a list, as it only adds each unique item once. Duplicates are ignored.

But where would this code go? We could place it in a getter for MovieDatabase.Genres:

    public static IEnumerable<string> Genres 
    {
        get 
        {
            HashSet<string> genres = new HashSet<string>();
            foreach(Movie movie in All) {
                if(movie.MajorGenre != null) 
                {
                    genres.Add(movie.MajorGenre);
                }
            }

            return genres;
        }
    }

But this means that every time we want to access it, we’ll search through all the movies… This is an O(n) operation, and will make our website slower.

Instead, let’s create a private static variable in the MovieDatabase class to cache this collection as an array of strings:

    // The genres represented in the database
    private static string[] _genres;

And expose it with a public static property:

    /// <summary>
    /// Gets the movie genres represented in the database 
    /// </summary>
    public static string[] Genres => _genres;

And finally, we’ll populate this array in the static constructor of MovieDatabase, after the JSON file has been processed:

    HashSet<string> genreSet = new HashSet<string>();
    foreach(Movie movie in _movies) {
        if(movie.MajorGenre != null) 
        {
            genreSet.Add(movie.MajorGenre);
        }
    }
    _genres = genreSet.ToArray();

This approach means the finding of genres only happens once, and getting the Genre property is a constant-time O(1) operation.

We can implement the filters for the genres in the same way as we did for the MPAA filters; I’ll leave that as an exercise for the reader.

Adding Categorical Filters to the Movie Site

Let’s add some filters to the page as well. We’ll start with categorical filters, i.e. filtering by category. Let’s start by filtering for MPAA Rating. We know that there are only a handful of ratings issued by the Motion Picture Association of America - G, PG, PG-13, R, and NC-17. We might be tempted to use an enumeration to represent these values, but in C# an enumeration cannot have strings for values. Nor can we use the hyphen (-) in an enumeration name.

Defining the MPAA Ratings

So let’s define a string array with our MPAA values, and make it accessible from our MovieDatabase class:

    /// <summary>
    /// Gets the possible MPAARatings
    /// </summary>
    public static string[] MPAARatings
    {
        get => new string[]
        {
            "G",
            "PG",
            "PG-13",
            "R",
            "NC-17"
        };
    }

Now in our <form> in Index.cshtml we can add a checkbox for each of these possible values:

<form>
    @foreach  (string rating in MovieDatabase.MPAARatings) 
    {
        <label>
            <input type="checkbox" name="MPAARatings" value="@rating"/>
            @rating
        </label>
    }
    
    <input type="text" name="SearchTerms" value="@Model.SearchTerms"/>
    <input type="submit" value="Search">
</form>

If you try running the project now, and check a few boxes, you’ll see the query string results look something like:

?SearchTerms=&MPAARatings=G&MPAARatings=PG-13

Notice how the key MPAARatings is repeated twice? What would that look like in our PageModel? We can find out; declare a var to hold the value in the OnGet() method of Index.cshtml.cs:

    var MPAARatings = Request.Query["MPAARatings"];

If we add a breakpoint on this line, and run our code, then check several boxes (you’ll have to continue the first time you hit the breakpoint), then step over the line, we’ll see that the var MPAA rating is set to a string collection. We could therefore store it in an array property in Index.cshtml.cs, much like we did with our SearchTerms:

    /// <summary>
    /// The filtered MPAA Ratings
    /// </summary>
    public string[] MPAARatings { get; set; }

And we can refactor the line we just added to OnGet() to use this new property:

    MPAARatings = Request.Query["MPAARatings"];

Then, in our Index.cshtml Razor Page, we can refactor the checkbox to be checked if we filtered against this rating in our last request:

    <input type="checkbox" name="MPAARatings" value="@rating" checked="@Model.MPAARatings.Contains(rating)"/>

Now our filters stick around when we submit the search request. That just leaves making the filters actually work.

Applying MPAA Rating Filters

Let’s add another method to our MovieDatabase class, FilterByMPAARating():

    /// <summary>
    /// Filters the provided collection of movies
    /// </summary>
    /// <param name="movies">The collection of movies to filter</param>
    /// <param name="ratings">The ratings to include</param>
    /// <returns>A collection containing only movies that match the filter</returns>
    public static IEnumerable<Movie> FilterByMPAARating(IEnumerable<Movie> movies, IEnumerable<string> ratings)
    {
        // TODO: Filter the list

    }

Notice that in this method, we accept an IEnumerable<Movie> parameter. This is the list of movies we want to filter. We use this, instead of the All() we did in the Search() method, as we would want to filter the results of a search.

Let’s do a null/empty check, and just return this shortlist if no filters are specified:

    // If no filter is specified, just return the provided collection
    if (ratings == null || ratings.Count() == 0) return movies;

Otherwise, we’ll use the same process we did before. Start with an empty list of movies, and iterate over the collection seeing if any match. However, as we have two collections (the movies and the ratings), we’ll see if the ratings collection contains the supplied movie’s rating.

    // Filter the supplied collection of movies
    List<Movie> results = new List<Movie>();
    foreach(Movie movie in movies)
    {
        if(movie.MPAARatings != null && ratings.Contains(movie.MPAARatings))
        {
            results.Add(movie);
        }
    }

Finally, we’ll return our results:

    return results;

Now, back in our PageModel Index.cshtml.cs, we’ll apply our filter to the results of our search. The refactored OnGet() should then be:

    public void OnGet()
    {
        SearchTerms = Request.Query["SearchTerms"];
        MPAARatings = Request.Query["MPAARatings"];
        Movies = MovieDatabase.Search(SearchTerms);
        Movies = MovieDatabase.FilterByMPAARating(Movies, MPAARatings);        
    }

Now we can run a search with filters applied. For example, searching for the word “Love” and movies that are PG or PG-13 yields:

Filtered Search Results Filtered Search Results

You might be wondering why Cloverfield is listed. But remember, we’re searching by substring, and C LOVE rfield contains love!

Filtering by Genre

Let’s add filters for genre next. But what genres should be included? This is not as clear-cut as our MPAA rating, as there is no standards organization that says “these are the only offical genres that exist.” In fact, new genres emerge from time to time. So a better source of this info might just be to see what Genres are defined in our data, i.e.:

    HashSet<string> genres = new HashSet<string>();
    foreach(Movie movie in All) {
        if(movie.MajorGenre != null) 
        {
            genres.Add(movie.MajorGenre);
        }
    }

Here we use a HashSet instead of a list, as it only adds each unique item once. Duplicates are ignored.

But where would this code go? We could place it in a getter for MovieDatabase.Genres:

    public IEnumerable<string> Genres 
    {
        get 
        {
            HashSet<string> genres = new HashSet<string>();
            foreach(Movie movie in All) {
                if(movie.MajorGenre != null) 
                {
                    genres.Add(movie.MajorGenre);
                }
            }
        }
    }

But this means that every time we want to access it, we’ll search through all the movies… This is an O(n) operation, and will make our website slower.

Instead, let’s create a private static variable in the MovieDatabase class to cache this collection as an array of strings:

    // The genres represented in the database
    private static string[] _genres;

And expose it with a public static property:

    /// <summary>
    /// Gets the movie genres represented in the database 
    /// </summary>
    public static string[] Genres => _genres;

And finally, we’ll populate this array in the static constructor of MovieDatabase, after the JSON file has been processed:

    HashSet<string> genreSet = new HashSet<string>();
    foreach(Movie movie in _movies) {
        if(movie.MajorGenre != null) 
        {
            genreSet.Add(movie.MajorGenre);
        }
    }
    _genres = genreSet.ToArray();

This approach means the finding of genres only happens once, and getting the Genre property is a constant-time O(1) operation.

We can implement the filters for the genres in the same way as we did for the MPAA filters; I’ll leave that as an exercise for the reader.

Numerical Filters

Let’s tackle one of the critics ratings next. While we could create categories and use checkboxes, this doesn’t capture the incremental values (i.e. 4.3), and it would be a lot of checkboxes for Rotten Tomatoes ratings! Instead, we’ll use a numerical filter, which limits our possible results to a range - between a minimum and maximum value.

Moreover, let’s clean up our Index page, as it is getting difficult to determine what filter(s) go together, and and are adding more.

Refactoring the Index Page

Let’s move the filters to a column on the left, leave the search bar above, and show our results on the right. This will require refactoring our Index.cshtml file:

    <form id="movie-database">

        <div id="search">
            <input type="text" name="SearchTerms" value="@Model.SearchTerms" />
            <input type="submit" value="Search">
        </div>

        <div id="filters">

            <h4>MPAA Rating</h4>
            @foreach (string rating in MovieDatabase.MPAARating)
            {
                <label>
                    <input type="checkbox" name="MPAARatings" value="@rating" checked="@Model.MPAARatings.Contains(rating)" />
                    @rating
                </label>
            }

            <h4>Genre</h4>
            @foreach (string genre in MovieDatabase.Genres)
            {
                <label>
                    <input type="checkbox" name="Genres" value="@genre" />
                    @genre
                </label>
            }

            <h4>IMDB Rating</h4>
            <div>
                Between
                <input name="IMDBMin" type="number" min="0" max="10" step="0.1" placeholder="min"/>
                and
                <input name="IMDBMax" type="number" min="0" max="10" step="0.1" placeholder="max"/>
            </div>

        </div>

        <div id="results">
            <h1>Movie Results</h1>

            <ul class="movie-list">
                @foreach (Movie movie in @Model.Movies)
                {
                    <li>
                        <div class="details">
                            <h3 class="title">@movie.Title</h3>
                            <div class="mpaa">@movie.MPAARating</div>
                            <div class="genre">@movie.MajorGenre</div>
                        </div>
                        <div class="ratings">
                            @if (movie.IMDBRating != null)
                            {
                                <div class="imdb">
                                    @movie.IMDBRating
                                </div>
                            }
                            @if (movie.RottenTomatoesRating != null)
                            {
                                <div class="rotten-tomatoes">
                                    @movie.RottenTomatoesRating
                                </div>
                            }
                        </div>
                    </li>
                }
            </ul>
        </div>

    </form>

Most of this is simply moving elements around the page, but note that we are using inputs of type=number to represent our range of IMDB values. We can specify a minimum and maximum for this range, as well as an allowable increment. Also, we use the placeholder attribute to put text into the input until a value is added.

Adding More Styles

Now we’ll need to add some rules to our wwwroot/css/styles.css. First, we’ll use a grid for the layout of the form:

form#movie-database {
    display: grid;
    grid-template-columns: 1fr 3fr;
    grid-template-rows: auto auto;
}

The right column will be three times as big as the right.

We can make our search bar span both columns with grid-column-start and grid-column-end:

#search {
    grid-column-start: 1;
    grid-column-end: 3;
    text-align: center;
}

Notice too that for CSS, we start counting at 1, not 0. The filters and the results will fall in the next row automatically, each taking up their own respective grid cell. You can read more about the grid layout in A Complete Guide to Grid.

Let’s go ahead and use flexbox to lay out our filters in a column:

#filters {
    display: flex;
    flex-direction: column;
}

And make our number inputs bigger:

#filters input[type=number] {
    width: 4rem;
}

Notice the use of square brackets in our CSS Selector to only apply to inputs with type number.

Also, let’s add a margin above and remove most of the margin below our <h4> elements:

#filters h4 {
    margin-bottom: 0.2rem;
    margin-top: 2rem;
}

The resulting page looks much cleaner:

The Styled Page The Styled Page

Capturing the Filter Values

Now we need to get the filter values from our GET request query string. We could do this like we’ve been doing before, with:

    Request.Query["IMDBMin"];

But the returned value would be a string, so we’d need to parse it:

    IMDBMin = double.Parse(Request.Query["IMDBMin"]);

If the query was null, then this would evaluate to NaN, which we wouldn’t want to set our <input> to…

Instead, we’ll look at some options built into the PageModel.

Parameter Binding

The first of these options is Parameter Binding. In this approach, we define parameters to our OnGet() method to be parsed out of the request automatically, i.e.:

    /// <summary>
    /// Gets the search results for display on the page
    /// </summary>
    public void OnGet(string SearchTerms, string[] MPAARatings, string[] Genres, double? IMDBMin, double? IMDBMax) {
        this.SearchTerms = SearchTerms;
        this.MPAARatings = MPAARatings;
        this.Genres = Genres;
        this.IMDBMin = IMDBMin;
        this.IMDBMax = IMDBMax;
        Movies = MovieDatabase.Search(SearchTerms);
        Movies = MovieDatabase.FilterByMPAARating(Movies, MPAARatings);
        Movies = MovieDatabase.FilterByGenre(Movies, Genres);
        Movies = MovieDatabase.FilterByIMDBRating(Movies, IMDBMin, IMDBMax);
    }

The benefit of this approach is that as long as C# knows a conversion into the type we specify, the conversion is done automatically. Note that the parameter name matches the name property of the corresponding <input> - this must be the case for the Razor Page to bind the parameter to the corresponding input value.

Note that we still need to assign these parameter values to the corresponding properties of our PageModel. If we don’t, then those properties will all be null, and the <inputs> rendered on our page will always be blank.

Model Binding

A second option is to use Model Binding. Model binding also automatically converts incoming form data, but in this case it binds directly to the properties of our PageModel. We indicate this form of binding with a [BindProperty] attribute, i.e.:

public class IndexModel : PageModel {

    [BindProperty(SupportsGet=true)]
    public string SearchTerms {get; set;}

    [BindProperty(SupportsGet=true)]
    public string[] MPAARatings {get; set;}

    [BindProperty(SupportsGet=true)]
    public string[] Genres {get; set;}

    [BindProperty(SupportsGet=true)]
    public double? IMDBMin {get; set;}

    [BindProperty(SupportsGet=true)]
    public double? IMDBMax {get; set;}

    /// <summary>
    /// Gets the search results for display on the page
    /// </summary>
    public void OnGet() {
        Movies = MovieDatabase.Search(SearchTerms);
        Movies = MovieDatabase.FilterByMPAARating(Movies, MPAARatings);
        Movies = MovieDatabase.FilterByGenre(Movies, Genres);
        Movies = MovieDatabase.FilterByIMDBRating(Movies, IMDBMin, IMDBMax);
    }
}

Note that with this approach, the incoming data is directly bound to the properties, so we don’t need to do any special assignments within our OnGet() method. Also, note that we have to use SupportsGet=true in order for this binding to occur on GET requests (by default, model binding only happens with POST requests).

Note

You only need to do one binding approach per property in a PageModel. I.e. you can just use the property decorator:

public class SomePageModel : PageModel
{
    [BindProperty(SupportsGet=true)]
    public float SomeProperty { get; set; }

    public void OnGet() {
        DoSomething(SomeProperty);
    }
}

or you might use parameter binding:

public class SomePageModel : PageModel
{
    public void OnGet(float SomeProperty) {
        DoSomething(SomeProperty);
    }
}

or you can parse it from the request:

public class SomePageModel : PageModel
{
    public void OnGet() {
        var someProperty = float.Parse(Request.Query["SomeProperty"]);
        DoSomething(someProperty);
    }
}

These are all different means of accessing the same data from the incoming request.

Now all we need to do is implement the actual filter.

Implementing the IMDB Rating Filter

We’ll define the new filter in our MovieDatabase class as another static method:

    /// <summary>
    /// Filters the provided collection of movies
    /// to those with IMDB ratings falling within
    /// the specified range
    /// </summary>
    /// <param name="movies">The collection of movies to filter</param>
    /// <param name="min">The minimum range value</param>
    /// <param name="max">The maximum range value</param>
    /// <returns>The filtered movie collection</returns>
    public static IEnumerable<Movie> FilterByIMDBRating(IEnumerable<Movie> movies, double? min, double? max)
    {
        // TODO: Filter movies
    }

Notice that here too we use the nullable double value. So our first step is probably to do a null check:

    if (min == null && max == null) return movies;

But what if only one is null? Should we filter for that part of the range? It wouldn’t be hard to do:

    var results = new List<Movie>();

    // only a maximum specified
    if(min == null)
    {
        foreach(Movie movie in movies)
        {
            if (movie.IMDBRating <= max) results.Add(movie);
        }
        return results;
    }

And the minimum would mirror that:

    // only a minimum specified
    if(max == null)
    {
        foreach(Movie movie in movies)
        {
            if (movie.IMDBRating >= min) results.Add(movie);
        }
        return results;
    }

Finally, we could handle the case where we have both a min and max value to our range:

    // Both minimum and maximum specified
    foreach(Movie movie in movies)
    {
        if(movie.IMDBRating >= min && movie.IMDBRating <= max)
        {
            results.Add(movie);
        }
    }
    return results;

Notice too, that in each of these cases we’re treating the range as inclusive (including the specified minimum and maximum). This is the behavior most casual internet users will expect. If the database and user expectations are different for your audience, you’d want your code to match that expectation.

Now we can filter by IMDB rating:

Filtering By IMDB Filtering By IMDB

Finishing Up

Since we’re displaying the Rotten Tomatoes rating, we should probably also have a filter for it. This will work almost exactly like the IMDB rating - but with the range from 0 to 100. I’ll leave this as an exercise for the reader.

Numerical Filters

Let’s tackle one of the critics ratings next. While we could create categories and use checkboxes, this doesn’t capture the incremental values (i.e. 4.3), and it would be a lot of checkboxes for Rotten Tomatoes ratings! Instead, we’ll use a numerical filter, which limits our possible results to a range - between a minimum and maximum value.

Moreover, let’s clean up our Index page, as it is getting difficult to determine what filter(s) go together, and and are adding more.

Refactoring the Index Page

Let’s move the filters to a column on the left, leave the search bar above, and show our results on the right. This will require refactoring our Index.cshtml file:

    <form id="movie-database">

        <div id="search">
            <input type="text" name="SearchTerms" value="@Model.SearchTerms" />
            <input type="submit" value="Search">
        </div>

        <div id="filters">

            <h4>MPAA Rating</h4>
            @foreach (string rating in MovieDatabase.MPAARating)
            {
                <label>
                    <input type="checkbox" name="MPAARatings" value="@rating" checked="@Model.MPAARatings.Contains(rating)" />
                    @rating
                </label>
            }

            <h4>Genre</h4>
            @foreach (string genre in MovieDatabase.Genres)
            {
                <label>
                    <input type="checkbox" name="Genres" value="@genre" />
                    @genre
                </label>
            }

            <h4>IMDB Rating</h4>
            <div>
                Between
                <input name="IMDBMin" type="number" min="0" max="10" step="0.1" placeholder="min"/>
                and
                <input name="IMDBMax" type="number" min="0" max="10" step="0.1" placeholder="max"/>
            </div>

        </div>

        <div id="results">
            <h1>Movie Results</h1>

            <ul class="movie-list">
                @foreach (Movie movie in @Model.Movies)
                {
                    <li>
                        <div class="details">
                            <h3 class="title">@movie.Title</h3>
                            <div class="mpaa">@movie.MPAARating</div>
                            <div class="genre">@movie.MajorGenre</div>
                        </div>
                        <div class="ratings">
                            @if (movie.IMDBRating != null)
                            {
                                <div class="imdb">
                                    @movie.IMDBRating
                                </div>
                            }
                            @if (movie.RottenTomatoesRating != null)
                            {
                                <div class="rotten-tomatoes">
                                    @movie.RottenTomatoesRating
                                </div>
                            }
                        </div>
                    </li>
                }
            </ul>
        </div>

    </form>

Most of this is simply moving elements around the page, but note that we are using inputs of type=number to represent our range of IMDB values. We can specify a minimum and maximum for this range, as well as an allowable increment. Also, we use the placeholder attribute to put text into the input until a value is added.

Adding More Styles

Now we’ll need to add some rules to our wwwroot/css/styles.css. First, we’ll use a grid for the layout of the form:

form#movie-database {
    display: grid;
    grid-template-columns: 1fr 3fr;
    grid-template-rows: auto auto;
}

The right column will be three times as big as the left.

We can make our search bar span both columns with grid-column-start and grid-column-end:

#search {
    grid-column-start: 1;
    grid-column-end: 3;
    text-align: center;
}

Notice too that for CSS, we start counting at 1, not 0. The filters and the results will fall in the next row automatically, each taking up their own respective grid cell. You can read more about the grid layout in A Complete Guide to Grid.

Let’s go ahead and use flexbox to lay out our filters in a column:

#filters {
    display: flex;
    flex-direction: column;
}

And make our number inputs bigger:

#filters input[type=number] {
    width: 4rem;
}

Notice the use of square brackets in our CSS Selector to only apply to inputs with type number.

Also, let’s add a margin above and remove most of the margin below our <h4> elements:

#filters h4 {
    margin-bottom: 0.2rem;
    margin-top: 2rem;
}

The resulting page looks much cleaner:

The Styled Page The Styled Page

Capturing the Filter Values

Now we need to get the filter values from our GET request query string. We could do this like we’ve been doing before, with:

    Request.Query["IMDBMin"];

But the returned value would be a string, so we’d need to parse it:

    IMDBMin = double.Parse(Request.Query["IMDBMin"]);

If the query was null, then this would evaluate to NaN, which we wouldn’t want to set our <input> to…

Instead, we’ll look at some options built into the PageModel.

Parameter Binding

The first of these options is Parameter Binding. In this approach, we define parameters to our OnGet() method to be parsed out of the request automatically, i.e.:

    /// <summary>
    /// Gets the search results for display on the page
    /// </summary>
    public void OnGet(string SearchTerms, string[] MPAARatings, string[] Genres, double? IMDBMin, double? IMDBMax) {
        this.SearchTerms = SearchTerms;
        this.MPAARatings = MPAARatings;
        this.Genres = Genres;
        this.IMDBMin = IMDBMin;
        this.IMDBMax = IMDBMax;
        Movies = MovieDatabase.Search(SearchTerms);
        Movies = MovieDatabase.FilterByMPAARating(Movies, MPAARatings);
        Movies = MovieDatabase.FilterByGenre(Movies, Genres);
        Movies = MovieDatabase.FilterByIMDBRating(Movies, IMDBMin, IMDBMax);
    }

The benefit of this approach is that as long as C# knows a conversion into the type we specify, the conversion is done automatically. Note that the parameter name matches the name property of the corresponding <input> - this must be the case for the Razor Page to bind the parameter to the corresponding input value.

Note that we still need to assign these parameter values to the corresponding properties of our PageModel. If we don’t, then those properties will all be null, and the <inputs> rendered on our page will always be blank.

Model Binding

A second option is to use Model Binding. Model binding also automatically converts incoming form data, but in this case it binds directly to the properties of our PageModel. We indicate this form of binding with a [BindProperty] attribute, i.e.:

public class IndexModel : PageModel {

    [BindProperty(SupportsGet=true)]
    public string SearchTerms {get; set;}

    [BindProperty(SupportsGet=true)]
    public string[] MPAARatings {get; set;}

    [BindProperty(SupportsGet=true)]
    public string[] Genres {get; set;}

    [BindProperty(SupportsGet=true)]
    public double? IMDBMin {get; set;}

    [BindProperty(SupportsGet=true)]
    public double? IMDBMax {get; set;}

    /// <summary>
    /// Gets the search results for display on the page
    /// </summary>
    public void OnGet() {
        Movies = MovieDatabase.Search(SearchTerms);
        Movies = MovieDatabase.FilterByMPAARating(Movies, MPAARatings);
        Movies = MovieDatabase.FilterByGenre(Movies, Genres);
        Movies = MovieDatabase.FilterByIMDBRating(Movies, IMDBMin, IMDBMax);
    }
}

Note that with this approach, the incoming data is directly bound to the properties, so we don’t need to do any special assignments within our OnGet() method. Also, note that we have to use SupportsGet=true in order for this binding to occur on GET requests (by default, model binding only happens with POST requests).

Note

You only need to do one binding approach per property in a PageModel. I.e. you can just use the property decorator:

public class SomePageModel : PageModel
{
    [BindProperty(SupportsGet=true)]
    public float SomeProperty { get; set; }

    public void OnGet() {
        DoSomething(SomeProperty);
    }
}

or you might use parameter binding:

public class SomePageModel : PageModel
{
    public void OnGet(float SomeProperty) {
        DoSomething(SomeProperty);
    }
}

or you can parse it from the request:

public class SomePageModel : PageModel
{
    public void OnGet() {
        var someProperty = float.Parse(Request.Query["SomeProperty"]);
        DoSomething(someProperty);
    }
}

These are all different means of accessing the same data from the incoming request.

Now all we need to do is implement the actual filter.

Implementing the IMDB Rating Filter

We’ll define the new filter in our MovieDatabase class as another static method:

    /// <summary>
    /// Filters the provided collection of movies
    /// to those with IMDB ratings falling within
    /// the specified range
    /// </summary>
    /// <param name="movies">The collection of movies to filter</param>
    /// <param name="min">The minimum range value</param>
    /// <param name="max">The maximum range value</param>
    /// <returns>The filtered movie collection</returns>
    public static IEnumerable<Movie> FilterByIMDBRating(IEnumerable<Movie> movies, double? min, double? max)
    {
        // TODO: Filter movies
    }

Notice that here too we use the nullable double value. So our first step is probably to do a null check:

    if (min == null && max == null) return movies;

But what if only one is null? Should we filter for that part of the range? It wouldn’t be hard to do:

    var results = new List<Movie>();

    // only a maximum specified
    if(min == null)
    {
        foreach(Movie movie in movies)
        {
            if (movie.IMDBRating <= max) results.Add(movie);
        }
        return results;
    }

And the minimum would mirror that:

    // only a minimum specified
    if(max == null)
    {
        foreach(Movie movie in movies)
        {
            if (movie.IMDBRating >= min) results.Add(movie);
        }
        return results;
    }

Finally, we could handle the case where we have both a min and max value to our range:

    // Both minimum and maximum specified
    foreach(Movie movie in movies)
    {
        if(movie.IMDBRating >= min && movie.IMDBRating <= max)
        {
            results.Add(movie);
        }
    }
    return results;

Notice too, that in each of these cases we’re treating the range as inclusive (including the specified minimum and maximum). This is the behavior most casual internet users will expect. If the database and user expectations are different for your audience, you’d want your code to match that expectation.

Now we can filter by IMDB rating:

Filtering By IMDB Filtering By IMDB

Finishing Up

Since we’re displaying the Rotten Tomatoes rating, we should probably also have a filter for it. This will work almost exactly like the IMDB rating - but with the range from 0 to 100. I’ll leave this as an exercise for the reader.

Numerical Filters

Let’s tackle one of the critics ratings next. While we could create categories and use checkboxes, this doesn’t capture the incremental values (i.e. 4.3), and it would be a lot of checkboxes for Rotten Tomatoes ratings! Instead, we’ll use a numerical filter, which limits our possible results to a range - between a minimum and maximum value.

Moreover, let’s clean up our Index page, as it is getting difficult to determine what filter(s) go together, and and are adding more.

Refactoring the Index Page

Let’s move the filters to a column on the left, leave the search bar above, and show our results on the right. This will require refactoring our Index.cshtml file:

    <form id="movie-database">

        <div id="search">
            <input type="text" name="SearchTerms" value="@Model.SearchTerms" />
            <input type="submit" value="Search">
        </div>

        <div id="filters">

            <h4>MPAA Rating</h4>
            @foreach (string rating in MovieDatabase.MPAARatings)
            {
                <label>
                    <input type="checkbox" name="MPAARatings" value="@rating" checked="@Model.MPAARatings.Contains(rating)" />
                    @rating
                </label>
            }

            <h4>Genre</h4>
            @foreach (string genre in MovieDatabase.Genres)
            {
                <label>
                    <input type="checkbox" name="Genres" value="@genre" />
                    @genre
                </label>
            }

            <h4>IMDB Rating</h4>
            <div>
                Between
                <input name="IMDBMin" type="number" min="0" max="10" step="0.1" placeholder="min"/>
                and
                <input name="IMDBMax" type="number" min="0" max="10" step="0.1" placeholder="max"/>
            </div>

        </div>

        <div id="results">
            <h1>Movie Results</h1>

            <ul class="movie-list">
                @foreach (Movie movie in @Model.Movies)
                {
                    <li>
                        <div class="details">
                            <h3 class="title">@movie.Title</h3>
                            <div class="mpaa">@movie.MPAARating</div>
                            <div class="genre">@movie.MajorGenre</div>
                        </div>
                        <div class="ratings">
                            @if (movie.IMDBRating != null)
                            {
                                <div class="imdb">
                                    @movie.IMDBRating
                                </div>
                            }
                            @if (movie.RottenTomatoesRating != null)
                            {
                                <div class="rotten-tomatoes">
                                    @movie.RottenTomatoesRating
                                </div>
                            }
                        </div>
                    </li>
                }
            </ul>
        </div>

    </form>

Most of this is simply moving elements around the page, but note that we are using inputs of type=number to represent our range of IMDB values. We can specify a minimum and maximum for this range, as well as an allowable increment. Also, we use the placeholder attribute to put text into the input until a value is added.

Adding More Styles

Now we’ll need to add some rules to our wwwroot/css/styles.css. First, we’ll use a grid for the layout of the form:

form#movie-database {
    display: grid;
    grid-template-columns: 1fr 3fr;
    grid-template-rows: auto auto;
}

The right column will be three times as big as the left.

We can make our search bar span both columns with grid-column-start and grid-column-end:

#search {
    grid-column-start: 1;
    grid-column-end: 3;
    text-align: center;
}

Notice too that for CSS, we start counting at 1, not 0. The filters and the results will fall in the next row automatically, each taking up their own respective grid cell. You can read more about the grid layout in A Complete Guide to Grid.

Let’s go ahead and use flexbox to lay out our filters in a column:

#filters {
    display: flex;
    flex-direction: column;
}

And make our number inputs bigger:

#filters input[type=number] {
    width: 4rem;
}

Notice the use of square brackets in our CSS Selector to only apply to inputs with type number.

Also, let’s add a margin above and remove most of the margin below our <h4> elements:

#filters h4 {
    margin-bottom: 0.2rem;
    margin-top: 2rem;
}

The resulting page looks much cleaner:

The Styled Page The Styled Page

Capturing the Filter Values

Now we need to get the filter values from our GET request query string. We could do this like we’ve been doing before, with:

    Request.Query["IMDBMin"];

But the returned value would be a string, so we’d need to parse it:

    IMDBMin = double.Parse(Request.Query["IMDBMin"]);

If the query was null, then this would evaluate to NaN, which we wouldn’t want to set our <input> to…

Instead, we’ll look at some options built into the PageModel.

Parameter Binding

The first of these options is Parameter Binding. In this approach, we define parameters to our OnGet() method to be parsed out of the request automatically, i.e.:

    /// <summary>
    /// Gets the search results for display on the page
    /// </summary>
    public void OnGet(string SearchTerms, string[] MPAARatings, string[] Genres, double? IMDBMin, double? IMDBMax) {
        this.SearchTerms = SearchTerms;
        this.MPAARatings = MPAARatings;
        this.Genres = Genres;
        this.IMDBMin = IMDBMin;
        this.IMDBMax = IMDBMax;
        Movies = MovieDatabase.Search(SearchTerms);
        Movies = MovieDatabase.FilterByMPAARating(Movies, MPAARatings);
        Movies = MovieDatabase.FilterByGenre(Movies, Genres);
        Movies = MovieDatabase.FilterByIMDBRating(Movies, IMDBMin, IMDBMax);
    }

The benefit of this approach is that as long as C# knows a conversion into the type we specify, the conversion is done automatically. Note that the parameter name matches the name property of the corresponding <input> - this must be the case for the Razor Page to bind the parameter to the corresponding input value.

Note that we still need to assign these parameter values to the corresponding properties of our PageModel. If we don’t, then those properties will all be null, and the <inputs> rendered on our page will always be blank.

Model Binding

A second option is to use Model Binding. Model binding also automatically converts incoming form data, but in this case it binds directly to the properties of our PageModel. We indicate this form of binding with a [BindProperty] attribute, i.e.:

public class IndexModel : PageModel {

    [BindProperty(SupportsGet=true)]
    public string SearchTerms {get; set;}

    [BindProperty(SupportsGet=true)]
    public string[] MPAARatings {get; set;}

    [BindProperty(SupportsGet=true)]
    public string[] Genres {get; set;}

    [BindProperty(SupportsGet=true)]
    public double? IMDBMin {get; set;}

    [BindProperty(SupportsGet=true)]
    public double? IMDBMax {get; set;}

    /// <summary>
    /// Gets the search results for display on the page
    /// </summary>
    public void OnGet() {
        Movies = MovieDatabase.Search(SearchTerms);
        Movies = MovieDatabase.FilterByMPAARating(Movies, MPAARatings);
        Movies = MovieDatabase.FilterByGenre(Movies, Genres);
        Movies = MovieDatabase.FilterByIMDBRating(Movies, IMDBMin, IMDBMax);
    }
}

Note that with this approach, the incoming data is directly bound to the properties, so we don’t need to do any special assignments within our OnGet() method. Also, note that we have to use SupportsGet=true in order for this binding to occur on GET requests (by default, model binding only happens with POST requests).

Note

You only need to do one binding approach per property in a PageModel. I.e. you can just use the property decorator:

public class SomePageModel : PageModel
{
    [BindProperty(SupportsGet=true)]
    public float SomeProperty { get; set; }

    public void OnGet() {
        DoSomething(SomeProperty);
    }
}

or you might use parameter binding:

public class SomePageModel : PageModel
{
    public void OnGet(float SomeProperty) {
        DoSomething(SomeProperty);
    }
}

or you can parse it from the request:

public class SomePageModel : PageModel
{
    public void OnGet() {
        var someProperty = float.Parse(Request.Query["SomeProperty"]);
        DoSomething(someProperty);
    }
}

These are all different means of accessing the same data from the incoming request.

Now all we need to do is implement the actual filter.

Implementing the IMDB Rating Filter

We’ll define the new filter in our MovieDatabase class as another static method:

    /// <summary>
    /// Filters the provided collection of movies
    /// to those with IMDB ratings falling within
    /// the specified range
    /// </summary>
    /// <param name="movies">The collection of movies to filter</param>
    /// <param name="min">The minimum range value</param>
    /// <param name="max">The maximum range value</param>
    /// <returns>The filtered movie collection</returns>
    public static IEnumerable<Movie> FilterByIMDBRating(IEnumerable<Movie> movies, double? min, double? max)
    {
        // TODO: Filter movies
    }

Notice that here too we use the nullable double value. So our first step is probably to do a null check:

    if (min == null && max == null) return movies;

But what if only one is null? Should we filter for that part of the range? It wouldn’t be hard to do:

    var results = new List<Movie>();

    // only a maximum specified
    if(min == null)
    {
        foreach(Movie movie in movies)
        {
            if (movie.IMDBRating <= max) results.Add(movie);
        }
        return results;
    }

And the minimum would mirror that:

    // only a minimum specified
    if(max == null)
    {
        foreach(Movie movie in movies)
        {
            if (movie.IMDBRating >= min) results.Add(movie);
        }
        return results;
    }

Finally, we could handle the case where we have both a min and max value to our range:

    // Both minimum and maximum specified
    foreach(Movie movie in movies)
    {
        if(movie.IMDBRating >= min && movie.IMDBRating <= max)
        {
            results.Add(movie);
        }
    }
    return results;

Notice too, that in each of these cases we’re treating the range as inclusive (including the specified minimum and maximum). This is the behavior most casual internet users will expect. If the database and user expectations are different for your audience, you’d want your code to match that expectation.

Now we can filter by IMDB rating:

Filtering By IMDB Filtering By IMDB

Finishing Up

Since we’re displaying the Rotten Tomatoes rating, we should probably also have a filter for it. This will work almost exactly like the IMDB rating - but with the range from 0 to 100. I’ll leave this as an exercise for the reader.

Numerical Filters

Let’s tackle one of the critics ratings next. While we could create categories and use checkboxes, this doesn’t capture the incremental values (i.e. 4.3), and it would be a lot of checkboxes for Rotten Tomatoes ratings! Instead, we’ll use a numerical filter, which limits our possible results to a range - between a minimum and maximum value.

Moreover, let’s clean up our Index page, as it is getting difficult to determine what filter(s) go together, and and are adding more.

Refactoring the Index Page

Let’s move the filters to a column on the left, leave the search bar above, and show our results on the right. This will require refactoring our Index.cshtml file:

    <form id="movie-database">

        <div id="search">
            <input type="text" name="SearchTerms" value="@Model.SearchTerms" />
            <input type="submit" value="Search">
        </div>

        <div id="filters">

            <h4>MPAA Rating</h4>
            @foreach (string rating in MovieDatabase.MPAARatings)
            {
                <label>
                    <input type="checkbox" name="MPAARatings" value="@rating" checked="@Model.MPAARatings.Contains(rating)" />
                    @rating
                </label>
            }

            <h4>Genre</h4>
            @foreach (string genre in MovieDatabase.Genres)
            {
                <label>
                    <input type="checkbox" name="Genres" value="@genre" />
                    @genre
                </label>
            }

            <h4>IMDB Rating</h4>
            <div>
                Between
                <input name="IMDBMin" type="number" min="0" max="10" step="0.1" placeholder="min"/>
                and
                <input name="IMDBMax" type="number" min="0" max="10" step="0.1" placeholder="max"/>
            </div>

        </div>

        <div id="results">
            <h1>Movie Results</h1>

            <ul class="movie-list">
                @foreach (Movie movie in @Model.Movies)
                {
                    <li>
                        <div class="details">
                            <h3 class="title">@movie.Title</h3>
                            <div class="mpaa">@movie.MPAARating</div>
                            <div class="genre">@movie.MajorGenre</div>
                        </div>
                        <div class="ratings">
                            @if (movie.IMDBRating != null)
                            {
                                <div class="imdb">
                                    @movie.IMDBRating
                                </div>
                            }
                            @if (movie.RottenTomatoesRating != null)
                            {
                                <div class="rotten-tomatoes">
                                    @movie.RottenTomatoesRating
                                </div>
                            }
                        </div>
                    </li>
                }
            </ul>
        </div>

    </form>

Most of this is simply moving elements around the page, but note that we are using inputs of type=number to represent our range of IMDB values. We can specify a minimum and maximum for this range, as well as an allowable increment. Also, we use the placeholder attribute to put text into the input until a value is added.

Adding More Styles

Now we’ll need to add some rules to our wwwroot/css/styles.css. First, we’ll use a grid for the layout of the form:

form#movie-database {
    display: grid;
    grid-template-columns: 1fr 3fr;
    grid-template-rows: auto auto;
}

The right column will be three times as big as the left.

We can make our search bar span both columns with grid-column-start and grid-column-end:

#search {
    grid-column-start: 1;
    grid-column-end: 3;
    text-align: center;
}

Notice too that for CSS, we start counting at 1, not 0. The filters and the results will fall in the next row automatically, each taking up their own respective grid cell. You can read more about the grid layout in A Complete Guide to Grid.

Let’s go ahead and use flexbox to lay out our filters in a column:

#filters {
    display: flex;
    flex-direction: column;
}

And make our number inputs bigger:

#filters input[type=number] {
    width: 4rem;
}

Notice the use of square brackets in our CSS Selector to only apply to inputs with type number.

Also, let’s add a margin above and remove most of the margin below our <h4> elements:

#filters h4 {
    margin-bottom: 0.2rem;
    margin-top: 2rem;
}

The resulting page looks much cleaner:

The Styled Page The Styled Page

Capturing the Filter Values

Now we need to get the filter values from our GET request query string. We could do this like we’ve been doing before, with:

    Request.Query["IMDBMin"];

But the returned value would be a string, so we’d need to parse it:

    IMDBMin = double.Parse(Request.Query["IMDBMin"]);

If the query was null, then this would evaluate to NaN, which we wouldn’t want to set our <input> to…

Instead, we’ll look at some options built into the PageModel.

Parameter Binding

The first of these options is Parameter Binding. In this approach, we define parameters to our OnGet() method to be parsed out of the request automatically, i.e.:

    /// <summary>
    /// Gets the search results for display on the page
    /// </summary>
    public void OnGet(string SearchTerms, string[] MPAARatings, string[] Genres, double? IMDBMin, double? IMDBMax) {
        this.SearchTerms = SearchTerms;
        this.MPAARatings = MPAARatings;
        this.Genres = Genres;
        this.IMDBMin = IMDBMin;
        this.IMDBMax = IMDBMax;
        Movies = MovieDatabase.Search(SearchTerms);
        Movies = MovieDatabase.FilterByMPAARating(Movies, MPAARatings);
        Movies = MovieDatabase.FilterByGenre(Movies, Genres);
        Movies = MovieDatabase.FilterByIMDBRating(Movies, IMDBMin, IMDBMax);
    }

The benefit of this approach is that as long as C# knows a conversion into the type we specify, the conversion is done automatically. Note that the parameter name matches the name property of the corresponding <input> - this must be the case for the Razor Page to bind the parameter to the corresponding input value.

Note that we still need to assign these parameter values to the corresponding properties of our PageModel. If we don’t, then those properties will all be null, and the <inputs> rendered on our page will always be blank.

Model Binding

A second option is to use Model Binding. Model binding also automatically converts incoming form data, but in this case it binds directly to the properties of our PageModel. We indicate this form of binding with a [BindProperty] attribute, i.e.:

public class IndexModel : PageModel {

    [BindProperty(SupportsGet=true)]
    public string SearchTerms {get; set;}

    [BindProperty(SupportsGet=true)]
    public string[] MPAARatings {get; set;}

    [BindProperty(SupportsGet=true)]
    public string[] Genres {get; set;}

    [BindProperty(SupportsGet=true)]
    public double? IMDBMin {get; set;}

    [BindProperty(SupportsGet=true)]
    public double? IMDBMax {get; set;}

    /// <summary>
    /// Gets the search results for display on the page
    /// </summary>
    public void OnGet() {
        Movies = MovieDatabase.Search(SearchTerms);
        Movies = MovieDatabase.FilterByMPAARating(Movies, MPAARatings);
        Movies = MovieDatabase.FilterByGenre(Movies, Genres);
        Movies = MovieDatabase.FilterByIMDBRating(Movies, IMDBMin, IMDBMax);
    }
}

Note that with this approach, the incoming data is directly bound to the properties, so we don’t need to do any special assignments within our OnGet() method. Also, note that we have to use SupportsGet=true in order for this binding to occur on GET requests (by default, model binding only happens with POST requests).

Note

You only need to do one binding approach per property in a PageModel. I.e. you can just use the property decorator:

public class SomePageModel : PageModel
{
    [BindProperty(SupportsGet=true)]
    public float SomeProperty { get; set; }

    public void OnGet() {
        DoSomething(SomeProperty);
    }
}

or you might use parameter binding:

public class SomePageModel : PageModel
{
    public void OnGet(float SomeProperty) {
        DoSomething(SomeProperty);
    }
}

or you can parse it from the request:

public class SomePageModel : PageModel
{
    public void OnGet() {
        var someProperty = float.Parse(Request.Query["SomeProperty"]);
        DoSomething(someProperty);
    }
}

These are all different means of accessing the same data from the incoming request.

Now all we need to do is implement the actual filter.

Implementing the IMDB Rating Filter

We’ll define the new filter in our MovieDatabase class as another static method:

    /// <summary>
    /// Filters the provided collection of movies
    /// to those with IMDB ratings falling within
    /// the specified range
    /// </summary>
    /// <param name="movies">The collection of movies to filter</param>
    /// <param name="min">The minimum range value</param>
    /// <param name="max">The maximum range value</param>
    /// <returns>The filtered movie collection</returns>
    public static IEnumerable<Movie> FilterByIMDBRating(IEnumerable<Movie> movies, double? min, double? max)
    {
        // TODO: Filter movies
    }

Notice that here too we use the nullable double value. So our first step is probably to do a null check:

    if (min == null && max == null) return movies;

But what if only one is null? Should we filter for that part of the range? It wouldn’t be hard to do:

    var results = new List<Movie>();

    // only a maximum specified
    if(min == null)
    {
        foreach(Movie movie in movies)
        {
            if (movie.IMDBRating <= max) results.Add(movie);
        }
        return results;
    }

And the minimum would mirror that:

    // only a minimum specified
    if(max == null)
    {
        foreach(Movie movie in movies)
        {
            if (movie.IMDBRating >= min) results.Add(movie);
        }
        return results;
    }

Finally, we could handle the case where we have both a min and max value to our range:

    // Both minimum and maximum specified
    foreach(Movie movie in movies)
    {
        if(movie.IMDBRating >= min && movie.IMDBRating <= max)
        {
            results.Add(movie);
        }
    }
    return results;

Notice too, that in each of these cases we’re treating the range as inclusive (including the specified minimum and maximum). This is the behavior most casual internet users will expect. If the database and user expectations are different for your audience, you’d want your code to match that expectation.

Now we can filter by IMDB rating:

Filtering By IMDB Filtering By IMDB

Finishing Up

Since we’re displaying the Rotten Tomatoes rating, we should probably also have a filter for it. This will work almost exactly like the IMDB rating - but with the range from 0 to 100. I’ll leave this as an exercise for the reader.

Numerical Filters

Let’s tackle one of the critics ratings next. While we could create categories and use checkboxes, this doesn’t capture the incremental values (i.e. 4.3), and it would be a lot of checkboxes for Rotten Tomatoes ratings! Instead, we’ll use a numerical filter, which limits our possible results to a range - between a minimum and maximum value.

Moreover, let’s clean up our Index page, as it is getting difficult to determine what filter(s) go together, and and are adding more.

Refactoring the Index Page

Let’s move the filters to a column on the left, leave the search bar above, and show our results on the right. This will require refactoring our Index.cshtml file:

    <form id="movie-database">

        <div id="search">
            <input type="text" name="SearchTerms" value="@Model.SearchTerms" />
            <input type="submit" value="Search">
        </div>

        <div id="filters">

            <h4>MPAA Rating</h4>
            @foreach (string rating in MovieDatabase.MPAARatings)
            {
                <label>
                    <input type="checkbox" name="MPAARatings" value="@rating" checked="@Model.MPAARatings.Contains(rating)" />
                    @rating
                </label>
            }

            <h4>Genre</h4>
            @foreach (string genre in MovieDatabase.Genres)
            {
                <label>
                    <input type="checkbox" name="Genres" value="@genre" checked="@Model.Genres.Contains(genre)" />
                    @genre
                </label>
            }

            <h4>IMDB Rating</h4>
            <div>
                <input name="IMDBMin" type="number" min="0" max="10" step="0.1" placeholder="min"/>
                to
                <input name="IMDBMax" type="number" min="0" max="10" step="0.1" placeholder="max"/>
            </div>

        </div>

        <div id="results">
            <h1>Movie Results</h1>

            <ul class="movie-list">
                @foreach (Movie movie in @Model.Movies)
                {
                    <li>
                        <div class="details">
                            <h3 class="title">@movie.Title</h3>
                            <div class="mpaa">@movie.MPAARating</div>
                            <div class="genre">@movie.MajorGenre</div>
                        </div>
                        <div class="ratings">
                            @if (movie.IMDBRating != null)
                            {
                                <div class="imdb">
                                    @movie.IMDBRating
                                </div>
                            }
                            @if (movie.RottenTomatoesRating != null)
                            {
                                <div class="rotten-tomatoes">
                                    @movie.RottenTomatoesRating
                                </div>
                            }
                        </div>
                    </li>
                }
            </ul>
        </div>

    </form>

Most of this is simply moving elements around the page, but note that we are using inputs of type=number to represent our range of IMDB values. We can specify a minimum and maximum for this range, as well as an allowable increment. Also, we use the placeholder attribute to put text into the input until a value is added.

Adding More Styles

Now we’ll need to add some rules to our wwwroot/css/styles.css. First, we’ll use a grid for the layout of the form:

form#movie-database {
    display: grid;
    grid-template-columns: 1fr 3fr;
    grid-template-rows: auto auto;
}

The right column will be three times as big as the left.

We can make our search bar span both columns with grid-column-start and grid-column-end:

#search {
    grid-column-start: 1;
    grid-column-end: 3;
    text-align: center;
}

Notice too that for CSS, we start counting at 1, not 0. The filters and the results will fall in the next row automatically, each taking up their own respective grid cell. You can read more about the grid layout in A Complete Guide to Grid.

Let’s go ahead and use flexbox to lay out our filters in a column:

#filters {
    display: flex;
    flex-direction: column;
}

And make our number inputs bigger:

#filters input[type=number] {
    width: 4rem;
}

Notice the use of square brackets in our CSS Selector to only apply to inputs with type number.

Also, let’s add a margin above and remove most of the margin below our <h4> elements:

#filters h4 {
    margin-bottom: 0.2rem;
    margin-top: 2rem;
}

The resulting page looks much cleaner:

The Styled Page The Styled Page

Capturing the Filter Values

Now we need to get the filter values from our GET request query string. We could do this like we’ve been doing before, with:

    Request.Query["IMDBMin"];

But the returned value would be a string, so we’d need to parse it:

    IMDBMin = double.Parse(Request.Query["IMDBMin"]);

If the query was null, then this would evaluate to NaN, which we wouldn’t want to set our <input> to…

Instead, we’ll look at some options built into the PageModel.

Parameter Binding

The first of these options is Parameter Binding. In this approach, we define parameters to our OnGet() method to be parsed out of the request automatically, i.e.:

    /// <summary>
    /// Gets the search results for display on the page
    /// </summary>
    public void OnGet(string SearchTerms, string[] MPAARatings, string[] Genres, double? IMDBMin, double? IMDBMax) {
        this.SearchTerms = SearchTerms;
        this.MPAARatings = MPAARatings;
        this.Genres = Genres;
        this.IMDBMin = IMDBMin;
        this.IMDBMax = IMDBMax;
        Movies = MovieDatabase.Search(SearchTerms);
        Movies = MovieDatabase.FilterByMPAARating(Movies, MPAARatings);
        Movies = MovieDatabase.FilterByGenre(Movies, Genres);
        Movies = MovieDatabase.FilterByIMDBRating(Movies, IMDBMin, IMDBMax);
    }

The benefit of this approach is that as long as C# knows a conversion into the type we specify, the conversion is done automatically. Note that the parameter name matches the name property of the corresponding <input> - this must be the case for the Razor Page to bind the parameter to the corresponding input value.

Note that we still need to assign these parameter values to the corresponding properties of our PageModel. If we don’t, then those properties will all be null, and the <inputs> rendered on our page will always be blank.

Model Binding

A second option is to use Model Binding. Model binding also automatically converts incoming form data, but in this case it binds directly to the properties of our PageModel. We indicate this form of binding with a [BindProperty] attribute, i.e.:

public class IndexModel : PageModel {

    [BindProperty(SupportsGet=true)]
    public string SearchTerms {get; set;}

    [BindProperty(SupportsGet=true)]
    public string[] MPAARatings {get; set;}

    [BindProperty(SupportsGet=true)]
    public string[] Genres {get; set;}

    [BindProperty(SupportsGet=true)]
    public double? IMDBMin {get; set;}

    [BindProperty(SupportsGet=true)]
    public double? IMDBMax {get; set;}

    /// <summary>
    /// Gets the search results for display on the page
    /// </summary>
    public void OnGet() {
        Movies = MovieDatabase.Search(SearchTerms);
        Movies = MovieDatabase.FilterByMPAARating(Movies, MPAARatings);
        Movies = MovieDatabase.FilterByGenre(Movies, Genres);
        Movies = MovieDatabase.FilterByIMDBRating(Movies, IMDBMin, IMDBMax);
    }
}

Note that with this approach, the incoming data is directly bound to the properties, so we don’t need to do any special assignments within our OnGet() method. Also, note that we have to use SupportsGet=true in order for this binding to occur on GET requests (by default, model binding only happens with POST requests).

Note

You only need to do one binding approach per property in a PageModel. I.e. you can just use the property decorator:

public class SomePageModel : PageModel
{
    [BindProperty(SupportsGet=true)]
    public float SomeProperty { get; set; }

    public void OnGet() {
        DoSomething(SomeProperty);
    }
}

or you might use parameter binding:

public class SomePageModel : PageModel
{
    public void OnGet(float SomeProperty) {
        DoSomething(SomeProperty);
    }
}

or you can parse it from the request:

public class SomePageModel : PageModel
{
    public void OnGet() {
        var someProperty = float.Parse(Request.Query["SomeProperty"]);
        DoSomething(someProperty);
    }
}

These are all different means of accessing the same data from the incoming request.

Now all we need to do is implement the actual filter.

Implementing the IMDB Rating Filter

We’ll define the new filter in our MovieDatabase class as another static method:

    /// <summary>
    /// Filters the provided collection of movies
    /// to those with IMDB ratings falling within
    /// the specified range
    /// </summary>
    /// <param name="movies">The collection of movies to filter</param>
    /// <param name="min">The minimum range value</param>
    /// <param name="max">The maximum range value</param>
    /// <returns>The filtered movie collection</returns>
    public static IEnumerable<Movie> FilterByIMDBRating(IEnumerable<Movie> movies, double? min, double? max)
    {
        // TODO: Filter movies
    }

Notice that here too we use the nullable double value. So our first step is probably to do a null check:

    if (min == null && max == null) return movies;

But what if only one is null? Should we filter for that part of the range? It wouldn’t be hard to do:

    var results = new List<Movie>();

    // only a maximum specified
    if(min == null)
    {
        foreach(Movie movie in movies)
        {
            if (movie.IMDBRating <= max) results.Add(movie);
        }
        return results;
    }

And the minimum would mirror that:

    // only a minimum specified
    if(max == null)
    {
        foreach(Movie movie in movies)
        {
            if (movie.IMDBRating >= min) results.Add(movie);
        }
        return results;
    }

Finally, we could handle the case where we have both a min and max value to our range:

    // Both minimum and maximum specified
    foreach(Movie movie in movies)
    {
        if(movie.IMDBRating >= min && movie.IMDBRating <= max)
        {
            results.Add(movie);
        }
    }
    return results;

Notice too, that in each of these cases we’re treating the range as inclusive (including the specified minimum and maximum). This is the behavior most casual internet users will expect. If the database and user expectations are different for your audience, you’d want your code to match that expectation.

Now we can filter by IMDB rating:

Filtering By IMDB Filtering By IMDB

Finishing Up

Since we’re displaying the Rotten Tomatoes rating, we should probably also have a filter for it. This will work almost exactly like the IMDB rating - but with the range from 0 to 100. I’ll leave this as an exercise for the reader.

Numerical Filters

Let’s tackle one of the critics ratings next. While we could create categories and use checkboxes, this doesn’t capture the incremental values (i.e. 4.3), and it would be a lot of checkboxes for Rotten Tomatoes ratings! Instead, we’ll use a numerical filter, which limits our possible results to a range - between a minimum and maximum value.

Moreover, let’s clean up our Index page, as it is getting difficult to determine what filter(s) go together, and are adding more.

Refactoring the Index Page

Let’s move the filters to a column on the left, leave the search bar above, and show our results on the right. This will require refactoring our Index.cshtml file:

    <form id="movie-database">

        <div id="search">
            <input type="text" name="SearchTerms" value="@Model.SearchTerms" />
            <input type="submit" value="Search">
        </div>

        <div id="filters">

            <h4>MPAA Rating</h4>
            @foreach (string rating in MovieDatabase.MPAARatings)
            {
                <label>
                    <input type="checkbox" name="MPAARatings" value="@rating" checked="@Model.MPAARatings.Contains(rating)" />
                    @rating
                </label>
            }

            <h4>Genre</h4>
            @foreach (string genre in MovieDatabase.Genres)
            {
                <label>
                    <input type="checkbox" name="Genres" value="@genre" checked="@Model.Genres.Contains(genre)" />
                    @genre
                </label>
            }

            <h4>IMDB Rating</h4>
            <div>
                <input name="IMDBMin" type="number" min="0" max="10" step="0.1" placeholder="min"/>
                to
                <input name="IMDBMax" type="number" min="0" max="10" step="0.1" placeholder="max"/>
            </div>

        </div>

        <div id="results">
            <h1>Movie Results</h1>

            <ul class="movie-list">
                @foreach (Movie movie in @Model.Movies)
                {
                    <li>
                        <div class="details">
                            <h3 class="title">@movie.Title</h3>
                            <div class="mpaa">@movie.MPAARating</div>
                            <div class="genre">@movie.MajorGenre</div>
                        </div>
                        <div class="ratings">
                            @if (movie.IMDBRating != null)
                            {
                                <div class="imdb">
                                    @movie.IMDBRating
                                </div>
                            }
                            @if (movie.RottenTomatoesRating != null)
                            {
                                <div class="rotten-tomatoes">
                                    @movie.RottenTomatoesRating
                                </div>
                            }
                        </div>
                    </li>
                }
            </ul>
        </div>

    </form>

Most of this is simply moving elements around the page, but note that we are using inputs of type=number to represent our range of IMDB values. We can specify a minimum and maximum for this range, as well as an allowable increment. Also, we use the placeholder attribute to put text into the input until a value is added.

Adding More Styles

Now we’ll need to add some rules to our wwwroot/css/site.css. First, we’ll use a grid for the layout of the form:

form#movie-database {
    display: grid;
    grid-template-columns: 1fr 3fr;
    grid-template-rows: auto auto;
}

The right column will be three times as big as the left.

We can make our search bar span both columns with grid-column-start and grid-column-end:

#search {
    grid-column-start: 1;
    grid-column-end: 3;
    text-align: center;
}

Notice too that for CSS, we start counting at 1, not 0. The filters and the results will fall in the next row automatically, each taking up their own respective grid cell. You can read more about the grid layout in A Complete Guide to Grid.

Let’s go ahead and use flexbox to lay out our filters in a column:

#filters {
    display: flex;
    flex-direction: column;
}

And make our number inputs bigger:

#filters input[type=number] {
    width: 4rem;
}

Notice the use of square brackets in our CSS Selector to only apply to inputs with type number.

Also, let’s add a margin above and remove most of the margin below our <h4> elements:

#filters h4 {
    margin-bottom: 0.2rem;
    margin-top: 2rem;
}

The resulting page looks much cleaner:

The Styled Page The Styled Page

Capturing the Filter Values

Now we need to get the filter values from our GET request query string. We could do this like we’ve been doing before, with:

    Request.Query["IMDBMin"];

But the returned value would be a string, so we’d need to parse it:

    IMDBMin = double.Parse(Request.Query["IMDBMin"]);

If the query was null, then this would evaluate to NaN, which we wouldn’t want to set our <input> to…

Instead, we’ll look at some options built into the PageModel.

Parameter Binding

The first of these options is Parameter Binding. In this approach, we define parameters to our OnGet() method to be parsed out of the request automatically, i.e.:

    /// <summary>
    /// Gets the search results for display on the page
    /// </summary>
    public void OnGet(string SearchTerms, string[] MPAARatings, string[] Genres, double? IMDBMin, double? IMDBMax) {
        this.SearchTerms = SearchTerms;
        this.MPAARatings = MPAARatings;
        this.Genres = Genres;
        this.IMDBMin = IMDBMin;
        this.IMDBMax = IMDBMax;
        Movies = MovieDatabase.Search(SearchTerms);
        Movies = MovieDatabase.FilterByMPAARating(Movies, MPAARatings);
        Movies = MovieDatabase.FilterByGenre(Movies, Genres);
        Movies = MovieDatabase.FilterByIMDBRating(Movies, IMDBMin, IMDBMax);
    }

The benefit of this approach is that as long as C# knows a conversion into the type we specify, the conversion is done automatically. Note that the parameter name matches the name property of the corresponding <input> - this must be the case for the Razor Page to bind the parameter to the corresponding input value.

Note that we still need to assign these parameter values to the corresponding properties of our PageModel. If we don’t, then those properties will all be null, and the <inputs> rendered on our page will always be blank.

Model Binding

A second option is to use Model Binding. Model binding also automatically converts incoming form data, but in this case it binds directly to the properties of our PageModel. We indicate this form of binding with a [BindProperty] attribute, i.e.:

public class IndexModel : PageModel {

    [BindProperty(SupportsGet=true)]
    public string SearchTerms {get; set;}

    [BindProperty(SupportsGet=true)]
    public string[] MPAARatings {get; set;}

    [BindProperty(SupportsGet=true)]
    public string[] Genres {get; set;}

    [BindProperty(SupportsGet=true)]
    public double? IMDBMin {get; set;}

    [BindProperty(SupportsGet=true)]
    public double? IMDBMax {get; set;}

    /// <summary>
    /// Gets the search results for display on the page
    /// </summary>
    public void OnGet() {
        Movies = MovieDatabase.Search(SearchTerms);
        Movies = MovieDatabase.FilterByMPAARating(Movies, MPAARatings);
        Movies = MovieDatabase.FilterByGenre(Movies, Genres);
        Movies = MovieDatabase.FilterByIMDBRating(Movies, IMDBMin, IMDBMax);
    }
}

Note that with this approach, the incoming data is directly bound to the properties, so we don’t need to do any special assignments within our OnGet() method. Also, note that we have to use SupportsGet=true in order for this binding to occur on GET requests (by default, model binding only happens with POST requests).

Note

You only need to do one binding approach per property in a PageModel. I.e. you can just use the property decorator:

public class SomePageModel : PageModel
{
    [BindProperty(SupportsGet=true)]
    public float SomeProperty { get; set; }

    public void OnGet() {
        DoSomething(SomeProperty);
    }
}

or you might use parameter binding:

public class SomePageModel : PageModel
{
    public void OnGet(float SomeProperty) {
        DoSomething(SomeProperty);
    }
}

or you can parse it from the request:

public class SomePageModel : PageModel
{
    public void OnGet() {
        var someProperty = float.Parse(Request.Query["SomeProperty"]);
        DoSomething(someProperty);
    }
}

These are all different means of accessing the same data from the incoming request.

Now all we need to do is implement the actual filter.

Implementing the IMDB Rating Filter

We’ll define the new filter in our MovieDatabase class as another static method:

    /// <summary>
    /// Filters the provided collection of movies
    /// to those with IMDB ratings falling within
    /// the specified range
    /// </summary>
    /// <param name="movies">The collection of movies to filter</param>
    /// <param name="min">The minimum range value</param>
    /// <param name="max">The maximum range value</param>
    /// <returns>The filtered movie collection</returns>
    public static IEnumerable<Movie> FilterByIMDBRating(IEnumerable<Movie> movies, double? min, double? max)
    {
        // TODO: Filter movies
    }

Notice that here too we use the nullable double value. So our first step is probably to do a null check:

    if (min == null && max == null) return movies;

But what if only one is null? Should we filter for that part of the range? It wouldn’t be hard to do:

    var results = new List<Movie>();

    // only a maximum specified
    if(min == null)
    {
        foreach(Movie movie in movies)
        {
            if (movie.IMDBRating <= max) results.Add(movie);
        }
        return results;
    }

And the minimum would mirror that:

    // only a minimum specified
    if(max == null)
    {
        foreach(Movie movie in movies)
        {
            if (movie.IMDBRating >= min) results.Add(movie);
        }
        return results;
    }

Finally, we could handle the case where we have both a min and max value to our range:

    // Both minimum and maximum specified
    foreach(Movie movie in movies)
    {
        if(movie.IMDBRating >= min && movie.IMDBRating <= max)
        {
            results.Add(movie);
        }
    }
    return results;

Notice too, that in each of these cases we’re treating the range as inclusive (including the specified minimum and maximum). This is the behavior most casual internet users will expect. If the database and user expectations are different for your audience, you’d want your code to match that expectation.

Now we can filter by IMDB rating:

Filtering By IMDB Filtering By IMDB

Finishing Up

Since we’re displaying the Rotten Tomatoes rating, we should probably also have a filter for it. This will work almost exactly like the IMDB rating - but with the range from 0 to 100. I’ll leave this as an exercise for the reader.

Using LINQ

In our movie website, we’ve been using a custom “database” object, MovieDatabase to provide the movie data and search and filter functionality. In professional practice, we would probably replace this with an external database program. These programs have been developed specifically for persisting and accessing data, and leverage specialized data structures like B+ trees that allow them to do searches and filters much more efficiently.

Some of the most widely adopted database programs are relational databases like MySQL, MsSQL, Postgres, and SQLLite. These predate object-orientation, and accept requests for data in the form of SQL (Structured Query Language). You will learn more about these databases and how to use them in CIS560.

LINQ

Microsoft, inspired by SQL, introduced Language Integrated Query (LINQ) in 2007 to bring the concept of a query language into .NET. LINQ allows you to construct queries in either chained method calls or in a syntax similar to SQL that can operate on collections or relational databases.

For relational databases, LINQ queries are converted internally to SQL queries, and dispatched to the database. But their real power is that they can also be used with any collection that supports either the IEnumerable or IEnumerable<T> interfaces. LINQ can be used to replace the kinds of filter and search functions we wrote for our MovieDatabase. Let’s try it out with our movie website.

LINQ operates through extension methods, so we need to be sure to add the LINQ namespace to our CS files where we are going to employ them. We’ll be adding our LINQ commands to our page model, so sure that is the case with your Pages/Index.cshtml.cs:

using System.Linq;

Adding this using statement will bring in all the extension methods for LINQ, and will also prompt Visual Studio to recognize the query-style syntax.

Searching with LINQ

One of the most useful extension methods in LINQ is Where(). It can be invoked on any IEnumerable and accepts a Func (a delegate type). This delegate takes a single argument - the current item being enumerated over, and its body should return a boolean value - true if the item should be included in the results, and false if it should not.

We can thus use our existing SearchTerms property as the basis for our test: movie => movie.Title != null && movie.Title.Contains(SearchTerms) (note that with lambda expressions, if the result is single expression we can omit the {} and the return is implied). Also note that we incorporate the null check directly into the expression using the boolean and && operator.

We can replace our current search with a Where() call invoking this method:

Movies = MovieDatabase.All.Where(movie => movie.Title != null && movie.Title.Contains(SearchTerms, String));

However if we run the program we will encounter a null exception. On our first GET request, the value of SearchTerms is null, which is not a legal argument for String.Contains().

Making Search Conditional

If the SearchTerms is null, that indicates that the user did not enter a search term - accordingly, we probably don’t want to search. We can wrap our filter in an if test to prevent applying the filter when there is no search term. Thus, we would refactor our filtering expression to:

Movies = MovieDatabase.All;
// Search movie titles for the SearchTerms
if(SearchTerms != null) {
    Movies = Movies.Where(movie => movie.Title != null && movie.Title.Contains(SearchTerms, StringComparison.InvariantCultureIgnoreCase));
}

Now if we run the program, and search for a specific term, we’ll see our results is modified to contain only those movies with the terms in their title!

We can also use the query syntax instead of the extension method syntax, which is similar to SQL:

Movies = MovieDatabase.All;
// Search movie titles for the SearchTerms
if(SerchTerms != null)
{
    Movies = from movie in Movies
        where movie.Title != null && movie.Title.Contains(SearchTerms, StringComparison.InvariantCultureIgnoreCase)
        select movie;
}

This syntax is converted by the C# compiler to use the extension methods as part of the compilation process.

Info

The search approach listed above will only find the exact search terms, i.e. searching for “Clear Present Danger” will not match the film “Clear and Present Danger”. How might you tweak the search approach so that terms would be found individually?

You can use either form for writing your queries, though it is best to stay consistent within a single program.

Filtering with LINQ

Filtering is also accomplished through the Where(). We can add additional tests within our search Where(), but it is more legible to add additional Where calls:

Movies = MovieDatabase.All
    // Search movie titles for the SearchTerms
    if(SearchTerms != null) {
        Movies = Movies.Where(movie => movie.Title != null && movie.Title.Contains(SearchTerms, StringComparison.InvariantCultureIgnoreCase));
    }
    // Filter by MPAA Rating
    if(MPAARatings != null && MPAARatings.Length != 0)
    {
        Movies = Movies.Where(movie =>
            movie.MPAARating != null &&
            MPAARatings.Contains(movie.MPAARating)
            );
    }

The numerical filters are handled the same way - with additional Where clauses. I leave this as an exercise for the reader to complete.

Benefits of LINQ

While LINQ is a bit less code for us to write, there is another big benefit - the actual filtering is only applied when we start iterating through the results. This means that LINQ queries with multiple Where() invocations can combine them into a single iteration.

Consider our movie website example. We have four filters - the search, the MPAA Ratings, the IMDB Ratings, and the Rotten Tomato Rating. In the worst case, each movie in the database would pass each filter, so that our list never got smaller. With the filter functions we wrote in MovieDatabase, we would have to iterate over that full list four times. In terms of complexity, that’s $O(4n)$.

In contrast, because LINQ doesn’t actually run the filters until we start iterating, it can combine all the While tests into a single boolean expression. The result is it only has to iterate through the list once. Hence, its complexity becomes $O(n)$. There is a little additional overhead for holding onto the query information that way, but it’s small compared to the benefit.

Also, we could have done the same kind of optimization ourselves, but it takes a lot of work to set up, and may not be worth it for a single web app. But LINQ provides us that benefit for free.

Deployment

Now that we have a well-functioning web application, we are ready to publish it to a production environment, where eveyrone on the web will have access to it! This exercise walks you through deploying your new application using Microsoft’s Azure Web Services, utilizing the built-in support for this deployment process in Visual Studio.

Subsections of Deployment

Creating an Azure Account

Deploying our Movie Site to Azure

Before you start this exercise, you should:

  1. Set up your Azure account (see the previous section).
  2. Sign into Visual Studio using the same account as you used for Azure

Open your Movie Site project from your previous web data exercises. If you right-click on the project name in the Solution Explorer, you can choose publish from the context menu:

The Publish option in the Solution Explorer The Publish option in the Solution Explorer

The first time you choose this option in a project, it will launch a publish wizard. The first step is to choose your deployment target:

Deployment Targets in the Publish Wizard Deployment Targets in the Publish Wizard

For this exercise, we’ll choose “Azure”, but feel free to explore the other options. Once you’ve chosen Azure and clicked the Next button, you’ll see the second step of the wizard, which seeks more detail on your deployment target:

Azure Deployment Targets in the Publish Wizard Azure Deployment Targets in the Publish Wizard

The options here include deploying to a Windows or Linux infrastructure, or as a Docker image, or as an Azure VM (in which case you would need to set up the OS and environment). Let’s choose the “Linux” option and click Next to reach the next step in the wizard.

Azure App Service in the Publish Wizard Azure App Service in the Publish Wizard

This step lists the subscription(s) available to your account, as well as any Resource Groups and App Services you have created. Most likely the latter two are empty, and your Subscription will be “Azure for Students” (this is the $100 of credit you receive as a K-State student). If you have used Azure previously, you may have additional options. For this project, choose the “Create a new Azure App Service…”

This launches another dialog, for creating a new App Service:

App Service creation dialog App Service creation dialog

You may change the name to any available name (by default it will be your project name followed by a timestamp). When you’re happy with your name, click the Create button. It will take a few minutes to create your App Service, so please be patient. Once it finishes, you’ll be back in the Wizard with your new app service selected:

Finishing the Publish Wizard Finishing the Publish Wizard

Go ahead and click the Finish button. This will close the wizard and return you to Visual Studio, where the Publish configuration screen is now visible:

The Publish Configuration The Publish Configuration

Two elements are important here - the Site URL is the URL where you will be able to visit your published web application (after you publish it), and the Publish button launches the publishing process. Go ahead and click the Publish button. It will take a few minutes, but you’ll see the project is built and deployed to the Azure service. When the process completes, it will open your site in the default web browser (or you can navigate there using the supplied URL).

Your published website will now look something like this:

The published website The published website

While the website is now loaded and functional, it does not look like our debug build - it seems to be missing all of our styles! If we inspect the CSS in the browser development tools, we’ll see that it is loading site.min.css, and this file contains the minified boilerplate CSS from the original site template, but none of the changes we made! If we examine our Layout.csthml template file, we can find the lines where the <link> element for the CSS is defined:

<environment include="Development">
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
</environment>
<environment exclude="Development">
    <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>

Notice the use of the ASP.NET-specific <environment> element? This conditionally includes its children based on the environment the web server is running in. Debug mode corresponds to our “Development” environment, so in debug we are including site.css. However, when we run it in “Production” mode, i.e. in our Azure service, we’re instead using site.min.css. This is intended to be a minified and bundled version of site.css. Minification is a transpiling process where we strip out unnecessary content, i.e. whitespace and comments. Bundling refers to gathering multiple files - i.e. multiple CSS files, and combining them into a single file (this is helpful if we like to keep our CSS rules separated according to what pages they correspond to).

The issue is that the site.min.css file is not currently being updated when we build our project - instead we’re still using the example file that came with the project template. We’ll need to install and configure a NuGet package within the Movie project to enable this extra building step. You can read more about bundling and minification within ASP.NET Core here.

Start by selecting Tools > NuGet Package Manager > Manage Nuget Packages for Solution... from the Tools menu:

Launching the NuGet Package Manager Launching the NuGet Package Manager

This loads the NuGet Package Manager, which allows us to install additional components into our project. By default it starts with the “Installed” tab, which shows which packages are currently installed. Not surprisingly, we have several Microsoft.NETCore packages - these contain the library code for .NETCore and for procesing Razor pages. We also have a third-party library, Newtonsoft.Json, which is used to parse the JSON database file. We’ll want to install a new package, so select the “Browse” tab:

The NuGet Package Manager The NuGet Package Manager

Then type into the search box “BuildBundlerMinifier”. The corresponding package should show up in the results below. This tool will add the bundling and minification step to the build process of Visual Studio. Clicking on the project will open a checklist allowing you to choose which projects you want to install the package into. Check the box next to the Movies project, and then click the Install button.

Installing the BuildBundlerMinifier Package Installing the BuildBundlerMinifier Package

YOu will need to click Ok in the confirmation dialog, and then the package will be installed. Visual Studio will report that the bundleconfig.json files does not exist. This file configures the new package to create the minified bundle file - we’ll need to add it. It should be created at the root of the Move project, and it consists of a simple JSON structure:

[
  {
    "outputFileName": "wwwroot/css/site.min.css",
    "inputFiles": [
      "wwwroot/css/site.css"
    ]
  }
]

The "outputFileName" is the name of the file we want to write to (in this case, _site.min.css), and the "inputFiles" is an array containing file names of all the files we want to bundle (in this case, site.css). If we had additional CSS files in our project, we could also list those here as well, and all of the listed files would be combined into the single output file.

We can bundle and minify Javascript files the same way, by adding a second JSON object defining the "outputFileName" for the minified and bundled Javascript file (which we conventionally give the extension .min.js), and an "inputFiles" array consisting of all the JavaScript files we want to bundle.

With this change, now whenever we build our project, we will also rebuild the minified site.min.css file. Click the Publish button in the Publish screen again, and when the project finishes loading, you’ll now see the CSS rules being applied to your site:

Final Deployed Site Final Deployed Site

That’s all there is to it! You now have a published ASP.NET Core Web Application that can be accessed from any internet-connected browser!

Turn in the URL to your published site. After it is graded, feel free to take down your Azure service.