Using Assert Classes and Methods in Unit Tests

If you have been following my blog posts on unit testing, you have used the Assert class to signify if a unit test is successful or not. The following are my previous posts on unit testing. If you are not familiar with unit testing, go back and read these posts.

You have used the Inconclusive, IsTrue, IsFalse and Fail methods. In this blog post you will learn about some of the other methods you can utilize in the Assert class. In addition you will learn about two additional assert classes you may take advantage of when writing unit tests.

Assert Class

There are many methods in the Assert class. I won’t explain each one, but I will expose you to some so you can get an idea of what is available to use. You should search the MSDN documentation to see the complete list of properties and methods available to you in the Assert class.

Common Parameters to Assert Methods

Most of the methods you may invoke on the Assert class include an overload that allows you to specify a message to display in the test results. An additional overload lets you specify the message using the standard string.Format() tokens and a parameter array of the values to use to replace into the message.

[TestMethod]
public void FileNameDoesExistSimpleMessage() {
FileProcess fp = new FileProcess();
bool fromCall;
fromCall = fp.FileExists(_GoodFileName);
Assert.IsTrue(fromCall, "File Does Not Exist.");
}

When you run the above test, your Test Explorer window will show the hard-coded message after you click on the failed test (Figure 1).


Figure 1: Display hard-coded messages into the results window.

To include some of the data you gathered during the test, use the format items just as you use in the string.Format() method.

[TestMethod]
public void FileNameDoesExistSimpleMessageWithParams() {
FileProcess fp = new FileProcess();
bool fromCall;
fromCall = fp.FileExists(_GoodFileName);
Assert.IsTrue(fromCall, 
"File {0} Does Not Exist.", 
_GoodFileName);
}

When you run this test, you get the message shown in Figure 2.


Figure 2: Display messages with data from the unit test itself.

AreEqual Method

The AreEqual method compares two variables of the same data type to determine if they are equivalent. There are overloads for each of the different data types such as double, single, int, etc. You may add your own custom message, and specify format items as explained earlier. Below are just a few of the data types you can compare.

Assert.AreEqual(int, int);
Assert.AreEqual(bool, bool);
Assert.AreEqual(double, double);

Here is an example using integer data types.

[TestMethod]
public void AreEqualTest() {
int x = 1;
int y = 1;
Assert.AreEqual(x, y);
}

When the two variables you wish to compare are of a string data type, you may also specify a CultureInfo object to handle string comparisons based on the language of the user. You may also specify a boolean value to perform a case-sensitive or case-insensitive comparison of the strings.

AreEqual(string, string)               // case-insensitive
AreEqual(string, string, true)         // case-sensitive
AreEqual(string, string, CultureInfo)  // Use a culture

AreNotEqual Method

To compare two values to see if they are not equal you use the AreNotEqual method. This method, like the AreEqual method, has several overloads you can use based on the different data types. Below is a sample of using the AreNotEqual method.

[TestMethod]
public void AreNotEqualTest() {
int x = 1;
int y = 2;
Assert.AreNotEqual(x, y);
}

AreSame Method

To compare two objects to see if they are the same object. For example, if you write the following test:

[TestMethod]
public void AreSameTest() {
FileProcess x = new FileProcess();
FileProcess y = new FileProcess();
Assert.AreSame(x, y);
}

This test will fail, because the two objects point to two different objects. If you change this test to look like the following, where you assign the variable x to the variable y, then this test will succeed.

[TestMethod]
public void AreSameTest() {
FileProcess x = new FileProcess();
FileProcess y = x;
Assert.AreSame(x, y);
}

AreNotSame Method

To compare two objects to see if they are NOT the same object. In this case, the following test would succeed.

[TestMethod]
public void AreNotSameTest() {
FileProcess x = new FileProcess();
FileProcess y = new FileProcess();
Assert.AreNotSame(x, y);
}

IsInstanceOfType Method

You can use this method to determine if an object returned from a method is of a certain type. For example, you may have a method that returns an interface or a base class. When you call this method from your unit test, you might wish to compare the type of instance that is returned against the type you are expecting.

Look at the class diagram in Figure 3 in which you have a Person class with two properties. Both the Employee and the Supervisor classes inherit from the Person class. The PersonManager class has a method named CreatePerson that returns a Person object. Depending on the parameters you pass to the CreatePerson method, determines the type of object passed back. Either an Employee or a Supervisor object is returned.


Figure 3: A class diagram for our example

You can write a unit test to determine what the type is. In the unit test below, you pass a true value to the CreatePerson method. This value tells CreatePerson to return a Supervisor object. You use the IsInstanceOfType method to compare the variable per against the typeof(Supervisor).

[TestMethod]
public void IsInstanceOfTypeTest() {
PersonManager mgr = new PersonManager();
Person per;
per = mgr.CreatePerson("Paul", "Sheriff", true);
Assert.IsInstanceOfType(per, typeof(Supervisor));
}

IsNotInstanceOfType Method

This method is the same as the IsInstanceOfType, but checks to see if the instance returned is not of a specific type.

IsNull Method

Call this method to compare the return result from a method against null. If the return result is a null, then the test succeeds. In the unit test below, if you pass an empty string as the first name to the CreatePerson method, that method returns a null object.

[TestMethod]
public void IsNullTest() {
PersonManager mgr = new PersonManager();
Person per;
per = mgr.CreatePerson("", "Sheriff", true);
Assert.IsNull(per);
}

IsNotNull Method

This method is the same as the IsNull method, but checks to see if the value returned is not null.

StringAssert

When working with strings, you commonly need to check to see if one string is contained within another, or if one string matches a regular expression, or a string starts or ends with a specific character or other string value. To test these in a unit test, use the StringAssert class with any of the following methods.

  • Contains
  • DoesNotContain
  • Matches
  • DoesNotMatch
  • StartsWith
  • EndsWith

CollectionAssert

Many methods you write in your applications deal with collections of data. This data could be retrieved from a database, an XML file, or simply arrays of objects you create in your code. The CollectAssert class allows you to test a known set of data in a collection against the collection returned from the method you are testing. You do not need to do any looping through the collections, the CollectionAssert class will do all of that for you. Below is a list of the various methods you can use.

  • AllItemsAreInstancesOfType
  • AllItemsAreNotNull
  • AllItemsAreUnique
  • AreEqual
  • AreNotEqual
  • AreEquivalent
  • AreNotEquivalent
  • Contains
  • DoesNotContain
  • IsSubsetOf
  • IsNotSubsetOf

Summary

In this post, you learned more about the different methods in the Assert class. You also learned about two additional classes to help you test strings and collections. All methods in the various Assert classes contain overloads to allow you to add your own custom message to display in the test results. With this many methods available to you, writing your unit tests should go quickly.

Sample Code

You can download the code for this sample at www.pdsa.com/downloads. Choose the category “PDSA Blogs”, then locate the sample Using Assert Classes and Methods in Unit Tests.

Add Attributes to Unit Tests

In my previous blog posts, I introduced you to creating unit tests with Visual Studio. The following is the list of blog posts published thus far.

You have seen a few different attributes such as [TestClass], [TestMethod], and [TestInitialize] used to decorate classes and methods. There are several more attributes that you should be aware of. You may or may not use all the attributes presented in this blog post, but you may have a need for them at some time or another.

DataSource Attribute

The unit test framework in Visual Studio can data-drive your unit tests. This means you can read data from a data store and execute a single test repeatedly using the data read in. A later blog post will discuss how to use data-driven tests, so this attribute is not covered here.

Description Attribute

Add a Description attribute to any unit test method to describe why you wrote a particular unit test. It is recommended that you use good, long, description name for the unit test method name as well as the Description attribute. This attribute is not used by the unit test framework, nor does it show up anywhere within the Test Explorer window.

