About
This project presents an ASP.NET Web API Checking Account Register Service Application that demos some CRUD commands (post, get) operating on data in the web application while serializing/deserializing to an XML Datastore. Also, a client “tester” console application tests the API requests to add and retrieve checking account data with the HTTP protocol.
Description
This project emulates a checking account register that keeps tracks of your credits, debits, fees, and all transactions in one ledger.
Architecture
The demo project consists of these component topics:
- NET Web API Application
- Update-to-Date Web API Components
- XML Datastore
- Web API Controller with Add & Get Methods/Actions
- Models Class Library and XML Serializable Attributes (Shared)
- Utilities Class Library (Shared)
- Web API Client “Tester to Service” Console Application
- Post & Get Operations Testing using HTTP
Models Class Library
The “Models” project in the Visual Studio solution is a common class library shared amongst the service and client. The common models help the service provide details on how a client can best consume the data in its object-orientated architecture. The client also uses the common Models library to know how to build type-safe objects to send to the service so that it can process the requests correctly.
Data Architecture
The Checking Account Register service stores and retrieves records of data that represent money transactions of its customer. The Transaction object represents a general record of this money transaction. A collection of Transaction entities is stored in a TransactionList object that is an enumerable list and inherits from List<Transaction>. However, the Transaction record is further specified by a Credit or Debit type of transaction. The Credit and Debit objects inherit from the Transaction class which house common properties (ex: date, amount, etc..) common amongst the Credit and Debit child objects. Lastly, there are enumerations for both the Credit and Debit objects that organize the possible transaction categories (ex: cash, check, atm, etc.).
Data Models with XML Serializable Attributes
Transaction
The Transaction class data model with XML attributes for serialization is shown below. The properties are self-explanatory by names. The Transaction class data model file is available in the GitHub repo here.
The Transaction class also implements the IComparable<Transaction> and IComparer<Transaction> interfaces and sorts its objects with the criteria:
- Date (in ascending order)
- Transaction Type (Credits first, then Debits)
- Amount (in ascending order)
- Description (in ascending order)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
/// <summary> /// Describes a Transaction object /// </summary> [KnownType(typeof(Credit))] [KnownType(typeof(Debit))] [XmlInclude(typeof(Credit))] [XmlInclude(typeof(Debit))] [XmlType(TypeName = "transaction")] public class Transaction: IComparable<Transaction>, IComparer<Transaction> { #region Properties // Date of the transaction [XmlElement(ElementName = "date")] public DateTime Date { get; set; } // Transaction description [XmlElement(ElementName = "description")] public string Description { get; set; } // Transaction amount(positive value for deposits, negative for withdrawals) [XmlElement(ElementName = "amount")] public decimal Amount { get; set; } #endregion Properties #region Implement Comparison Interfaces for Sorting /// <summary> /// CompareTo /// Describes how an object should be compared to another /// for the purposes of sorting objects in our lists /// The sort criteria should be: /// Date(in ascending order) /// Transaction Type(Credits first, then Debits) /// Amount(in ascending order) /// Description(in ascending order) /// </summary> /// <param name="other">(Transaction) object to compare to this instance object</param> /// <returns>-1 if this instance precedes other, 0 if equal, 1 if other precedes this instance</returns> public int CompareTo(Transaction other) { // Compare Null First if (this == null && other == null) { return 1; } else if (this == null) { return -1; } else if (other == null) { return 1; } else { // Sort by Date First, Dates, Must be in Ascending Order if (this.Date.CompareTo(other.Date) == 0) { // We are Equal Continue to Compare by Transaction Type // (Credits first, then Debits) if((this is Credit) && (other is Debit)) { return -1; } // end of if else if ((this is Debit) && (other is Credit)) { return 1; } // end of else if // Both of the objects are equal else { // Sort by Amount(in ascending order) if (this.Amount.CompareTo(other.Amount) == 0) { // Both Amounts are Equal // Sort by Description(in ascending order) return this.Description.CompareTo(other.Description); } // end of if else { return this.Amount.CompareTo(other.Amount); } // end of else } // end of else } // end of if else { // Early date is prioritized return this.Date.CompareTo(other.Date); } // end of else } // end of else } // end of method /// <summary> /// Compare /// Describes how an object should be compared to another /// for the purposes of sorting objects in our lists /// The sort criteria should be: /// Date(in ascending order) /// Transaction Type(Credits first, then Debits) /// Amount(in ascending order) /// Description(in ascending order) /// </summary> /// <param name="x">(Transaction) object to be compared to y</param> /// <param name="y">(Transaction) object to be compared from x</param> /// <returns>-1 if x precedes y, 0 if equal, 1 if y precedes x</returns> public int Compare(Transaction x, Transaction y) { // Shortcut here return x.CompareTo(y); } // end of method #endregion Implement Comparison Interfaces for Sorting } // end of class |
TransactionList
The TransactionList class data model with XML root attributes is shown below. The Load() method deserializes from the xml file into memory all the Transaction objects into a TransactionList object which inherits from List<Transaction> and returns the entire Datastore memory object to the caller. Note: The Deserialization method is static and called on the class level.
Serialization is done by the Save() method, where the caller is an instance object of this class type TransactionList containing the entire in memory Datastore of Transaction objects. The caller will be a TransactionList object that contains a List of Transaction objects per the class definition. Serialization writes its own in-memory Datastore of the instance object to a specified file location. The TransactionList class data model file is available in the GitHub repo here.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
/// <summary> /// Describes the TransactionList /// Enuemerable List of type Transaction objects /// objects of this type can hold items of type Transaction, Debit and Credit /// Represents the checking register as a whole /// Balance of the account is not stored as a physical entity. /// Rather, it is calculated on the fly based on the contents of the TransactionList /// </summary> [XmlRoot(ElementName = "transactions")] public class TransactionList : List<Transaction> { #region Implement XML Serialization and Deserialization /// <summary> /// Load /// Loads data fom an xml file into memory /// </summary> /// <param name="filename">(string) name of file to load xml</param> /// <returns></returns> public static TransactionList Load(string filename) { TransactionList list = new TransactionList(); XmlSerializer ser = new XmlSerializer(typeof(Models.TransactionList)); using (StreamReader reader = new StreamReader(filename)) { if (reader != null) { list = ser.Deserialize(reader) as Models.TransactionList; } } // Sort the list per requirements list.Sort(); return list; } // end of Load method /// <summary> /// Save /// Saves and writes the data to XML file /// </summary> /// <param name="filename">(string) name of file to write data</param> public void Save(string filename) { XmlSerializer ser = new XmlSerializer(typeof(Models.TransactionList)); using (StreamWriter writer = new StreamWriter(filename)) { ser.Serialize(writer, this); } } // end of Save method #endregion Implement XML Serialization and Deserialization } // end of class |
Credit
The Credit data model class inherits from the Transaction data model class common properties along with an enumeration CreditTypeEnum describing the type of credit. In the demo application, a transaction will either be type of Credit or Debit, not both, and not a generic Transaction object. The Credit class data model file is available in the GitHub repo here.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/// <summary> /// Class Describes a Credit Transaction /// </summary> [XmlType(TypeName = "credit")] public class Credit : Transaction { /// <summary> /// Type of credit being applied to the account. /// These enum values can be used as the description /// for the transaction, unless the value is “Unknown”, /// in which case the user should type in a value. /// </summary> [XmlElement(ElementName = "credittype")] public CreditTypeEnum CreditType { get; set; } } // end of class |
CreditTypeEnum
The CreditTypeEnum is a simple enumeration for description credit type transactions (ex: cash) and will automatically serialize/deserialize without explicit XML attributes. The CreditTypeEnum class data model file is available in the GitHub repo here.
1 2 3 4 5 6 7 8 9 10 11 12 |
/// <summary> /// Enumeration for Credit Type Transactions /// </summary> public enum CreditTypeEnum { Unknown = 0, Check = 1, Cash = 2, MoneyOrder = 3, Wire = 4, AutomaticDeposit = 5 } // end of enum |
Debit
The Debit data model class inherits from the Transaction data model class common properties along with an enumeration DebitTypeEnum describing the type of debit, check number, and fee. In the demo application, a transaction will either be type of Credit or Debit, not both, and not a generic Transaction object. The Debit class data model file is available in the GitHub repo here.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
/// <summary> /// Describes Debit Transaction objects /// </summary> [XmlType(TypeName = "debit")] public class Debit : Transaction { /// <summary> /// Type of debit being applied to the account. These enum values can be used /// as the description for the transaction, unless the value is “Unknown”, in /// which case the user should type in a value. /// </summary> [XmlElement(ElementName = "debittype")] public DebitTypeEnum DebitType { get; set; } /// <summary> /// Check number, if applicable /// </summary> [XmlElement(ElementName = "checkno")] public int CheckNo { get; set; } /// <summary> /// Fee amount, if any, for this transaction /// </summary> [XmlElement(ElementName = "fee")] public decimal Fee { get; set; } } // end of class |
DebitTypeEnum
The DebitTypeEnum is a simple enumeration for description Debit type transactions (ex: Check) and will automatically serialize/deserialize without explicit XML attributes. The DebitTypeEnum class data model file is available in the GitHub repo here.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/// <summary> /// Enumeration for Debit Type Transactions /// </summary> public enum DebitTypeEnum { Unknown = 0, Check = 1, Cash = 2, ATM = 3, AutomaticPayment = 4, debitCard = 5, FundsTransfer = 6 } // end of enum |
ASP.NET Web API Application
This project demos the ASP.NET Web API portion only, not the MVC. I did not build a fancy front-end webpage for the user interface. However, the project did build help documentation for the Web API service from the XML comments in the controller code. The API help document page can be access from the ASP.NET Web API main page and clicking on “help” from the main menu (see Demo section). See the entire ASP.NET Web API Application project code directory in the GitHub repo here.
Update-to-Date Web API Components
Packages from Microsoft.AspNet.WebApi were installed since the Web API updates are released out of band from the .Net framework. The Web API application should be deployed with the most recent and complete Web API package so that the project is not dependent on the localized .Net framework code of Web API, wherever it is hosted. Note: Use the package manager to update the installed packages before a deployment. See the project package code directory in the GitHub repo here.
XML Datastore
This data for this demo project is serialized/deserialized with the simple file “transactions.xml” stored in the App_Data directory of the web application project. To see it in the solution explorer you may need to click on the “Show All Files”. The file’s Build Action property, is set as “Content”. See the section “Web API Controller with CRUD methods/actions” below, to learn how the controller serialized/deserialized to this XML file. See the demo transactions.xml file in the GitHub repo here.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<?xml version="1.0" encoding="utf-8"?> <transactions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <transaction xsi:type="credit"> <date>2015-01-02T00:00:00</date> <description>Child Support</description> <amount>212.75</amount> <credittype>AutomaticDeposit</credittype> </transaction> <transaction xsi:type="debit"> <date>2015-01-02T00:00:00</date> <description>Electric company</description> <amount>55.47</amount> <debittype>Check</debittype> <checkno>1001</checkno> <fee>0</fee> </transaction> <transaction xsi:type="debit"> <date>2015-01-02T00:00:00</date> <description>Gas company</description> <amount>155.47</amount> <debittype>Check</debittype> <checkno>1002</checkno> <fee>2.01</fee> </transaction> <transaction xsi:type="debit"> <date>2015-01-02T00:00:01</date> <description>Mortgage</description> <amount>1355.47</amount> <debittype>FundsTransfer</debittype> <checkno>0</checkno> <fee>0.25</fee> </transaction> <transaction xsi:type="credit"> <date>2015-01-09T00:00:00</date> <description>UCSD Paycheck</description> <amount>2512.75</amount> <credittype>AutomaticDeposit</credittype> </transaction> </transactions> |
Web API Controller with Get and Post CRUD Methods/Actions
The TransactionsController class file is available in the GitHub repo here. It is specifically an ApiController and very specific to the Web API engine and implements the API methods or actions that a client requested.
The Web API supports four types of Get (Read) requests:
- Get All Transactions
- Get Transaction in a Date Range
- Get Credit Transactions by Type
- Get Debit Transactions by Type
Also, it supports two types of Post (add) requests:
- Add Credit Transaction
- Add Debit Transaction
FilePath
The FilePath property stores the file name and location of the Datastore xml file where data is to be serialized/deserialized. For this demo project, it is stored in the App_Data directory of the web application project. The Controller calls the TransactionList static class method to deserialize into an instance object and also calls upon an instance object method to serialize to file (see the TransactionList data model above).
1 2 3 4 5 6 7 8 9 10 |
/// <summary> /// Stores filepath where server stores data locally /// </summary> private string FilePath { get { return HttpContext.Current.Server.MapPath("~/App_Data/transactions.xml"); } } |
Get “Read” All Transactions
This method loads all transactions from the memory store into a TransactionList object (List<Transaction>), sorts the list per the implemented interface on the Transaction object, and returns to the caller.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
/// <summary> /// Returns a list of all transactions in the register /// in sorted order /// </summary> /// <returns>(TransactionList) a Transaction List type object /// (list that holds transaction objects)</returns> public TransactionList GetAllTransactions() { TransactionList data = TransactionList.Load(FilePath); // Check to see if we have results if (data.Count > 0) { // Sort the Results data.Sort(); // Return the Results return data; } else { return null; //throw new HttpResponseException(HttpStatusCode.NoContent); } } // end of method |
Get “Read” All Transactions in Date Range
This method loads all transactions from the memory store into a TransactionList object (List<Transaction>), performs a LINQ query with the supplied input Date parameters, sorts the list per the implemented interface on the Transaction object, and returns to the caller.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
/// <summary> /// Gets a list of transactions that are between the given /// dates(inclusively). Items should be sorted according /// to defined rules. /// </summary> /// <param name="start">(DateTime) start time period</param> /// <param name="end">(DateTime) end time period</param> /// <returns>(TransactionList) a Transaction List type object /// (list that holds transaction objects)</returns> public TransactionList GetTransactionsByDateRange(DateTime start, DateTime end) { TransactionList data = TransactionList.Load(FilePath); // Pick items from the list that meet the criteria IEnumerable<Transaction> dataPicked = data.Where(s => (s.Date >= start) && (s.Date <= end)); // make a new list for output TransactionList dataResults = new TransactionList(); // Put the enuerable items in a Transaction List // The casting to TransactionList did not work foreach(Transaction item in dataPicked) { dataResults.Add(item); } // Check to see if we have results if (dataResults.Count > 0) { // Sort the Results dataResults.Sort(); // Return the Results return dataResults; } else { return null; //throw new HttpResponseException(HttpStatusCode.NoContent); } } // end of method |
Get “Read” All Credit Transactions
This method loads all transactions from the memory store into a TransactionList object (List<Transaction>), and performs a LINQ query to obtain first the Credit type transactions and then those with the Credit Enumeration matching the query input parameter. It then sorts the list per the implemented interface on the Transaction object, and returns to the caller.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
/// <summary> /// Gets a list of credit transactions that are of the given type. /// Items should be sorted according to defined rules. /// </summary> /// <param name="creditType">(CreditTypeEnum) type of credit transaction</param> /// <returns>(TransactionList) a Transaction List type object /// (list that holds transaction objects)</returns> public TransactionList GetCreditsByType(CreditTypeEnum creditType) { TransactionList data = TransactionList.Load(FilePath); // Pick items from the list that meet the criteria of being a Credit IEnumerable<Transaction> dataCredits = data.Where(s => (s is Credit)); // Pick items from the list that meet the criteria of matching creditType IEnumerable<Transaction> dataPicked = dataCredits.Where(s => (s as Credit).CreditType == creditType); // make a new list for output TransactionList dataResults = new TransactionList(); // Put the enuerable items in a Transaction List // The casting to TransactionList did not work foreach (Transaction item in dataPicked) { dataResults.Add(item); } // Check to see if we have results if (dataResults.Count > 0) { // Sort the Results dataResults.Sort(); // Return the Results return dataResults; } else { return null; //throw new HttpResponseException(HttpStatusCode.NoContent); } } // end of method |
Get “Read” All Debit Transactions
This method loads all transactions from the memory store into a TransactionList object (List<Transaction>), and performs a LINQ query to obtain first the Debit type transactions and then those with the Debit Enumeration matching the query input parameter. It then sorts the list per the implemented interface on the Transaction object, and returns to the caller.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
/// <summary> /// Gets a list of debit transactions that are of the given type. /// Items should be sorted according to defined rules. /// </summary> /// <param name="debitType">(DebitTypeEnum) type of debit transaction</param> /// <returns>(TransactionList) a Transaction List type object /// (list that holds transaction objects)</returns> public TransactionList GetDebitsByType(DebitTypeEnum debitType) { TransactionList data = TransactionList.Load(FilePath); // Pick items from the list that meet the criteria of being a Debit IEnumerable<Transaction> dataDebits = data.Where(s => (s is Debit)); // Pick items from the list that meet the criteria of matching debitType IEnumerable<Transaction> dataPicked = dataDebits.Where(s => (s as Debit).DebitType == debitType); // make a new list for output TransactionList dataResults = new TransactionList(); // Put the enuerable items in a Transaction List // The casting to TransactionList did not work foreach (Transaction item in dataPicked) { dataResults.Add(item); } // Check to see if we have results if (dataResults.Count > 0) { // Sort the Results dataResults.Sort(); // Return the Results return dataResults; } else { return null; //throw new HttpResponseException(HttpStatusCode.NoContent); } } // end of method |
Post “Create” Credit Transaction
The Post() method in the controller creates a record. It simply takes the HTTP Request’s content body and attempts to add the Credit object to the XML Datastore. It will first load the XML Datastore to temporary memory “TransactionList”, add the HTTP Request’s Credit object, then call the Save() method on the instance TransactionList object to serialize and save to the XML file Datastore.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/// <summary> /// Adds a new Credit object to the checking register /// </summary> /// <param name="credit">(Credit) credit object to add</param> [HttpPost()] public Credit AddCredit([FromBody]Credit credit) { TransactionList data = TransactionList.Load(FilePath); if (credit == null) { throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); } else { data.Add(credit); data.Save(FilePath); } // Return the object that was passed return credit; } // end of method |
Post “Create” Debit Transaction
The Post() method in the controller creates a record. It simply takes the HTTP Request’s content body and attempts to add the Debit object to the XML Datastore. It will first load the XML Datastore to temporary memory “TransactionList”, add the HTTP Request’s Debit object, then call the Save() method on the instance TransactionList object to serialize and save to the XML file Datastore.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/// <summary> /// Adds a new Debit object to the checking register /// </summary> /// <param name="debit">(Debit) debit object to add</param> [HttpPost()] public Debit AddDebit([FromBody]Debit debit) { TransactionList data = TransactionList.Load(FilePath); if (debit == null) { throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); } else { data.Add(debit); data.Save(FilePath); } // Return the Debit object return debit; } // end of method |
Web API Client “Tester to Service” Console Application
A simple console application was created to test the Checking Account Register ASP.NET Web API service capabilities. The Checking Account Register ASP.NET Web API publishes a help page in the project build settings that make public the available API methods and instructions on how to consume it (see Demo section). The purpose of the client was to test consumption of all the available methods in the Web API. The client console “tester” application project code is available on GitHub here.
CRUD Operations Testing
The main Program class in the Client Console Application “Tester” executes CRUD (post, read) operations on the Checking Account Register Web API Service. It utilizes the ClientHelper class (see Utilities Class Library below) to execute commands with appropriate parameters (which may include Transaction model data) on the service. The code for the Program class is available in the client project on GitHub here. Note: It specifies in the code the Checking Account Register Web API Service address as a constant for use in testing the API. The Demo section shows screen captures of the client console application testing the service and the results printed to the console.
Utilities Class Library
The Utilities class library is a project with multiple classes that make user data entry, menu navigation, enumerations, text manipulations, streaming, and building http response/request objects a lot easier than coding from scratch. You can download the code for the Utilities Class Library on GitHub here. Note: I did not develop the classes for this framework class project included for our labs. We would not had been capable to code this in a week’s deadline for the class assignment. Instead, we were expected to learn about and how to utilize this framework and integrate into our client application to test the Web API service.
Helper Classes
Only the main Web API helper classes that help build HttpClient objects are discussed below.
HttpResults
The HttpResults data model class helps to encapsulate and simplify the Web API Response processing. The code for the HttpResults class is available in the client project on GitHub here.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class HttpResults<OutputType> { public HttpStatusCode StatusCode { get; set; } public string Error { get; set; } public OutputType Result { get; set; } public string RawData { get; set; } public HttpResults(HttpStatusCode statusCode, OutputType result) { StatusCode = statusCode; Result = result; } public HttpResults(HttpStatusCode statusCode, string error) { StatusCode = statusCode; Error = error; } } |
ClientHelper
The ClientHelper class is a generic framework that simplifies the HTTP Request/Response processing and operations required by the client “tester” application to formally build request objects and process the responses from a service. The code for the ClientHelper class is available in the client project on GitHub here.
A summary of the capabilities and methods are shown in the list below:
- Gets an HttpClient object to connect to the given service URL
- Gets the media type for the given serialization mode
- Modifies the headers to support the given serialization mode
- Determines the contents of the http response message
- Parses an Http response message
- Executes a GET on the given Url
- Executes an HTTP method on the given Url
- Executes a POST on the given Url
- Executes a PUT on the given Url
- Executes a DELETE on the given Url
- Executes a DELETE on the given Url without header inputs or outputs
Demo
Code
The entire project code repository is available on GitHub here.