[Description("Check to see if a file exists.")]
public void FileNameDoesExist() {
}
[Description("Check to see if file does not exist.")]
public void FileNameDoesNotExist() {
}
[Description("Check for a thrown ArgumentNullException.")]
public void 
  FileNameNullOrEmpty_ThrowsArgumentNullException () {
}
[Description("Check for a thrown ArgumentNullException
              using ExpectedException.")]
public void FileNameNullOrEmpty_
   ThrowsArgumentNullException_UsingAttribute () {
}

DeploymentItem Attribute

You may add as many DeploymentItem attributes as you need to specify files and folders to copy to the directory where the unit test runs. The DeploymentItem attribute accepts one or two parameters. The first parameter is a folder or a file name. The path is always relative the build output folder. The second parameter, if passed, is to a new path to copy the data in the first parameter. This second path can be relative to the output folder, or can be an absolute path. It is highly recommended you use a relative folder to allow tests to run seamlessly on different machines.

For our simple example, you do not need to use any DeploymentItem attributes, but below is a sample that would copy two files named DeploymentFile1.txt and DeploymentFile2.txt to the build output folder. It is assumed these two files already exist. If the folder or file does not exist, no error is thrown when running in Visual Studio, the test simply continues. However, if you use the command line utility, a warning is generated.

[DeploymentItem("DeploymentFile1.txt")]
[DeploymentItem("DeploymentFile2.txt")]
public void FileNameDoesExist() {
}

Ignore Attribute

The Ignore attribute is intended to be a temporary attribute you add to skip one or more unit test methods.

[Ignore]
public void FileNameDoesExist() {
}

Owner Attribute

The Owner attribute allows you to specify the name of the developer responsible for the unit test. This helps with the assignment of work items to the developer of a unit test that breaks.

[Owner("PaulS")]	
public void FileNameDoesExist() {
}
[Owner("PaulS")]
public void FileNameDoesNotExist() {
}
[Owner("JohnK")]
public void 
  FileNameNullOrEmpty_ThrowsArgumentNullException () {
}
[Owner("JohnK")]
public void FileNameNullOrEmpty_
   ThrowsArgumentNullException_UsingAttribute () {
}

After the test runs, right mouse click on the test results in the Text Explorer window and select the Group By menu (Figure 1).


Figure 1: Right mouse click to group by traits 

Next select the Traits menu to sort by any attributes you have added (Figure 2).


Figure 2: The Test Explorer window can group results by different attributes you add.

Priority Attribute

The Priority attribute, like the Owner attribute, is considered a “Trait” by the Test Explorer window. It is not used by the unit test framework itself, but can be grouped in the Test Explorer window (Figure 3). When using the VSTest.Console.exe command-line utility, you may filter the tests to run by the Priority attribute.

[Priority(0)]	
public void FileNameDoesExist() {
}
[Priority(1)]
public void FileNameDoesNotExist() {
}
[Priority(1)]
public void 
  FileNameNullOrEmpty_ThrowsArgumentNullException () {
}
[Priority(0)]
public void FileNameNullOrEmpty_
   ThrowsArgumentNullException_UsingAttribute () {
}


Figure 3: Tests can display under multiple traits.

TestCategory Attribute

The TestCategory attribute, like Owner and Priority, is a “Trait” you may group upon in the Test Explorer window (Figure 4). You define any category name you wish and assign that name to one or many tests. After all tests have run, you may filter and sort within the Test Explorer window on the category names.

[TestCategory("NoException")]
public void FileNameDoesExist() {
}
[TestCategory("NoException")]
public void FileNameDoesNotExist() {
}
[TestCategory("Exception")]
public void 
  FileNameNullOrEmpty_ThrowsArgumentNullException () {
}
[TestCategory("Exception")]
public void FileNameNullOrEmpty_
   ThrowsArgumentNullException_UsingAttribute () {
}



Figure 4: Categories are common traits to use besides Owner.

Timeout Attribute

Use the Timeout attribute to specify how long a specific method is allowed to run before the unit test framework kills the test and marks it as a failure. The value you specify is expressed in milliseconds. In the following example, the unit test framework will allow the method to execute for only 5 seconds before it will stop the execution and throw an AssertFailedException.

[Timout(5000)]
public void FileNameNullOrEmpty_
   ThrowsArgumentNullException_UsingAttribute () {
}

Summary

In this blog post you learned about many of the attributes you may apply to unit test classes and methods. The most common attributes you should add are Description, Owner and TestCategory. Description is optional, and should be used in combination with a descriptive unit test method name. The Owner, Priority and TestCategory attributes may be grouped within the Test Explorer window.

Sample Code

You can download the code for this sample at www.pdsa.com/downloads. Choose the category “PDSA Blogs”, then locate the sample Add Attributes to Unit Tests.

String Interpolation in C# 6.0

I just found this handy shorthand for string.Format() which is available in C# 6.0. I am sure you have all used string.Format() before. Let's say you want to do a little formatting with some data in some variables like the following:

string first = "Paul";
string last = "Sheriff";
Console.WriteLine(string.Format("{0} {1}", first, last));

In C# 6.0 you may now use the string interpolation character, the dollar sign, instead of the string.Format().

string first = "Paul";
string last = "Sheriff";
Console.WriteLine($"{first} {last}");

Both of the above pieces of code will place the string "Paul Sheriff" into your output window.

I really like this new format. It is much easier to read and understand where exactly your variables are going without having to count the number of parameters and figure out which ones go where in your string.

You may also combine the $ with the @ sign if you are going to have a backslash in your string. You must specify them in the order $@ for the interpolation to work correctly. Here is an example of using these two together.

string dir = "Windows";
Console.WriteLine($@"C:\{dir}");

The above code will display the string "C:\Windows" in your output window.

Have fun with this new feature of C# 6.0.

Unit Test Initialization and Cleanup

This blog post continues our look at unit testing techniques. See my previous two blog posts for the sample used for testing.

In this blog post you learn about initialization and cleanup of the test environment. There are different methods of initialization and cleanup available to developers in Visual Studio. This blog post will introduce you to each and describe how to use each one.

Overview

There are six attributes you can use to perform initialization and cleanup. Which ones you use, depends on when you need to initialize something and/or clean something up. You can initialize/cleanup once for all classes within a unit test assembly. You can initialize/cleanup once for all test methods within a test class. You can initialize/cleanup before and after each test method runs within a class.

  • AssemblyInitialize
  • AssemblyCleanup
  • ClassInitialize
  • ClassCleanup
  • TestInitialize
  • TestCleanup

Add one or more of these attributes to a single method within your classes. You can see an example of how these attributes might be applied to a set of classes in your assembly displayed in Figure 1. The order in which these methods run is as follows.

  1. AssemblyInitialize
  2. ClassInitialize
  3. TestInitialize
  4. TestMethod, TestMethod, TestMethod, etc.
  5. TestCleanup
  6. ClassCleanup
  7. AssemblyCleanup


Figure 1: Overview of how test initialization and cleanup methods execute

Assembly Initialization and Clean Up

If you have several classes within a specific assembly and you need to create a database with some test records in some tables prior to running all, or the majority, of tests within these classes, create that database within a method decorated with the [AssemblyInitialize] attribute.

I think it is a good idea to create a separate class that is only used for assembly initialization and cleanup. Create a new class with the name of MyClassesTestInitialization within your test project. Add the following method to this class.

[AssemblyInitialize()]
public static void AssemblyInitialize(TestContext tc) {
  // TODO: Initialize for all tests within an assembly
  tc.WriteLine(“In AssemblyInitialize”);
}

Only one method within your entire assembly may be decorated with the [AssemblyInitialize] attribute. This method must be static and the test framework will pass in an instance of the TestContext object.

If you wish to delete any files or a database after all methods have run within an assembly, place the code to perform these operations within a method decorated with the [AssemblyCleanup] attribute. Only one method within your entire assembly may be decorated with the [AssemblyCleanup] attribute. This method must be declared as static.

[AssemblyCleanup()]
public static void AssemblyCleanup() {
  // TODO: Clean up after all tests in an assembly
}

Class Initialization and Clean Up

Just as you create classes in your application to encapsulate a specific set of functionality, organize your unit test classes the same way. In the test program for this series of blog posts, I have a FileProcess class. There is currently only one method in that class, but in a real application, there would probably be many methods all dealing with file IO.

In the FileProcessTest class you only have unit test methods that perform testing on methods within the FileProcess class. Within the unit test class, you may need to create some files (or a database, or other data) that is needed when running all or the majority of the test methods. If that is the case, create a method within that class that is decorated with the [ClassInitialize] attribute. Only one method within your test class may be decorated with the [ClassInitialize] attribute.

[ClassInitialize()]
public static void ClassInitialize(TestContext tc) {
  // TODO: Initialize for all tests within a class
  tc.WriteLine(“In ClassInitialize”);
}

If you create a file (or a database, or other data), you might want to delete that file, or other data after all of the tests have run in that class. Add a method to perform this cleanup within a method decorated with the [ClassCleanup] attribute. Only one method within your class may be decorated with the [ClassCleanup] attribute.

[ClassCleanup()]
public static void ClassCleanup() {
  // TODO: Clean up after all tests within this class
}

Test Initialization and Clean Up

The method decorated with the [ClassInitialize] attribute only runs once the first time a class is created. A method decorated with the [TestInitialize] attribute will run before each test method within that class. If you have four methods in a test class, the TestInitialize method will run four times. As an example, if you need a specific file name to exist before all, or the majority, of tests within a class are run, create a method like the one shown below.

[TestInitialize()]
public void TestInitialize() {
  // Create the Test.txt file.
  File.AppendAllText("Test.txt", "Some Text");
}

The above code will run before each test method is executed. If you did not clean up after each test, then you would have four lines of text within the Test.txt file. To clean up after each method, write a method that has the [TestCleanup] attribute attached to it.

[TestCleanup()]
public void TestCleanup() {
  // Delete Text.txt file
  if (File.Exists("Test.txt")) {
    File.Delete("Test.txt");
  }
}

Since this method runs after each unit test method, each new test method that runs will have a file available to it that only has a single line of text within it.

Check for Test Name

Within the TestInitialize method you can check the TestName property of the TestContext object to see if a specific test is being run. If it is, you could do a specific initialization just for that test. In the following sample, you check to see if the current test being run is “FileNameDoesExist” and if it does, you create the file contained within the _GoodFileName property. Of course, you are already doing this in the FileNameDoesExist method already, so you do not need to do it in the TestInitialize method, but it does show an example of what you could do.

[TestInitialize]
public void TestInitialize() {
TestContext.WriteLine("In TestInitialize");
if (TestContext.TestName == "FileNameDoesExist") {
if (!string.IsNullOrEmpty(_GoodFileName)) {
TestContext.WriteLine("Creating file: " + _GoodFileName);
// Create the 'Good' file.
File.AppendAllText(_GoodFileName, "Some Text");
}
}
}

Summary

In this blog post you learned about initialization and clean up for unit tests. You have three ways to perform initialization and clean up in the unit test framework. You just need to decide where in the process you need to perform the initialization. Then decide how much clean up you need, and write the appropriate methods, and decorate with the appropriate attributes.

Sample Code

You can download the code for this sample at www.pdsa.com/downloads. Choose the category “PDSA Blogs”, then locate the sample Unit Test Initialization and Cleanup.


Avoid Hard-Coding in Unit Tests

In my previous blog post entitled Introduction to Unit Testing with Visual Studio, I introduced you to creating unit tests with Visual Studio. A method named FileExists was created to which you pass a file name to see if it exists. In the tests you created, you use hard-coded file names to test. Just as you wouldn’t hard-code values in a normal application, you should not do this with unit tests either. In this blog post you will learn to use constants, a configuration file, and how to create and delete test files.

Please go read the previous blog post and create the project, or download the project at http://www.pdsa.com/downloads and select “Introduction to Unit Testing” from the list.

Use a Constant

Constants are a great way to centralize hard-coded data that would otherwise be repeated throughout an application. In this case, you are going to replace the hard-coded file name used in the FileNameDoesNotExist method with a constant. At the top of the FileProcessTest class, add the following constant.

private const string BAD_FILE_NAME = @"C:\NotExists.bad";

Modify the FileNameDoesNotExist to use this new constant as shown in the code snippet below.

public void FileNameDoesNotExist() {
  FileProcess fp = new FileProcess();
  bool fromCall;
  fromCall = fp.FileExists(BAD_FILE_NAME);
  Assert.IsFalse(fromCall);
}

Use a Configuration File

A constant is a good option for the “bad” file name. For the “good” file name you wish to test to see exists, let’s add that to a configuration file so it can be modified easily. In fact, let’s add a replaceable token called [AppPath] that will figure out the appropriate path to use based on the machine the test is running upon.

Right mouse click on the FileProcessTest project and select Add | New Item… from the menu. From the template dialog select General | Application Configuration File. The name should already be set to App.config, so click on the Add button.

Within the <configuration> element add an <appSettings> section. Within the <appSettings> section add a key called GoodFileName with a value of [AppPath]\TestFile.text as shown below.

<appSettings>
  <add key="GoodFileName" value="[AppPath]\TestFile.txt"/>
</appSettings>

In order to retrieve this value from the configuration file, you need to use the ConfigurationManager class from the System.Configuration namespace. By default, the System.Configuration DLL is not added to a test project. Right mouse click on References folder in your FileProcessTest project and select Add Reference from the menu. From the dialog select Assemblies | Framework. Locate the System.Configuration dll and select the check box. Click the OK button to add this DLL to your test project.

At the top of the FileProcessTest class, add a using statement for the System.Configuration namespace.

using System.Configuration;

Add private field to FileProcessTest class to hold the value you are going to retrieve from the configuration file. Set the name of this field to _GoodFileName as shown below.

private string _GoodFileName;

Add a constructor to the FileProcessTest class in which you will read the GoodFileName value from the configuration file. Within this constructor you will replace the token [AppPath] with the value from the Environment.SpecialFolder.ApplicationData. This enumeration, supplied by .NET, when passed to the GetFolderPath, returns the pre-defined path for any data you need to store for this application. The location may vary from OS to OS, but on Windows 10 it is C:\\Users\\YOUR_USERNAME\\AppData\\Roaming. Write the constructor as shown below.

public FileProcessTest() {
  _GoodFileName =
     ConfigurationManager.AppSettings["GoodFileName"];
  if (_GoodFileName.Contains("[AppPath]")) {
     _GoodFileName = _GoodFileName.Replace("[AppPath]", 
        Environment.GetFolderPath(
          Environment.SpecialFolder.ApplicationData));
  }
}

Locate and modify the FileNameDoesExist method to use this new property name.

[TestMethod]
public void FileExistsTestTrue() {
  FileProcess fp = new FileProcess();
  bool fromCall;
  fromCall = fp.FileExists(_GoodFileName);
  Assert.AreEqual(true, fromCall);
}

Create / Delete File

Instead of you having to create the file name in the location specified, prior to running the FileNameDoesExist test, you should create the file name within the method itself. You should delete the file after you have performed the FileExists call so you don’t keep files around you don’t need. Modify the FileNameDoesExist method to look like the following.

[TestMethod]
public void FileNameDoesExist() {
  FileProcess fp = new FileProcess();
  bool fromCall;
  if (!string.IsNullOrEmpty(_GoodFileName)) {
    // Create the 'Good' file.
    File.AppendAllText(_GoodFileName, "Some Text");
  }

  fromCall = fp.FileExists(_GoodFileName);
  // Delete file
  if (File.Exists(_GoodFileName)) {
    File.Delete(_GoodFileName);
  }

  Assert.IsTrue(fromCall);
}

TestContext

When the unit test framework creates an instance of a test class (a class marked with the [TestClass] attribute), the test framework creates a TestContext object. This object contains properties and methods related to testing. To access this TestContext object, you must create a public property named TestContext in each of your test classes. The test framework checks for this property and inserts the instance of this TestContext into your property.

private TestContext _TestInstance;  
public TestContext TestContext  
{  
    get { return _TestInstance; }  
    set { _TestInstance = value; }  
}

One of the things you can do with this TestContext is to use the WriteLine method to add some output into the test results. Add the lines shown in bold below to write some messages about what file you are creating into the output area of the test results.

[TestMethod]
public void FileNameDoesExist() {
  FileProcess fp = new FileProcess();
  bool fromCall;
  if (!string.IsNullOrEmpty(_GoodFileName)) {
    TestContext.WriteLine("Creating file: " + _GoodFileName);
    // Create the 'Good' file.
    File.AppendAllText(_GoodFileName, "Some Text");
  }
  TestContext.WriteLine("Checking file: " + _GoodFileName);
  fromCall = fp.FileExists(_GoodFileName);
  // Delete file
  if (File.Exists(_GoodFileName)) {
    TestContext.WriteLine("Deleting file: " + _GoodFileName);
    File.Delete(_GoodFileName);
  }
  Assert.IsTrue(fromCall);
}

Run this test, once it is complete, click on the FileNameDoesExist test in the Test Explorer window, locate the Output link at the bottom of the window and click on it. You should then see your messages appear in an output window as shown in Figure 1.


Figure 1: Write output messages using the TestContext property

Summary

In this blog post a constant was used in place of a hard-coded file name. You placed another hard-coded file name into the App.config file in the test project. Within a test method you created a file, tested that file’s existence, then deleted the file. This helps keep that method self-contained and avoids manual setup of files prior to running these tests. Finally, you added a TestContext property to access the WriteLine method of the TestContext property. This allows you to add additional messages into the output of the test results.

Sample Code

You can download the code for this sample at www.pdsa.com/downloads. Choose the category “PDSA Blogs”, then locate the sample Avoid Hard-Coding in Unit Tests.

Introduction to Unit Testing with Visual Studio

Every developer needs to test their code, or have it tested by someone. Many developers are not great at testing their own code. The main reason is we tend to test only the “happy path” through the functionality that we wrote. We often avoid testing the boundaries of our code such as invalid inputs, exceptions that might occur, etc. One way to become a better tester is to start writing unit tests. While it takes more time up-front to write unit tests, it saves a ton of time when you must regression test changes to existing features.

Starting with Visual Studio 2008, Microsoft added a unit testing framework right into Visual Studio. There are also several third-part testing frameworks you may use. In this blog post, you are going to learn the basics of using the unit test framework in Visual Studio.

Our Method to Test

For this blog post, you are going to build a method that checks to see if a file exists. You are then going to build unit tests to check each type of input you can pass to this method.

To start, create a new Class Library project in Visual Studio using C#. Set the name of this class library project to MyClasses. Rename the Class1.cs file created by Visual Studio to FileProcess. Add a method in this class called FileExists as shown in the following code snippet.

public bool FileExists(string fileName) {
  if (string.IsNullOrEmpty(fileName)) {
    throw new ArgumentNullException("fileName");
  }
  return File.Exists(fileName);
}

This is a very simple method, yet it requires at least three unit test methods to ensure this method works with all possible inputs. The three possible values you can pass to the fileName parameter are:

  • A file name that exists
  • A file name that does not exist
  • A null or empty string

Create a Test Project

Right mouse click on your MyClasses solution and choose Add | New Project. From the list of templates, click on the Visual C# | Test | Unit Test Project. Set the Name to MyClassesTest. Click the OK button. Rename the UnitTest1.cs file to FileProcessTest.cs. You are going to test the method in your MyClasses class library, so you need to add a reference to that project. Right mouse click on the References folder in the MyClassesTest project and select MyClasses. Add a using statement at the top of the FileProcessTest.cs file.

using MyClasses;

Create Stubs for all Tests

As you have identified three different tests, it is a good idea to go ahead and write all three methods right away so you don’t forget what you want to test. The code shown below shows how you structure your test class.

[TestClass]
public class FileProcessTest
{
  [TestMethod]
  public void FileNameDoesExist() {
    Assert.Inconclusive();
  }
  [TestMethod]
  public void FileNameDoesNotExist() {
    Assert.Inconclusive();
  }
  [TestMethod]
  public void
     FileNameNullOrEmpty_ThrowsArgumentNullException() {
    Assert.Inconclusive();
  }
}

The first thing you notice about this class is the presence of the [TestClass] attribute before the class definition. This attribute informs the unit test framework that this class is one that can be included in the testing process. Next, you notice that each method is prefixed with a [TestMethod] attribute. Again, this is to inform the unit test framework that this is a test method that needs to run. Within each method, call the Assert.Inconclusive() method. This informs the testing framework that you have not written any code for this test method. This is not a success, or a failure, of the test. This shows up as a skipped test in the Test Explorer window. The advantage of using this method is this gives you a checklist of the tests that you still need to write.

After adding this code, right mouse click in your code window and choose Run Tests from the context-sensitive menu that appears. After the code runs, a Test Explorer window appears with the results of the test(s) as shown in Figure 1.


Figure 1: Skipped tests are the result of the Inconclusive method call

Write the Tests

It is now time to start writing the code in the unit tests to create each of the three possible inputs identified for this method. The first one is to test that a file exists. Modify the FileNameDoesExist method shown below. Feel free to change the drive letter, path and file name to a file that exists on your computer.

[TestMethod]
public void FileNameDoesExist() {
  FileProcess fp = new FileProcess();
  bool fromCall;
  fromCall = fp.FileExists(@"C:\Windows\Regedit.exe");
  Assert.IsTrue(fromCall);
}

After adding this code, right mouse click in your code window and choose Run Tests from the context-sensitive menu that appears. After the code runs, a Test Explorer window appears with the results of the test. If the file exists, the window should display something that looks like Figure 2.


Figure 2: Test results appear in the Test Explorer window

The next method to write is to test for a file that does not exist. Modify the FileNameDoesNotExist method in your FileProcessTest class to test this condition. Write the code shown in the following code snippet.

[TestMethod]
public void FileNameDoesNotExist() {
  FileProcess fp = new FileProcess();
  bool fromCall;
  fromCall = fp.FileExists(@"C:\NotExists.bad");
  Assert.IsFalse(fromCall);
}

Once again, run these tests and you should now see two passed tests in your Test Explorer window.

Handling Exceptions

You should always test for any exceptions being thrown from your methods. There are two ways to handle a thrown exception; add a catch block in your test method or add an [ExpectedException] attribute. In the FileExists method an ArgumentNullException is thrown if a null or blank value is passed to the method. Let’s take a look at the two ways to handle this thrown exception.

Add a test to the FileProcessTest class named FileNameNullOrEmpty_CausesArgumentNullException_UsingAttribute. Add an [ExpectedException] attribute after the [TestMethod] attribute on this method as shown in the following code snippet.

[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void FileNameNullOrEmpty_
             CausesArgumentNullException_UsingAttribute () {
  FileProcess fp = new FileProcess();
  fp.FileExists("");
}

Run this test and you should see that this test passes.

The second way to handle this thrown exception is to wrap up the call to the FileExists method within a try…catch block in your test method. The catch block should check to see if the exception is a ArgumentNullException. If it is, then the test received the correct return value. If no exception was thrown, or any other kind of exception is returned from FileExists, call the Assert.Fail() method to let the unit test framework that this test failed. Modify the FileNameNullOrEmpty_ThrowsArgumentNullException method as shown in the following code snippet.

[TestMethod]
public void FileNameNullOrEmpty_ThrowsArgumentNullException()
{
  FileProcess fp = new FileProcess();
  try {
    fp.FileExists("");
  }
  catch (ArgumentNullException) {
    // Test was a success
    return;
  }
  // Fail the test
  Assert.Fail("Call to FileExists() did NOT throw 
               an ArgumentNullException.");
}

Run the unit tests one more time, and you should now see four passed tests in the Test Explorer window.

Summary

Visual Studio makes it easy to get started creating unit tests. Take advantage of the unit test framework built-in to Visual Studio. It is important to think of as many ways as possible to break your code. Then, write all the unit tests to test that your code does not break. Use the ExpectedException attribute to help you with exceptions that are thrown from your methods.

Sample Code

You can download the code for this sample at www.pdsa.com/downloads. Choose the category “PDSA Blogs”, then locate the sample Introduction to Unit Testing.

Testing is Not Just for Quality Assurance People

Testing Every developer needs to test their code, or have it tested by someone. I don’t know about you, but I am horrible at testing my own code. Does this mean that I do not need to test my code? Heck, no! It is always best if you do not rely on your end-user to test your code. This can end up with a very frustrated user, and your user can lose faith in your ability to get their project done. There are several ways you can get your code tested. This article explores a few of these methods for testing and talk about the advantages and disadvantages of each.

Why you should Test your Code

We all know that we need to test our code prior to putting it into production. Testing is your way of ensuring that the code you have written actually works as expected. But it is not enough to check that it works as expected, but also under varying circumstances, and with different inputs. For example, what if someone takes the database offline? Will your code recover gracefully from this exception? Does your code inform the user that there is a problem with the database connection in a friendly, easily-understood manner? All of these questions should be answered in the testing of your code. You need to simulate these exceptions so you can test your exception code.

Performing the act of testing often helps you improve the quality of your code. As you think about the various scenarios that can go wrong, you add additional code to handle these scenarios. This leads to your code being more robust and ultimately more maintainable and user-friendly. You will find that taking time to test your code makes you a better programmer in the long run. Your boss, and your end-users, will appreciate the extra effort.

Problems with the Develop/Test Cycle

There are a lot of disconnects between the development of the code and testing of code. In many cases there is too much code embedded in the user interface (UI). Too much code in the UI makes testing hard because the tools for testing user interfaces aren’t very robust. It is also hard to determine if all of the code in the UI has actually been tested. A tester has to know all of the inputs to give the UI to have it run all of the code.

This leads to yet another disconnect; there is too much communication required between a developer and the QA person. Sometimes a developer will forget to tell the QA person about a “special” case. In other cases, a QA person forgets to tell the developer about something that failed, so it does not get caught and ends up as another bug that, hopefully, will be found later.

Another problem is when a developer thinks they have fixed a bug, only to find out that the fix caused a bug in another part of the program. The QA department may not know to test that other part of the program which could mean a bug gets shipped to a customer. Or, the QA department thinks it affects other areas, so they end up doing a lot more regression testing than maybe they need to.

How to Test Your Code

There are many different methods used to test code. Here are some different methods you can use to ensure you are writing quality code.

  • Use a Quality Assurance (QA) person or company
  • Have your end-user test
  • Have a co-worker test
  • Write code to test your code

Of the above listed methods of testing, the first three involve humans, while the last one is a more automated approach. Let’s look at these different methods of testing code.

The Human Approach

Having a human test your code does have a lot of advantages. First off a human is going to be able to click on different buttons and test the UI very well. Having different people test your code can be helpful because each person may think of different things to test. In addition it will take less time up front to develop your software. 

But just as there are advantages to using humans for testing, there are also disadvantages. Testers find bugs, document them and push them back to you to re-code. After this, the tester has to go back and re-test. You will find this takes a lot more time in the long run. Many times prior to testing someone has to create records in a database, and afterwards get rid of tables, records, or files on disk. This is a very time-consuming and error-prone operation. You also find that the burn-out rate for QA people is very high. You frequently have a high turnover rate in this job function. So there are many down-sides to using humans to perform testing.

The Automated Approach

Instead of having a human do all the testing, you should start to use the great testing tools that are available in Visual Studio to create automated tests. Unit testing has become very prevalent in today’s professional software shops. Unit testing simply means you write a program, or most likely a series of programs to test each line of code you write to ensure that it operates properly under all circumstances.

While this sounds like a lot of work, and it can be more work up-front, you will more than make up that time by avoiding multiple regression tests by a human. You can automate the setup and tear down of databases, files, etc. With the correct architecture you can automate almost all the various inputs a human tester would have to enter by hand.

You will also save the time you normally eat up when you do your own testing by pressing F5 to start Visual Studio, wait for the compile process, click through a half dozen menus to finally get to the point where you want to start testing. As you know, this can sometimes take 15 seconds to a minute depending on how large your application is. Instead, you can run one or two tests in just a couple of seconds. This will add up in saving you many hours over the complete development cycle.

As you can see, there are many advantages to an automated approach over a human approach to testing. Automated tests are repeatable as opposed to having a human who might forget to test something. You will end up with more of your code tested because things won’t be forgotten. Because you will be forced to think of how to test your code while you are writing it, you will write better code. You will also save time on the setup and the tear down of the tests since these tasks can also be automated.

Of course, there are disadvantages to the automated approach as well. First of all, it does take more time up-front to develop these tests. Automated tests are only as good as the person that develops the tests. The tools to test user interfaces are not too great at this time without spending a fortune on third party testing tools. Of course, if you are using good N-Tier techniques, MVC, MVP, MVVM or similar design patterns, there should be very little UI for you to test anyway. 

Which Approach Should You Use?

The big question is this: do you use automated unit tests or do use a QA department? I still think you need a little of both. You want to get as many automated tests as you possibly can as this will save a lot of regression testing. Then the QA department can focus more on system testing, workflow and ensuring the correct data is flowing all the way through the application. The QA department can also focus on the UI features that are difficult to automate. A good mix of the two will go a long way toward making your applications much more robust and error free.

Architecting Your Code for Testing

As you are coding your application there are things you can do to prepare to take advantage of unit testing. Correctly architecting an application will do wonders for re-usability, readability, upgradeability, maintainability and testability. Take advantage of the design patterns that exist today such as MVC, MVP, MVVM or derivatives thereof. All of these patterns help you remove code from the user interface layer of your application. Removing code from the UI layer is this is the single best thing you can do to have a well architected, easily testable application.

Your UI code should strive to just call or bind to classes. Each class you write is where all the logic is contained for your application. Moving all application logic into classes, you take advantage of the various unit testing tools in Visual Studio. All of these classes should be combined into assemblies to aid not only in testing, but also in re-usability. 

Other techniques you should employ are to make your methods as small as possible.  A method should do one thing and one thing only. This makes testing that method easy as you most likely will only need to write one method to test that method. It is better to have many smaller methods and smaller tests, than one large method with lots of tests against that one method. One last technique is to use code generation tools for generating your CRUD layer. Create, Read, Update and Delete logic is employed in almost all business applications and is very standard code. There is no reason to hand-write this code with all of the tools available today to generate it. And, generated code is generally code you do not need to test. If the code generator is good, it will generate bullet proof code every time.

Visual Studio Testing Tools

Beginning with Visual Studio 2008, unit testing tools were added to almost all SKUs of Visual Studio. This means you do not need to purchase Team Foundation Server to be able to use unit testing. It also means you do not have to use NUnit or some other third-party application for unit testing. It is available to you right out of the box. Of course, the TFS version of Visual Studio has many more features, but for basic unit testing, you will find everything you need in the normal versions.

Summary

Unit testing is something every developer should employ as a part of their software development life cycle. Not only will help you improve the quality of your code, it will save you time and money in the long run. As you focus on architecting your applications for unit testing, you will find that your code will become re-usable, maintainable and more robust. Take some time to learn about the unit testing tools built-in to the Visual Studio suite of products and you will be on your way to better productivity.


Build a Credit Card Entry Page using Angular – Part 3

In the last two blog posts (Part 1 and Part 2), you built an HTML page (Figure 1) to enter credit card information. You have the drop-down lists loaded with data coming from a Web API service. Your last tasks for this page are to validate the data entered is correct, both on the client and the server, display any validation messages, and finally, save the credit card data into the CreditCard table in your SQL Server database.

You are going to build custom directives in Angular to validate the input on the client side. A new method is going to be added to the view model class to save the credit card data. An additional method is added to the EF classes to validate the data on the server-side. If validation errors are detected, those errors are serialized and sent back to the client for display in an unordered list. This blog post will show you how to accomplish each of these tasks.

Figure 1: The Credit Card data entry page

Save Credit Card Data

The credit card table has a primary key field named CreditCardId. This field is a property in the CreditCard class generated by the Entity Framework. In the first blog post in this series, a hidden input field was created within the <form> tag to hold the CreditCardId field. If this value is null or an empty Guid, you know you need to insert the entered credit card data. If the value is a real Guid, then you know you just want to update the entered credit card data.

In the ViewModelLayer project, open the CreditCardViewModel class and add a SaveData() method. This class has an Entity property which is an instance of the CreditCard class. When the data is submitted by the user, the Entity property of the view model class is filled in by the controller. If the CreditCardId property is filled in with a valid Guid, assume that the credit card data already exists in the database and set the state of the current entity in the EF data context to modified. When the SaveChanges method is called on the data context, an INSERT or an UPDATE statement is sent to the SQL Server.

public void SaveData() {
  PTC db = null;
  Messages = null;
  try {
    db = new PTC();
    // Determine whether to Insert or Update
    if (Entity.CreditCardId == null ||
        Entity.CreditCardId == Guid.Empty) {
      // Set new Guid for Primary Key
      Entity.CreditCardId = Guid.NewGuid();
      db.CreditCards.Add(Entity);
    }
    else {
      db.Entry(Entity).State = 
         System.Data.Entity.EntityState.Modified;
    }
    db.SaveChanges();
  }
  catch (DbEntityValidationException ex) {
    IsValid = false;
    Messages = ex.EntityValidationErrors.ToList();
  }
}

Add CreditCardController

Go back to the CreditCardEntry project and locate the Controllers folder. Right mouse click on this folder and select Add | Web API Controller Class (v2.1). If this option does not appear in your drop down menu, select Add | New Item and choose it under the Web | Web API templates. Set the name of this controller to CreditCardController. Delete all the code in this controller and add a Post method as shown below:

[HttpPost()]
public IHttpActionResult Post(
      CreditCard card) {
  IHttpActionResult ret = null;
  CreditCardViewModel vm = 
    new CreditCardViewModel();
  ModelStateDictionary Messages = 
    new ModelStateDictionary();
  // Assign client-side credit card object 
  // to view model entity
  vm.Entity = card;
  // Attempt to save data
  vm.SaveData();
  if (vm.IsValid) {
    ret = Created<CreditCard>(
       Request.RequestUri +
         vm.Entity.CreditCardId.ToString(), 
           vm.Entity);
  }
  else {
    if (vm.Messages.Count > 0) {
      // Validation errors
      foreach (var msgs in vm.Messages) {
        foreach (var item in msgs.ValidationErrors) {
          Messages.AddModelError(item.PropertyName,
             item.ErrorMessage);
        }
      }
      ret = BadRequest(Messages);
    }
  }
      
  return ret;
}

Add some using statements to the top of this controller file to resolve references to the various classes used in this method.

using DataLayer;
using ViewModelLayer;
using System.Web.Http.ModelBinding;

This Post method takes the CreditCard object passed from the UI and places it into the Entity property of the view model class. Next it calls the SaveData method you just wrote to either insert or update the data into the CreditCard table. If the view model’s IsValid property is set to true, set the return value to the result of the Created method. This method returns the URI where the front end can retrieve the new entity. The Created method also returns the complete entity object back to the front end. You should use this method to pass back any property data that may have changed because of the insert or update into the table.

If the IsValid property is set to a false value, then check to see if there are messages in the Messages property of the view model class. If there are you loop through the messages in the Messages property and add them as a model error in a ModelStateDictionary. This dictionary can be passed as a payload from the BadRequest method. In your Angular controller, loop through these model state dictionary objects and retrieve the error messages and display them on the UI to your user. Later in this blog post you add validation failure messages to this Messages property.

Save Data from Angular

Now that you have the back-end services written to save the credit card data to your database table, it is now time to write the Angular code to clean up the data on the front end and call the Post method in your CreditCard Web API controller.

Add Clean Up Data Function

Before I pass any data from the client-side to the server-side, I always have a little routine I call named cleanUpData. This function can clean up dates (which can be an issue in Internet Explorer), it can take selected values from drop-downs and move it into the actual entity object to pass to the server. It can also do any other clean up that you may find necessary. Open the creditcard.controller.js file and add a function named cleanUpData.

function cleanUpData() {
  // Get card type
  vm.creditCard.cardType = 
      vm.selectedCardType.cardType;
  // Get expiration month
  vm.creditCard.expMonth = 
      vm.selectedMonth.monthNumber;
}

In the cleanUpData function for the credit card page, take the selected card type from the vm.selectedCardType.cardType property and put it into the vm.creditCard.cardType property. Next, take the selected month number from the vm.selectedMonth.monthNumber property and put it into the vm.creditCard.expMonth property. You are going to pass the vm.creditCard object to the server and this methods retrieves the data from the appropriate selected objects and puts that data into the appropriate properties in the vm.creditCard object.

Add Insert Data Function

Add a function named insertData to your creditcard.controller.js. Call the cleanUpData function you just created. Call the dataService.post function to call the Post method on your server. Pass to the post function the URL where your post method is located and the vm.creditCard object which contains the data your user entered on the credit card page. If the call to the Post method is successful, get the result.data and set it back into the vm.creditCard object. In case the server updates any of the fields during the insert or update statement, you want to be able to reflect that data back on the page for the user. However, in this function, the user won’t see it as you are going to navigate back to the home page using $location service. However, if you were going to stay on this page for some reason, it would be good to display the updated values to the user.

function insertData() {
  // Clean up object before sending to server
  cleanUpData();
  // Post credit card info to server
  dataService.post(
      "/api/CreditCard", vm.creditCard)
    .then(function (result) {
      // Update credit card object
      vm.creditCard = result.data;
      // Redirect back to home page
      $location.path("/");
    }, function (error) {
      handleException(error);
    });
}

Add Save Click Function

The save button on your credit card HTML page has a ng-click event which calls a function named saveClick. You pass in the name of the <form> to this function to test the validity of validation you created on the form. You are going to learn about validation a little later in this blog post. For now, just write the saveClick as shown below.

function saveClick(creditCardForm) {
  if (creditCardForm.$valid) {
    vm.uiState.isMessageAreaHidden = true;
    creditCardForm.$setPristine();
    insertData();
  }
  else {
    vm.uiState.isMessageAreaHidden = false;
  }
}

If all the controls pass Angular validation the $valid property is set to true on the creditCardForm object. To make sure no further messages are displayed on the web page, call the $setPristine function. The $setPristine function sets the internal flags on the Angular credit card model to a value which is consistent with the first time you entered the page. Call the insertData function to post the data entered to the Web API controller.

All functions you need to call from the HTML page must be registered on the $scope object. Add a blank line just below your variable declarations and before the call to the loadCardTypes function and add the following lines of code.

// Expose public functions
vm.saveClick = saveClick;

Run the credit card page and enter some data, click on the save button, and you should be able to look in the CreditCard table and see the data you entered.

Add Angular Validation

For each field on your web page, decide which fields to perform validation upon. Determine the type of validation you can accomplish with the attributes available in HTML/HTML5 and Angular. In the table below is a list of the attributes you can use with Angular validation.

Attribute Type Description
required HTML The field must contain a value.
min HTML A minimum value for a numeric input field.
max HTML A maximum value for a numeric input field.
ng-minlength Angular The minimum number of characters for a field.
ng-maxlength Angular The maximum number of characters for a field.
ng-required Angular The field must contain a value. Same as 'required'.
ng-pattern Angular A regular expression the input value must match.
Table 1: Validation attributes you may add to any input field.

Each input field must have the name attribute in addition to the id attribute if you wish to use Angular validation. The name attribute, combined with one or more of the attributes listed in Table 1, is what Angular uses to determine the set of fields that need to be validated.

Add Attributes to Input Fields

Open the creditcard.html page and start adding validation to each of the input controls that require it. You are going to add a combination of HTML attributes, custom validation directives, and Angular validation. Start by locating the nameOnCard field and add the pdsa-validatenotlowercase and the required attributes. The custom directive pdsa-validatenotlowercase will check to make sure that the name on the credit card is not entered as all lower-case characters. You will write this custom directive later in this blog post, for now, just add the attribute.

<input id="nameOnCard"
       name="nameOnCard"
       ng-model="vm.creditCard.nameOnCard"
       pdsa-validatenotlowercase
       required
       class="form-control"
       placeholder="Name on Card"
       title="Name on Card"
       type="text" />

A credit card number needs to be of a certain length, and you can validate the number entered by using the Luhn algorithm. The custom directive pdsa-validatenotlower case is simply going to check the length, but you can add the Luhn algorithm if you wish. Yes, we could use the ng-minlength and ng-maxlength attributes for verifying length, but I want to show you how to create a custom directive. Locate the cardNumber control and add the pdsa-validatecreditcard and required attributes.

<input id="cardNumber"
       name="cardNumber"
       ng-model="vm.creditCard.cardNumber"
       pdsa-validatecreditcard
       required
       class="form-control"
       placeholder="Credit Card Number"
       title="Credit Card Number"
       type="text" />

A credit card security code is the 3 or 4-digit code found on the back of a credit card. Again, you are going to write a custom directive called pdsa-validatesecuritycode to check the length of the input. Find the securityCode control and add the pdsa-validatesecuritycode and required attributes.

<input id="securityCode"
       name="securityCode"
       ng-model="vm.creditCard.securityCode"
       pdsa-validatesecuritycode
       required
       class="form-control"
       placeholder="Security Code"
       title="Security Code"
       type="text" />

The billing postal code for the credit card can be just about any combination of letters, spaces and numbers up to 18 characters. You can add another custom directive to test to ensure only letters, spaces and numbers and no other characters, or you can use the ng-pattern directive and a regular expresssion. Locate the billingPostalCode control and add the ng-maxlength and required attributes.

<input id="billingPostalCode"
       name="billingPostalCode"
       ng-model="vm.creditCard.billingPostalCode"
       ng-maxlength="18"
       required
       class="form-control"
       placeholder="Billing Postal Code"
       title="Billing Postal Code"
       type="text" />

Add Custom Directives

You added some custom attributes on the input fields above, so you now need to register the appropriate directives on your Angular application module. Directives are registered to the application module using the directive API. You may chain multiple directive functions together if you have more than one.

Add a new JavaScript file in the \creditcard folder named creditcard.directives.js. In this file, add the following code to define the overall structure for each of the custom directives you added to the input fields.

(function () {
  "use strict";
  angular.module("app")
   .directive('pdsaValidatenotlowercase', function () {
    return { };
  })
  .directive('pdsaValidatecreditcard', function () {
    return { };
  })
  .directive('pdsaValidatesecuritycode', function () {
    return { };
  });
})();

Each directive is defined with two parameters, the directive name (expressed in camel case) and a function that returns an object. The first parameter is the directive name you wish to use for the HTML attribute. This name must start with a lower-case letter, then must have one, and only one, upper case letter. It is at the break between the lower-case letters and upper case letter where you use a hyphen in your HTML attribute. It is recommended that you come up with your own unique prefix to avoid name collisions with other libraries you might add. In this case I used ‘pdsa’ as my prefix.

There are many properties that can be returned from the object supplied by the second parameter to the directive function. For a simple validation directive, the object returned from the function simply needs two properties; require and link. As an example of this object, below is the code you return from the pdsaValidatenotlowercase directive.

return {
  require: 'ngModel',
  link: 
    function (scope, element, attributes, ngModel) {
      ngModel.$validators.pdsavalidatenotlowercase =
        function (value) {
          if (value) {
            return value.toLowerCase() != value;
          }
          return true;
        }
    }
};

Starting with the second property, link, you see that it is a function that receives four parameters. The only one you are interested in for a validation directive is the last one, ngModel. Now you see why the require property is needed. When Angular sees that you are requiring the use of ngModel, it knows to pass in the four parameters to the function specified in the link property.

Within the function in the link parameter you add your own function, named pdsavalidatenotlowercase in this sample, to the $validators collection of the current ngModel. When Angular performs its validation, it loops through the $validators collection checking to see what Angular validators are connected, such as ng-minlength and ng-maxlength. By adding your own function into this collection, Angular will call your function. Your function must accept one parameter, the value from the control to which this directive is attached, and the function must return a true or false value. This true or false value can be tested in your HTML and can then display or hide appropriate validation messages. The complete code for all the custom directives you added to the HTML is listed below.

(function () {
  "use strict";
  angular.module("app")
   .directive('pdsaValidatenotlowercase', 
    function () {
    return {
      require: 'ngModel',
      link: function (scope, element,
                      attributes, ngModel) {
        ngModel.$validators
          .pdsavalidatenotlowercase =
          function (value) {
          if (value) {
            return value.toLowerCase() != value;
          }
          return true;
        }
      }
    };
  })
  .directive('pdsaValidatecreditcard', 
    function () {
    return {
      require: 'ngModel',
      link: function (scope, element, 
                      attributes, ngModel) {
        ngModel.$validators
         .pdsavalidatecreditcard = 
          function (value) {
          if (value) {
            value = value.toString().split("-").join("")
                .split(" ").join("");
            return value.length >= 13 && 
                   value.length <= 16;
          }
          return true;
        }
      }
    };
  })
  .directive('pdsaValidatesecuritycode', 
    function () {
    return {
      require: 'ngModel',
      link: function (scope, element, 
                      attributes, ngModel) {
        ngModel.$validators
          .pdsavalidatesecuritycode =
          function (value) {
          if (value) {
            return value.length === 3 ||
                   value.length === 4;
          }
          return true;
        }
      }
    };
  });
})();

After you have added all the directive code, open the index.html and add a reference to your new creditcard.directives.js file below your other script references.

<script src="app/creditcard/creditcard.directives.js">
</script>

Display Validation Messages

In part one of this blog post series, you added a <div> element in which you added an unordered list and a list item that uses the ng-repeat directive to display messages from the vm.uiState.messages array. Just below this list item you are going to add a series of additional list item elements to display validation messages coming from the Angular validation system.

Each new list item element uses the ng-show directive to display the item based on the result of the function call within the ng-show directive. For example, here is the list item to display a message if the user enters all lower-case letters in the Name on Card field.

<li ng-show="creditCardForm.nameOnCard.
             $error.pdsavalidatenotlowercase">
  Name on Card must not be all lower case.
</li>

Angular looks for a control in the creditCardForm with the name of nameOnCard. It then checks its $error property to see if the result of calling the custom directive function is true. If it is, then it knows that the value entered was in all lower-case letters, thus this list item is displayed in the unordered list. The listing below are the list items to add to the unordered list in the messages area to display all Angular validation messages.

<ul>
  <li ng-repeat="msg in vm.uiState.messages">
    {{msg.message}}
  </li>
      <li ng-show="creditCardForm.nameOnCard.
               $error.required">
    Name on Card must be filled in.
  </li>
  <li ng-show="creditCardForm.nameOnCard.
               $error.pdsavalidatenotlowercase">
    Name on Card must not be all lower case.
  </li>
  <li ng-show="creditCardForm.cardNumber.
               $error.required">
    Credit Card Number must be filled in.
  </li>
  <li ng-show="creditCardForm.cardNumber.
               $error.pdsavalidatecreditcard">
    Credit Card Number is invalid
  </li>
  <li ng-show="creditCardForm.securityCode.
               $error.required">
    Security Code must be filled in.
  </li>
  <li ng-show="creditCardForm.securityCode.
               $error.pdsavalidatesecuritycode">
    Security Code is invalid
  </li>
  <li ng-show="creditCardForm.billingPostalCode.
               $error.required">
    Billing Postal Code must be filled in.
  </li>
  <li ng-show="!creditCardForm.
               billingPostalCode.$valid">
    Billing Postal Code must have 18 
    characters or less.
  </li>
</ul>

Run this sample and enter various combinations of bad data to test out each of the validation error messages.

Add Server-Side Validation

Now that you have all the client-side validation working, add similar functionality to the server-side code as well. As it is very simple for a hacker to bypass client-side validation, you always check to ensure the data is validated on the server-side as well. To do this you add a new class to the DataLayer project named PTC-Extension. After the file is added, rename the class inside of the file to PTC and make it a partial class. This will allow us to add additional functionality to the PTC Entity Framework model created in part two of this blog post series.

public partial class PTC
{
}

When you attempt to insert or update data using the Entity Framework, it first calls a method named ValidateEntity to perform the validation on any data annotations added to each property. You may override this method to add your own custom validations. Add the following code to the PTC class in the PTC-Extension.cs file you just added.

protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry,
  IDictionary<object, object> items) {
  return base.ValidateEntity(entityEntry, items);
}

Add a new method named ValidateCreditCard just after the ValidateEntity method you added. In this method is where you add your own custom validations. You return a list of DbValidationError objects for each validation that fails.

protected List<DbValidationError> ValidateCreditCard(CreditCard entity) {
  List<DbValidationError> list = 
    new List<DbValidationError>();
  return list;
}

The ValidateEntity method is called once for each entity class in your model that you are trying to validate. In our example, you are only validating the CreditCard object since that is what the user is inputting. The entityEntry parameter passed into this method has an Entity property which contains the current entity being validated. Write code to check to see if that property is a CreditCard object. If it is, pass that object to the ValidateCreditCard method. The ValidateCreditCard method returns a list of additional DbValidationError objects that need to be returned. If the list count is greater than zero, then return a new DbEntityValidationResult object by passing in the entityEntry property and your new list of DbValidationError objects.

protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry,
    IDictionary<object, object> items) {
  List<DbValidationError> list = 
    new List<DbValidationError>();
  if (entityEntry.Entity is CreditCard) {
    CreditCard entity = entityEntry.Entity as CreditCard;
    list = ValidateCreditCard(entity);
    if (list.Count > 0) {
      return new DbEntityValidationResult(entityEntry, list);
    }
  }
  return base.ValidateEntity(entityEntry, items);
}

Now write the ValidateCreditCard method to perform the various validations for your credit card data. Check the same validations you performed on the client-side. In this ValidateCreditCard method, you are going to retrieve a the YearsInFuture value from the <appSettings> section in your Web.config file. Add a reference in your DataLayer project to System.Configuration.dll so you can use the ConfigurationManager class.

protected List<DbValidationError> 
   ValidateCreditCard(CreditCard entity) {
  List<DbValidationError> list = 
    new List<DbValidationError>();
  // Check Name on Card
  if (entity.NameOnCard.ToLower() == entity.NameOnCard) {
    list.Add(new DbValidationError("NameOnCard",
      "Name on Card must not be all lower case."));
  }
  // Check Card Number
  entity.CardNumber = entity.CardNumber
    .Replace("-", "").Replace(" ", "");
  if (entity.CardNumber.Length < 13 || 
      entity.CardNumber.Length > 16) {
    list.Add(new DbValidationError("CardNumber",
      "Card Number is not valid"));
  }
  // Check Security Code
  if (entity.SecurityCode.Length < 3 ||
      entity.SecurityCode.Length > 4) {
    list.Add(new DbValidationError("SecurityCode",
      "Security Code is not valid"));
  }
  // Check Month and Year
  if (entity.ExpMonth < 1 || 
      entity.ExpMonth > 12) {
    list.Add(new DbValidationError("ExpMonth", 
      "Invalid Month."));
  }
  else {
    if (entity.ExpYear < DateTime.Now.Year ||
        entity.ExpYear > DateTime.Now.Year +
            Convert.ToInt32(ConfigurationManager
              .AppSettings["YearsInFuture"])) {
      list.Add(new DbValidationError("ExpYear",
        string.Format("Expiration Year must be
          greater than {0} and less than {1}.",
            DateTime.Now.Year.ToString(), 
              ConfigurationManager
                .AppSettings["YearsInFuture"])));
    }
    else {
      if (entity.ExpMonth < DateTime.Now.Month &&
          entity.ExpYear == DateTime.Now.Year) {
        list.Add(new DbValidationError("ExpYear",
          "Expiration Month/Year must be 
           greater than this month."));
      }
    }
  }
  return list;
}

Modify handleException Function

The last thing you need to do is to modify the handleException() function located in the creditcard.controller.js file. Add a new case statement to handle a 400 which is a bad request. The BadRequest method is used as the return value in your CreditCardController.cs Post method when the view model property IsValid is set to false. This property is set to false when the Entity Framework raises an exception due to validation failing. Add the code shown below to the handleException function.

function handleException(error) {
  vm.uiState.isMessageAreaHidden = false;
  vm.uiState.isLoading = false;
  vm.uiState.messages = [];
  switch (error.status) {
        case 400:   // 'Bad Request'
      // Model state errors
      var errors = error.data.modelState;
      // Loop through and get all 
      // validation errors
      for (var key in errors) {
        for (var i = 0; i < errors[key].length;
                i++) {
          vm.uiState.messages.push({
            property: key,
            message: errors[key][i]
          });
        }
      }
      break;
  // PREVIOUS EXCEPTION HANDLING CODE HERE
}

To try out the server-side validation, comment out the creditcard.directives.js <script> tag in the index.html file and run the project. Put in a lower-case name in the nameOnCard input field. Fill in valid values for all other fields. Click the Save button to call the Web API to post the data and an error message should be returned and displayed telling you that you can’t have a name that is all lower case.

Summary

In this blog post, you learned to save the credit card data to your database table using the Entity Framework. In addition, you learned to validate data both on the client-side and the server-side. Over these past three blog posts you have built a credit card data entry page using Angular, the Web API and C#. You learned how to load drop-down lists, create areas on your page that you can turn off and on, validate data and save data into a database. Hopefully this will give you a lot of ideas on how to build other pages using Angular and the Web API.

Sample Code

You can download the code for this sample at www.pdsa.com/downloads . Choose the category "PDSA Blog", then locate the sample PDSA Blog Sample: Angular Credit Card Page – Part 3.

Build a Credit Card Entry Page using Angular – Part 2

In the last blog post, you created an HTML page (Figure 1) to enter credit card information using Angular. You created some hard-coded functions in your Angular controller to populate the three drop-down lists. In this blog post, you create Web API calls to gather the data for these three drop-down lists from a SQL Server table. These Web API calls request the information for these drop-down lists from a view model class. The view model class uses the Entity Framework (EF) to build a collection credit card types from a SQL Server table, a collection of language-specific month names, and a collection of years. Once you have this built, you call the Web API from your Angular controller to load the drop-down lists from this data instead of the hard-coded data you used in the last blog post.

Figure 1: The Credit Card data entry page

Database Design

There are two tables (shown in Figure 2) needed for the credit card page. The first table, named CreditCardType, holds the list of credit card types (Visa, MasterCard, etc.) to be loaded into the drop-down on the web page. The second table, named CreditCard, holds the credit card information entered by the user, and will be used in the next blog post.

Figure 1: Two tables are needed for credit card information

To create these two tables, open SQL Server Management Studio and select an existing, or create a new database. Open a query window in your new database and type in the following script to create the CreditCard and CreditCardType table.

CREATE TABLE CreditCard (
	CreditCardId uniqueidentifier NOT NULL 
	   PRIMARY KEY NONCLUSTERED,
	CardType varchar(20) NOT NULL,
	NameOnCard varchar(100) NOT NULL,
	CardNumber varchar(25) NOT NULL,
	SecurityCode varchar(4) NOT NULL,
	ExpMonth smallint NOT NULL,
	ExpYear smallint NOT NULL,
	BillingPostalCode varchar(10) NOT NULL
);
CREATE TABLE CreditCardType (
	CardTypeId int IDENTITY(1,1) NOT NULL 
	  PRIMARY KEY NONCLUSTERED,
	CardType varchar(20) NOT NULL,
	IsActive bit NOT NULL DEFAULT ((1)) 
);
INSERT CreditCardType (CardType)
       VALUES ('Visa');
INSERT CreditCardType (CardType) 
       VALUES ('Master Card');
INSERT CreditCardType (CardType) 
       VALUES ('American Express');
INSERT CreditCardType (CardType) 
       VALUES ('Discover');
INSERT CreditCardType (CardType) 
       VALUES ('Diners Club');

Add the Entity Framework

I separate the classes for my data access into a separate DLL. This provides me with the ability to change my data access method later if I want. To do this, right mouse click on your solution and choose Add | New Project from the menu. Select Windows | Class Library from the list of templates. Set the Name to DataLayer and click the OK button. Delete the Class1.cs file from the project as this is not needed.

Right mouse click on the new DataLayer project and select Add | New Item from the menu. Select Data | ADO.NET Entity Data Model from the list of templates. Set the Name to PTC and click the Add button. Choose Code First from Database from the list. Create a new connection to the database that contains the CreditCard and CreditCardType tables you created earlier. Select both tables from the list and click the Finish button.

After the generation is complete, you have three new classes in your class library project. An App.config file is also created with a <connectionString> element. Move the connection string from the App.config in this project into the Web.config file in your CreditCardEntry project. Open the App.config file and locate the <connectionStrings> element that looks like the following:

<connectionStrings>
  <add name="PTC"
     connectionString="data source=localhost;
         initial catalog=PTC;
         integrated security=True;
         MultipleActiveResultSets=True;
         App=EntityFramework"
    providerName="System.Data.SqlClient" />
</connectionStrings>

Cut this out of the App.config file, open the Web.config file in the CreditCardEntry project and paste this section into this config file. After you have moved the connection string, delete the App.config file from the DataLayer project. Your data layer is now complete and ready to be used.

Add a View Model

Sticking with our theme of "separation of concerns", let’s build a view model class to use as the intermediary between the Web API controller and the data access layer. It is a good practice to keep as little code as possible in the controller. By creating a view model class in a separate project it allows you to reuse all the business and data access logic in any other project.

Right mouse click on your CreditCardEntry solution and choose Add | New Project from the menu. Select Windows | Class Library from the list of templates. Set the Name to ViewModelLayer and click the OK button. Rename the Class1.cs file to CreditCardViewModel.cs. Answer yes when prompted if you wish to rename the class as well.

Right mouse click on the References folder and select Add Reference from the menu. Click on the Projects | Solution tab and check the DataLayer from the list of projects. Click the OK button to add the reference.

Since you are going to be using Entity Framework generated classes in your view model class you need to add some EF references to this project. Right mouse click on the ViewModelLayer project and select Manage NuGet Packages from the menu. Click on the browse tab and type in Entity Framework into the search text box and hit the Enter key. Locate the EntityFramework by Microsoft and click on it. Click the Install button to add all of the appropriate references to the entity framework for this project.

MonthInfo Class

When loading the months into the drop-down list, you are going to need to know the month number as well as the month name. Create a class called MonthInfo in the ViewModelLayer project that you can place these two values into. A collection of these objects will be serialized and sent to Angular for loading into the drop-down.

public class MonthInfo
{
  public MonthInfo(short number, string name) {
    MonthNumber = number;
    MonthName = name;
  }
  public short MonthNumber { get; set; }
  public string MonthName { get; set; }
}

Add using Statements

The CreditCardViewModel class is going to call the CreditCardType class to retrieve the various credit card types to display. This class will retrieve validation error messages from your EF generated classes and generate a list of month names based on the user’s current browser language. With all of this functionality, and a few others, it will be necessary to add the following list of using statements at the top of the CreditCardViewModel file.

using DataLayer;
using System;
using System.Collections.Generic;
using System.Data.Entity.Validation;
using System.Globalization;
using System.Linq;

Create Properties for View Model

Like any view model class, a set of properties are needed to hold the state of the object. Add the following properties to the CreditCardViewModel class.

public bool IsValid { get; set; }
public List<DbEntityValidationResult> Messages { get; set; }
public string DefaultLanguage { get; set; }
public string Language { get; set; }
public CreditCard Entity { get; set; }
public List<CreditCardType> CardTypes { get; set; }
public List<MonthInfo> Months { get; set; }
public List<int> Years { get; set; }

The Entity property holds the current CreditCard object the user is attempting to insert. An IsValid property is used to report back if there were validation errors in the current Entity. The Messages property is a list of DbEntityValidationResult objects. Each of these objects contains a validation error for a field that failed.

As there are three drop-down lists to load on your page, create three properties to hold a collection for each of these; CardTypes, Months and Years. For the months drop-down list, you should strive to display them in the user’s language. You can attempt to retrieve the user’s language from the browser and pass that to the Language property in the view model so it can use the Globalization classes in .NET to retrieve month names in the language of choice. However, if you are unable to retrieve the user’s language setting, then you should have a DefaultLanguage property you can fill in with a default language setting.

Initialize the Public Properties

It is always a good idea to initialize your properties in your view model class to a valid start state. Build a constructor in your view model class to set each property to an initialized state.

public CreditCardViewModel() {
  IsValid = true;
  Messages = new List<DbEntityValidationResult>();
  DefaultLanguage = string.Empty;
  Language = string.Empty;
  Entity = new CreditCard();
  CardTypes = new List<CreditCardType>();
  Months = new List<MonthInfo>();
  Years = new List<int>();
}

Load Card Types

In the last blog post, you hard-coded credit card types in the Angular controller. Now that you have a database table, and the appropriate EF classes, you can retrieve these credit card types from your table and populate your CardTypes property. Create a method called LoadCardTypes() and write the code shown below.

public void LoadCardTypes() {
  PTC db = new PTC();
  CardTypes = db.CreditCardTypes
              .Where(c => (c.IsActive))
              .OrderBy(c => c.CardType).ToList();
}

Load Months

Create a method named LoadMonths to build this collection of MonthInfo objects. Using the System.Globalization namespace you attempt to get the month names from the CultureInfo class in .NET. The DateTimeFormat property of the CultureInfo class contains a MonthNames collection with the localized month names for the culture specified by the language passed to the constructor of the CultureInfo class. If you pass a bad language specifier, an exception is thrown. You then use the value in the DefaultLanguage property. This DefaultLanguage property is filled in by the Web API controller with data retrieved from the Web.config file.

public void LoadMonths() {
  string[] monthNames = null;
  try {
    // Try to get month names
    monthNames = (new CultureInfo(Language))
                    .DateTimeFormat.MonthNames;
  }
  catch (CultureNotFoundException) {
    // Default to a known language
    monthNames = (new System.Globalization
                    .CultureInfo(DefaultLanguage))
                      .DateTimeFormat.MonthNames;
  }
  // Create Months Array
  for (int index = 0; index < monthNames.Length; index++) {
    // NOTE: Month array is 13 entries long
    if (!string.IsNullOrEmpty(monthNames[index])) {
      Months.Add(new MonthInfo(Convert.ToInt16(index + 1),
                               monthNames[index]));
    }
  }
  if (Entity.ExpMonth == 0) {
    // Figure out which month to select
    // Make it next month by default
    Entity.ExpMonth = Convert.ToInt16(DateTime.Now.Month + 1);
    Entity.ExpYear = Convert.ToInt16(DateTime.Now.Year);
    // If past December, then make it January of the next year
    if (Entity.ExpMonth > 12) {
      Entity.ExpMonth = 1;
      Entity.ExpYear += 1;
    }
  }
}

Once you have the array of month names, you need to turn these into a collection of MonthInfo objects. Loop through the array and each time through create a new instance of the MonthInfo class, setting the MonthNumber and the MonthName properties from the month names array.

The last thing to do is to default the ExpMonth and ExpYear to some default values. As you do not want your user to put in a month and year less than the current month and year, you add one to the current month and set that into the ExpMonth property. If you add one to the current month and it comes out to be 13, then increment the year by one and place that value into the ExpYear property. Otherwise, you just set the ExpYear to be the current year.

Load Years

The last method to create is to load the years into the Years property. The LoadYears method accepts a specified number of years in the future to load a generic list of integers. If you do not pass in any number, the default of 20 years is used.

public void LoadYears(int yearsInFuture = 20) {
  List<int> ret = new List<int>();
  Years = new List<int>();
  for (int i = DateTime.Now.Year; 
       i <= (DateTime.Now.Year + yearsInFuture); i++) {
    Years.Add(i);
  }
}

Build Web API Controllers

Now that you have a database design, some Entity Framework classes, and a view model to encapsulate the data you need for your credit card page, you are ready to expose that data through a web service. Right mouse click on the References folder in the CreditCardEntry project and select Add Reference. Add references to your two new projects; DataLayer and ViewModelLayer.

Credit Card Types Controller

Right mouse click on the Controllers folder and select Add | Web API Controller Class (v2.1). If this option does not appear in your drop down menu, select Add | New Item and choose it under the Web | Web API templates. Set the name of this controller to CreditCardTypeController. Remove all code within the class and write the following method.

public IHttpActionResult Get() {
  IHttpActionResult ret;
  CreditCardViewModel vm = new CreditCardViewModel();
  vm.LoadCardTypes();
  if (vm.CardTypes.Count() > 0) { 
    ret = Ok(vm.CardTypes);
  }
  else {
    ret = NotFound();
  }
  return ret;
}

This method creates an instance of your CreditCardViewModel class. Call the LoadCardTypes method to load the CardTypes collection in the view model. If card types are loaded, return the status code of 200, via the Ok method, passing in the card types collection as the payload. If no card types are found, then return a 404 using the NotFound method.

Month Names Controller

Right mouse click on the Controllers folder and select Add | Web API Controller Class (v2.1). Set the name of this controller to MonthNamesController. Remove all code within the class and write the following method.

public IHttpActionResult Get(string id) {
  IHttpActionResult ret;
  CreditCardViewModel vm = new CreditCardViewModel();
  // Set default language
  vm.DefaultLanguage =
    ConfigurationManager.AppSettings["DefaultLanguage"];
  // Set the language passed in
  vm.Language = (string.IsNullOrEmpty(id) ? 
                 vm.DefaultLanguage : id);
  vm.LoadMonths();
  if (vm.Months.Count() > 0) {
    ret = Ok(vm.Months);
  }
  else {
    ret = NotFound();
  }
  return ret;
}

This method is called from your Angular controller, but you need to pass in a parameter that is named id. You must use the parameter name of id since this is what the default route is expecting. The id parameter is the language code retrieved from the browser. By passing in the language you can let .NET return the month names in the appropriate language for your user. Call the LoadMonths method in your view model class and if months are loaded, return the months via the Ok method.

If no language is passed to this method, retrieve a default language from your <appSettings> section in the Web.config file and put it into the DefaultLanguage property of your view model. This default value will be used if no language is passed, or an unrecognized language is passed from the browser. Open your Web.Config file and add the DefaultLanguage key and a key for YearsInFuture. You will need the number of years in the next controller.

<appSettings>
  <add key="DefaultLanguage"
        value="en-US" />
  <add key="YearsInFuture"
        value="20" />
</appSettings>

Years Controller

Right mouse click on the Controllers folder and select Add | Web API Controller Class (v2.1). Set the name of this controller to YearsController. Remove all code within the class and write the following method.

public IHttpActionResult Get() {
  IHttpActionResult ret;
  CreditCardViewModel vm = new CreditCardViewModel();
  vm.LoadYears(
    Convert.ToInt32(
      ConfigurationManager.AppSettings["YearsInFuture"]));
      
  if (vm.Years.Count() > 0) { 
    ret = Ok(vm.Years);
  }
  else {
    ret = NotFound();
  }
  return ret;
}

This method retrieves the YearsInFuture setting from the Web.config file and passes that value to the LoadYears method in your view model class. The Years collection is filled with the number of years specified by the value passed in. If there are years in the Years collection, they are returned via the Ok method.

Call Web API to Load Drop-Down Lists

Now that you have the Web API calls built for loading the drop-down lists, you can now call these from your Angular controller. All the hard-coded functions you wrote in the previous blog post are going to be rewritten to call the appropriate Web API methods. You also need to add some exception handling to report any errors. Open the \app\creditcard\creditcard.controller.js file and start adding this new functionality.

Handle Exceptions

When you make calls to a Web API you should always make sure you are checking for exceptions. Write a generic handleException function to retrieve any error message information and place an object with a single property called message into the vm.uiState.messages array.

function handleException(error) {
  vm.uiState.isMessageAreaHidden = false;
  vm.uiState.isLoading = false;
  vm.uiState.messages = [];
  switch (error.status) {
    case 404:  // 'Not Found'
      vm.uiState.messages.push(
        {
          message: "The data you were " +
                    "requesting could not be found"
        });
      break;
    case 500:  // 'Internal Error'
      vm.uiState.messages.push(
        {
          message: error.data.exceptionMessage
        });
      break;
    default:
      vm.uiState.messages.push(
        {
          message: "Status: " +
                  error.status +
                  " - Error Message: " +
                  error.statusText
        });
      break;
  }
}

Load Card Types

In Part 1 of this blog post you hard coded a set of credit card types in the loadCardTypes function. Locate the loadCardTypes function in the creditcard.controller.js file and modify the code to look like the following.

function loadCardTypes() {
  dataService.get("/api/CreditCardType")
    .then(function (result) {
      vm.cardTypes = result.data;
      if (vm.cardTypes.length > 0) {
        vm.selectedCardType = vm.cardTypes[0];
      }
    },
    function (error) {
      handleException(error);
    });
}

In Part 1 of this blog post, the $http data service was passed into the controller. We assigned this service to the variable named dataService. Use the get() function of this data service to call the CreditCardType controller you created earlier to retrieve the credit card types from your SQL Server database. Once you retrieve the card type from the Web API, the result.data property is filled in with an array of JSON objects that represent each card type. Assign these values to the vm.cardTypes property because this is the property that is bound to the HTML <select> element that displays them to the user. Finally set the vm.selectedCardType property to the first element in the array in order to position the <select> element to that card type.

Load Month Names

The month names are still hard-coded in the loadMonths function. Locate the loadMonths function and replace the hard-coding with the code to call the Web API you created.

function loadMonths() {
  var today = new Date();
  // Get the language from the browser
  var language = 
     navigator.languages && 
     navigator.languages[0] || // Chrome / Firefox
     navigator.language ||     // All browsers
     navigator.userLanguage;   // IE <= 10
  dataService.get("/api/MonthNames/" + language)
    .then(function (result) {
      // Transform the data to use nn - monthName format
      for (var index = 0; 
               index < result.data.length; 
               index++) {
        var month = {
          monthNumber: index + 1,
          monthName: (index + 1).toString() 
            + "-" + result.data[index].monthName
        };
        vm.months.push(month);
      }
      // Figure out which month to select
      // Make it next month by default
      vm.creditCard.expMonth = today.getMonth() + 2;
      // If past December, make it January of next year
      if (vm.creditCard.expMonth > 12) {
        vm.creditCard.expMonth = 1;
        vm.creditCard.expYear = vm.creditCard.expYear + 1;
      }
      vm.selectedMonth = 
        vm.months[vm.creditCard.expMonth - 1];
      vm.uiState.isLoading = false;
    },
    function (error) {
      handleException(error);
    });
}

The call to the Web API is like the other calls you just wrote except you must pass in the current language the browser is running. The navigator object is queried to see which of the languages, language or the userLanguage properties contain a value. This is the value that you pass to the id parameter of the MonthNames controller. When the data is returned, loop through each object and convert the data to display in a nn-monthName format, for example; 1-January, 2-February, etc. The rest of the code is what you wrote in the first part of this blog post to set the default month and year of the drop-downs.

Load Years

The loadYears JavaScript function you wrote in the last blog post is almost the same as the code you wrote in the Years controller. Having the functionality to load years, months and credit card types in a web service gives us more flexibility rather than hard-coding everything in JavaScript files. Assign the return value from the Years API to the vm.years property as this is the property that is bound to the <select> element used to display the years.

function loadYears() {
  var year = new Date().getFullYear();
  dataService.get("/api/Years")
    .then(function (result) {
      vm.years = result.data;
      vm.creditCard.expYear = year;
    },
    function (error) {
      handleException(error);
    });
}

You can now run the sample and see all of the data coming from the Web API calls.

Summary

In this blog post you created the appropriate Web API calls to load each of the drop-down lists on your credit card data entry page. You built a set of Entity Framework classes to read the credit card types from a SQL Server table. You built three Web API controllers to be called from your Angular functions to load each of the drop-down lists. Finally, you replaced the hard-coded functions you wrote in the first blog post, with calls to the Web API to get all data from the back-end server. In the next blog post, you are going to take the credit card data entered by the user, validate that data, and then save that data into a SQL Server table.

Sample Code

You can download the code for this sample at www.pdsa.com/downloads . Choose the category "PDSA Blog", then locate the sample PDSA Blog Sample: Angular Credit Card Page – Part 2.

Build a Credit Card Entry Page using Angular – Part 1

A common page on many public websites is a page that asks a user to submit their credit card information. This seemingly simple little page has quite a few moving pieces in it. This series of blog posts illustrates how to build the HTML, Web API calls, a view model class, the Entity Framework objects, and the appropriate AngularJS controller to create a credit card entry page. Yes, I am still using AngularJS (or Angular 1) as opposed to Angular 2. The reason for this is I am finding that many developers are more familiar with JavaScript than with TypeScript and wish to stay with a language they know. There is nothing wrong with Angular 1, and thus no compelling reason to upgrade to Angular 2 if you don't want to.

In the first part of this blog post series you build the basic HTML for the credit card entry page. You also load the credit card types, months and years into drop-down lists on the page using hard-coded data. Succeeding blog posts will show how to build a Web API, Entity Framework classes, and a view model to support getting and storing credit card data from a set of SQL Server tables.

Overview of SPA Architecture

In this first blog post, you are going to layout the overall SPA pages needed to support the credit card data entry page. Figure 1 shows the two pages you are going to create for this system. The index.html page is like a Master Page in Web Forms or a Shared Layout page in MVC in that it contains the "chrome" for all the pages you route to. The only page in this post you are going to route to is the creditcard.html page, but the ng-view directive can be used for as many pages as you need in your application.

Figure 1: Overall architecture of SPA architecture

The creditcard.html page (shown in Figure 2) is where the user enters their credit card information. The index-splash.html page is displayed when the user first enters the system. If you have a ng-view directive, you must always have something displayed in it. If not, Angular will go into a recursive loop and eventually error out.

Figure 2: The Credit Card data entry page

Build the Main SPA Page

You are going to build a Single Page Application (SPA) to illustrate how to build the credit card page. This means you build a single index.html page from which you call the credit card page in your application. This sample will just contain the one credit card HTML page, but it is good to build your application using the SPA design pattern so you can add additional pages later.

Open Visual Studio 2015 and select File | New | Project. Choose Web under the Visual C# templates, then select ASP.NET Web Application (.NET Framework). Set the Name of the project to CreditCardEntry and click the OK button. Select the Empty template, and check the Web API check box before pressing the OK button as shown in Figure 3.

Figure 3: Select an Empty project template using the Web API

Using the NuGet Package Manager tool, install the packages needed for the complete sample. You are not going to use the Entity Framework yet, but in the next blog posts you will need this, so you might as well add it now.

  • EntityFramework
  • AngularJS.Core
  • AngularJS.Route
  • Bootstrap

Add a new HTML page in the root of your project called index.html. Modify the page to look like Figure 4 using the HTML shown in the listing below:

<!DOCTYPE html>
<html>
<head>
    <title>Credit Card Entry Sample</title>
    <meta charset="utf-8" />
    <link href="Content/bootstrap.min.css"
          rel="stylesheet" />
</head>
<body>
    <div class="container"
         ng-app="app"
         ng-controller="IndexController as vm">
    <div class="row">
    <div class="col-sm-10">
    <h1>Credit Card Entry System in Angular
        </h1>
      </div>
    </div>
    <div class="row">
    <div class="col-sm-10">
    <a href="#/creditcard"
       class="btn btn-primary">
          Credit Card Entry
        </a>
      </div>
    </div>
    <!-- Separator Line -->
    <div class="row">
    <div class="col-sm-10">
         
      </div>
    </div>
    <!-- ** BEGIN PARTIAL VIEWS AREA -->
    <div ng-view></div>
    <!-- ** END PARTIAL VIEWS AREA -->
  </div>
    <script src="scripts/angular.js"></script>
    <script src="scripts/angular-route.js"></script>
</body>
</html>

Towards the bottom of the page you see a <div> tag with an attribute of ng-view. This attribute tells Angular where you wish to insert partial pages within this page. The # sign as the first character in the <a> tag, followed by the /creditcard informs Angular you want to find an Angular route named "creditcard" and to load that partial page within the <div> tag with the ng-view attribute.

Figure 4: The main HTML page for your SPA

Modify the Global.asax.cs file to handle self-referencing in the Entity Framework and convert all pascal case property names to camel case. There is no self-referencing in this sample, but I find in many projects, this helps avoid some hard-to-track-down bugs.

protected void Application_Start() {
  GlobalConfiguration.Configure(WebApiConfig.Register);
  // Handle self-referencing in Entity Framework
  HttpConfiguration config =
    GlobalConfiguration.Configuration;
  config.Formatters.JsonFormatter
    .SerializerSettings.ReferenceLoopHandling =
       Newtonsoft.Json.ReferenceLoopHandling.Ignore;
  // Convert to camelCase
  var jsonFormatter = config.Formatters
     .OfType<JsonMediaTypeFormatter>().FirstOrDefault();
  jsonFormatter.SerializerSettings.ContractResolver = 
     new CamelCasePropertyNamesContractResolver();
}

Build Angular Folder and JavaScript Files

A popular style for laying out Angular applications is to create a \app folder in your project and place all your HTML and JavaScript files in this folder. Group your files into folders with the name of the page you are working on. There are two Angular-controlled pages in this sample; the index page and the credit card page. Create a folder structure like the one shown in Figure 5.

Figure 5: Create your folder structure

Build Index Page JavaScript Files

In the \app\index folder add a new JavaScript file named index.module.js. This is where you create your Angular module that matches the name in the ng-app attribute of the <div> tag in your index.html page. As you are going to be using Angular routing include the 'ngRoute' module dependency.

(function () {
  'use strict';
  angular.module('app', ['ngRoute']);
})();

Create another JavaScript file named index.controller.js within the \app\index folder. In this file is where you define the Angular controller for this page. This page has no functionality other than to redirect to other pages, thus there is no code in this controller function.

(function () {
  'use strict';
  angular.module('app').controller('IndexController',
    IndexController);
  function IndexController() {
  }
})();

Add one more JavaScript file to the \app\index folder named index.route.js. This file defines the various routes that you use in anchor tags on your index.html page.

(function () {
  'use strict';
  // Create angular routing
  angular.module('app')
    .config(['$routeProvider', function ($routeProvider) {
      $routeProvider
      .when('/',
      {
        templateUrl: 'app/index/index-splash.html',
        controllerAs: 'vm',
        controller: 'IndexController'
      })
      .when('/creditcard',
      {
        templateUrl: 'app/creditcard/creditcard.html',
        controllerAs: 'vm',
        controller: 'CreditCardController'
      })
      .otherwise(
      {
        redirectTo: '/'
      });
    }]);
})();

Each route is defined using the when() function. You pass to the when() function the route defined in an anchor tag, or to call using Angular location services. You also pass in an object that defines either some inline HTML or an HTML template to display within the ng-view area. You may optionally define the controller name and a 'controllerAs' property to name each controller reference.

The default route, defined as when('/'), tells Angular to redirect back to index.html. You must define either some HTML, or an HTML template to display within the ng-view area when someone requests a route or Angular gets caught in a loop which eventually causes an error. Personally, I like to define a little "splash" page to display some opening remarks to the user. Create a new HTML page in the \app\index folder named index-splash.html. Strip all HTML from the page that is added, and just add this one line to the file.

<p>This sample illustrates a credit card entry system.</p>

Open the index.html page and add the following <script> tags below the angular script tags.

  <script src="scripts/angular.js"></script>
  <script src="scripts/angular-route.js"></script>

  <script src="app/index/index.module.js"></script>
  <script src="app/index/index.controller.js"></script>
  <script src="app/index/index.route.js"></script>
</body>
</html>

You should be able to run the main index page. Don't click on the button as it is not hooked up yet.

Build the Credit Card HTML

Add an HTML page to the \app\creditcard folder named creditcard.html. This is a partial page, so we don't need any of the normal HTML tags, so go ahead and delete all HTML in this page. You only need to write the HTML to be loaded within the <div> tag with the ng-view attribute. The code shown below is not the final code, but is just the initial layout for the credit card HTML page.

<div class="row">
  <div class="col-sm-8">
    <form name="creditCardForm" novalidate>
      <div class="panel panel-default">
        <div class="panel-heading">
          <h3 class="panel-title">
            Credit Card Information
          </h3>
        </div>
        <div class="panel-body">
        </div>
        <div class="panel-footer">
          <div class="row">
            <div class="col-xs-12">
              <div class="pull-right">
                <button type="button"
                    class="btn btn-primary"
                    ng-click=
                      "vm.saveClick(creditCardForm)">
                  <i class="glyphicon 
                            glyphicon-floppy-disk">
                  </i>
                  &nbsp;Save
                </button>
                <a class="btn btn-primary"
                   formnovalidate="formnovalidate"
                   href="#/">
                  <i class="glyphicon 
                            glyphicon-remove-circle">
                  </i>
                  &nbsp;Cancel
                </a>
              </div>
            </div>
          </div>
        </div>
      </div>
    </form>
  </div>
</div>

The credit card page is created within a Bootstrap row and a column. Add a <form> tag to encapsulate the input fields for the credit card data. Next, build the structure of the Bootstrap panel control into which all the messages and input fields will reside. The messages and input fields will be placed into the panel body. In the footer of the panel control you define a save and cancel button.

Add Credit Card Controller

For each partial web page, you need an Angular controller that goes with it. Add a JavaScript file in the \app\creditcard folder named creditcard.controller.js. Add the following code to this script file.

(function () {
  "use strict";
  angular.module("app")
    .controller("CreditCardController", CreditCardController);
  function CreditCardController($http, $location) {
    var vm = this;
    var dataService = $http;
    // Create UI state object
    vm.uiState = {
      isMessageAreaHidden: true,
      isLoading: true,
      messages: []
    };
  }
})();

In the above function the $http and $location services are injected into our controller. A common practice is to assign each of the services to a local variable. Assign the $scope from Angular to the variable vm, and the $http service to a variable named dataService.

The object in the credit card controller name uiState is used to hold properties that affect the user interface in some manner. The isMessageAreaHidden property turns on and off a Bootstrap alert area used to display messages to the user. The isLoading property determines whether to display a "Please wait while loading" message to the user when they first enter the web page or not. The messages array holds a collection of message objects that are displayed in the message area on the screen.

Error Message Area

Within the body of the panel control create a <div> tag to display error and validation messages to the user. This functionality for displaying messages will be added in a future post. Messages you add to the messages array you create in the controller are displayed within an unordered list. You can see the ng-repeat attribute is used to loop through each message in the array and display any values set in a message property within an <li> element. Within the <div class="panel-body"> add the HTML shown below.

<!-- ** BEGIN MESSAGE AREA ** -->
<div ng-hide="vm.uiState.isMessageAreaHidden ||
              (creditCardForm.$valid &&
               !creditCardForm.$pristine)"
     class="row">
  <div class="col-xs-12">
    <div class="alert alert-danger 
                alert-dismissable" 
         role="alert">
      <button type="button" class="close"
              data-dismiss="alert">
        <span aria-hidden="true">
          &times;
        </span>
        <span class="sr-only">Close</span>
      </button>
      <ul>
        <li ng-repeat="msg in vm.uiState.messages">
          {{msg.message}}
        </li>
      </ul>
    </div>
  </div>
</div>
<!-- ** END MESSAGE AREA ** -->

The <div> within the panel body uses the Angular ng-hide attribute to hide this area based on a few different flags. The isMessageAreaHidden property in the vm.uiState object in your controller is set by you depending on whether or not there are validation messages to be displayed. You check two other properties on the creditCardForm in addition to the isMessageAreaHidden property. If the creditCardForm is valid is true and pristine property is false, then the message area will be hidden.

Loading Message Area

Add another <div> tag just after the message area you created. This <div> tag uses the Angular ng-show attribute so this area is only displayed when a property in your controller, vm.uiState.isLoading, is set to a true value. When this page is first loaded, all the drop-down lists need to be loaded. Calling the Web API to get this data can take a couple of seconds on the first load, so it is a good idea to display a "Please wait while loading" message to the user before you display any other user interface items to them.

<!-- ** BEGIN LOADING MESSAGE AREA ** -->
<div class="row" ng-show="vm.uiState.isLoading">
  <div class="col-sm-offset-1 col-sm-10 
              alert alert-warning">
    <div class="text-center">
      Please wait while loading 
         credit card information...
    </div>
  </div>
</div>
<!-- ** END LOADING MESSAGE AREA ** -->

Input Fields

After the loading message area above, add another <div> element which is hidden until the vm.uiState.isLoading property is set to a false value. In the controller, this property is initially set to a true value. After all the drop-down lists have been loaded, this property is set to false. At that time the loading message will be hidden and the user input fields within this <div> tag will be displayed. Below is the HTML for each of the input fields required for the credit card data. You have not added the Angular binding yet, but will do that later in this post.

<!-- ** BEGIN CREDIT CARD ENTRY AREA ** -->
<div ng-hide="vm.uiState.isLoading">
  <div class="row">
    <div class="form-group col-sm-6">
      <label for="types">Select Credit Card Type
      </label>
      <select id="types"
         name="types"
         class="form-control">
      </select>
    </div>
  </div>
  <div class="form-group">
    <label for="nameOnCard">Name on Card</label>
    <input id="nameOnCard"
           name="nameOnCard"
           class="form-control"
           placeholder="Name on Card"
           title="Name on Card"
           type="text" />
  </div>
  <div class="row">
    <div class="form-group col-sm-8">
      <label for="cardNumber">Credit Card Number
      </label>
      <input id="cardNumber"
              name="cardNumber"
              class="form-control"
              placeholder="Credit Card Number"
              title="Credit Card Number"
              type="text" />
    </div>
    <div class="form-group col-sm-4">
      <label for="securityCode">Security Code
      </label>
      <input id="securityCode"
              name="securityCode"
              class="form-control"
              placeholder="Security Code"
              title="Security Code"
              type="text" />
    </div>
  </div>
  <div class="row">
    <div class="form-group col-sm-8">
      <label for="expMonths">Exp. Month</label>
      <select id="expMonths"
        name="expMonths"
        class="form-control">
      </select>
    </div>
    <div class="form-group col-sm-4">
      <label for="expYears">Exp. Year</label>
      <select id="expYears"
              name="expYears"
              class="form-control">
      </select>
    </div>
  </div>
  <div class="row">
    <div class="form-group col-sm-6">
      <label for="billingPostalCode">
        Billing Postal Code
      </label>
      <input id="billingPostalCode"
        name="billingPostalCode"
        class="form-control"
        placeholder="Billing Postal Code"
        title="Billing Postal Code"
        type="text" />
    </div>
  </div>
</div>
<!-- ** END CREDIT CARD ENTRY AREA ** -->

Open the index.html page and add the following <script> tag below the other script tags you added earlier.

  <script src="scripts/angular.js"></script>
  <script src="scripts/angular-route.js"></script>
  <script src="app/index/index.module.js"></script>
  <script src="app/index/index.controller.js"></script>
  <script src="app/index/index.route.js"></script>

  <script src="app/creditcard/creditcard.controller.js">
  </script>
</body>
</html>

You should be able to run the main index page and click on the Credit Card Entry button to display your credit card page. The page will show the "Loading" message because the isLoading property is set to true. To see the input fields, go into the creditcard.controller.js file, change the isLoading property to false, and rerun the page. If you do this, be sure to set it back to true once you stop the web application.

Credit Card Controller

Let's add more of the properties to the CreditCardController function that you are going to require for the credit card page. You need array properties to load the drop-down lists. You need objects for selecting a credit card type and a month, and you need an object to hold properties for each input field. In addition, you need properties to turn on and off the various message areas on the page. Add the additional code shown below to your creditcard.controller.js file.

function CreditCardController($http, $location) {
  var vm = this;
  var dataService = $http;
  // Expose public properties
  vm.cardTypes = [];
  vm.months = [];
  vm.years = [];
  vm.selectedCardType = {};
  vm.selectedMonth = {};
  vm.creditCard = {
    creditCardId: null,
    cardType: null,
    nameOnCard: null,
    cardNumber: null,
    securityCode: null,
    expMonth: null,
    expYear: null,
    billingPostalCode: null
  };
  vm.uiState = {
    isMessageAreaHidden: true,
    isLoading: true,
    messages: []
  };
  // Initialize Controller
  loadCardTypes();
  loadYears();
  loadMonths();
  // Load Credit Card Types
  function loadCardTypes() {
  
  }
  // Load years
  function loadYears() {
    
  }
  // Load months
  function loadMonths() {
     
  }
}

The three arrays in the scope for this controller hold the data to go into the three drop-down lists on the credit card page. The cardTypes array and month array are both object arrays. The years array is an array of integer values representing a year the user can select from.

vm.cardTypes = [];
vm.months = [];
vm.years = [];

When the user selects a value from a drop-down that is bound to an object, you can bind to another object in your controllers' scope. Create two additional properties to bind to for the month and card type.

vm.selectedMonth = {};
vm.selectedCardType = {};

The next object you create is one that holds the data for each input element on the screen. This object is named creditCard and is added to the scope just like the others. Within the creditCard object define a property to map to each input element.

vm.creditCard = {
  creditCardId: null,
  cardType: null,
  nameOnCard: null,
  cardNumber: null,
  securityCode: null,
  expMonth: null,
  expYear: null,
  billingPostalCode: null
};

Load Mock Data for Drop-Down Lists

Looking at Figure 2 you know you need to load the three drop-down lists for credit card types, months and years. Let's finish building the routines you added to the creditCard controller to load the data into the properties of our controller. For this first sample you are going to hard-code the various values. In a later post you learn to connect to a back-end through a Web API to retrieve credit card type data from a SQL Server database table, and to load the months and years.

Load Credit Card Types

Open up the \app\creditcard\creditcard.controller.js file and locate the loadCardTypes() function. Modify the code to look like the code shown below:

function loadCardTypes() {
  vm.cardTypes.push({ cardType: 'Visa' });
  vm.cardTypes.push({ cardType: 'MasterCard' });
  vm.cardTypes.push({ cardType: 'American Express' });
  vm.cardTypes.push({ cardType: 'Discover' });
  vm.selectedCardType = vm.cardTypes[0];
}

In the above code push a new object with a single property onto the cardTypes array. The property is called cardType and you specify a value to display in the drop-down list on the HTML page. Feel free to add as many different card types as you wish to this list. Lastly, take the first cardType object and assign it to the selectedCardType property in your scope.

When you build the <select> HTML element you are going to use two Angular attributes; ng-model and ng-options. The ng-model attribute binds to the vm.selectedCardType property in the CreditCardController. The ng-options attribute specifies how to load the <select> element. Go to the creditcard.html page, locate the types <select> element and add these two attributes.

<select id="types"
        name="types"
        class="form-control"
        ng-model="vm.selectedCardType"
        ng-options="item.cardType for item in vm.cardTypes 
                    track by item.cardType">
</select>

You can think of the ng-options attribute like a foreach loop in C#. Here would be the equivalent pseudo-code in C# for what the ng-options attribute is doing.

foreach (item in vm.cardTypes) {
  <option value=”item.cardType”>item.cardType</option>
}

Let's break down each piece of the ng-options attribute value. The "for item" defines a local variable with the name of "item". The "in vm.cardTypes" specifies the property in the scope of your controller to retrieve the collection of data from. The "item.cardType" before the "for item" is the name of the property within each object in the cardType array you wish to display in the text portion of the drop-down. The "track by item.cardType" is the property from which to retrieve the value to put into the value portion of each <option> element within the <select>.

Load Years

I am seeing more and more credit card forms asking for a year up to 20 years in the future. So, for this sample, you load 20 years into the vm.years array. This array is simply an array of integer values. No object is necessary for each element in this array.

function loadYears() {
  var year = new Date().getFullYear();
  for (var i = 0; i < 20 ; i++) {
    vm.years.push((year + i));
  }
  vm.creditCard.expYear = year;
}

Go to the creditcard.html page, locate the expYears <select> element and add these two attributes.

<select id="expYears"
        name="expYears"
        class="form-control"
        ng-model="vm.creditCard.expYear"
        ng-options="item for item in vm.years track by item">
</select>

The ng-model attribute binds directly to the vm.creditCard.expYear property in the CreditCardController. Since each item in the years array is just an integer, that integer value is assigned to the expYear property.

The ng-options attribute uses just the variable name 'item' to bind to the text portion of the drop-down and to the value portion. Since each element is just an integer value, you do not specify any property name within 'item', you just use 'item' itself.

Load Months

The loadMonths() function is similar to the loadCardTypes() function in that you are creating an object to load into the vm.months array. The month object contains two properties; monthNumber and monthName. The monthNumber property will be used in the value portion of the drop-down list, while the monthName property will be used in the text portion of the drop-down.

After loading the objects, set the expYear and expMonth of the vm.creditCard object to the next month, or January if the current month is 12, and to either the current year, or the next year if the current month is 12. Below is the code to load the months and set the expYear and expMonth.

function loadMonths() {
  var today = new Date();
  vm.months.push({ monthNumber: 1, monthName: 'January' });
  vm.months.push({ monthNumber: 2, monthName: 'February' });
  vm.months.push({ monthNumber: 3, monthName: 'March' });
  vm.months.push({ monthNumber: 4, monthName: 'April' });
  vm.months.push({ monthNumber: 5, monthName: 'May' });
  vm.months.push({ monthNumber: 6, monthName: 'June' });
  vm.months.push({ monthNumber: 7, monthName: 'July' });
  vm.months.push({ monthNumber: 8, monthName: 'August' });
  vm.months.push({ monthNumber: 9, monthName: 'September' });
  vm.months.push({ monthNumber: 10, monthName: 'October' });
  vm.months.push({ monthNumber: 11, monthName: 'November' });
  vm.months.push({ monthNumber: 12, monthName: 'December' });
  // Figure out which month to select
  // Make it next month by default
  vm.creditCard.expMonth = today.getMonth() + 2;
  // If past December, then make it January of the next year
  if (vm.creditCard.expMonth > 12) {
    vm.creditCard.expMonth = 1;
    vm.creditCard.expYear = vm.creditCard.expYear + 1;
  }
  vm.selectedMonth = vm.months[vm.creditCard.expMonth - 1];
  // Set the page UI flag as not loading anymore
  vm.uiState.isLoading = false;
}

Go to the creditcard.html page, locate the expMonths <select> element and add these two attributes.

<select id="expMonths"
        name="expMonths"
        class="form-control"
        ng-model="vm.selectedMonth"
        ng-options="item.monthName for item 
                    in vm.months 
                    track by item.monthNumber">
</select>

Just like for the card types, this <select> element is binding to the selectedMonth object in your controller.

The last thing the loadMonths function does is to set the isLoading property of the vm.uiState object to false. If you were calling the Web API in each of the above functions, these calls might take a little time. In the HTML page display a "Please wait while loading…" message to the user until the isLoading property is set to false. This is where that property is set to false, which makes the message disappear.

Bind Credit Card Input Fields

With your controller built and a creditCard object with properties to bind to the input fields, the only thing left to do is to perform the binding. Add a hidden input field directly below the <form> tag in your HTML. This hidden field is for the primary key value for the credit card record you add to a SQL Server database table. Initially it will be null, but after you add a record this value will be filled in and passed back from the Web API.

<input type="hidden" 
       ng-model="vm.creditCard.creditCardId" />

Locate the nameOnCard input field and bind to the creditCard object by adding the following ng-model attribute.

<input id="nameOnCard"
       name="nameOnCard"
       ng-model="vm.creditCard.nameOnCard"
       class="form-control"
       placeholder="Name on Card"
       title="Name on Card"
       type="text" />

Locate the cardNumber input field and bind to the creditCard object by adding the following ng-model attribute.

<input id="cardNumber"
       name="cardNumber"
       ng-model="vm.creditCard.cardNumber"
       class="form-control"
       placeholder="Credit Card Number"
       title="Credit Card Number"
       type="text" />

Locate the securityCode input field and bind to the creditCard object by adding the following ng-model attribute.

<input id="securityCode"
       name="securityCode"
       ng-model="vm.creditCard.securityCode"
       class="form-control"
       placeholder="Security Code"
       title="Security Code"
       type="text" />

Locate the billingPostalCode input field and bind to the creditCard object by adding the following ng-model attribute.

<input id="billingPostalCode"
       name="billingPostalCode"
       ng-model="vm.creditCard.billingPostalCode"
       class="form-control"
       placeholder="Billing Postal Code"
       title="Billing Postal Code"
       type="text" />

At this point you can run the sample and you should see the credit card page displayed with the appropriate data in the drop-down lists.

Summary

In this blog post you created a new empty web application in Visual Studio, added Angular and Bootstrap to display a credit card page. You built a few different JavaScript files to build an Angular module and routes for the SPA. You then built a credit card controller which loads data into arrays for displaying in drop-down lists on the credit card page. You also setup the appropriate HTML to display error messages, a loading message, and the various input fields for accepting credit card information from a user. In the next post you will learn to get data from the Web API instead of using hard-coded data.

Sample Code

You can download the code for this sample at www.pdsa.com/downloads . Choose the category "PDSA Blog", then locate the sample PDSA Blog Sample: Angular Credit Card Page – Part 1.