Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Custom ASP.NET Ajax Control for Loan Payment Calculation

4.71/5 (4 votes)
16 Aug 20074 min read 1   1.2K  
This article describes a custom ASP.NET Ajax control that calculates loan payment schedules

Screenshot - LoanPaymentCalculationControl.jpg

Introduction

I'm quite new to ASP.NET, but the technology seems to be very interesting, so I've decided to try to build a custom ASP.NET control. To make this task more interesting, I've tried to build an Ajax-enabled control. The new control had to do something useful, so I've opted to build a mortgage (loan) payment schedule. The resulting code rests on the following assumptions:

  • The discount rate, used to calculate monthly payments, is calculated using the following formula: ((Math.Pow((1 + monthRate), monLength)) - 1) / (monthRate * (Math.Pow((1 + monthRate), monLength)))
  • The interest payment fraction of the monthly payment is calculated by multiplying the monthly interest rate by the balance (yet unpaid mortgage). This means that each month, the interest part of the payment diminishes and the mortgage re-payment part increases.

These assumptions should not apply in practice, but the result is roughly equal to what banks usually use to calculate your mortgage payments. Anyway, the purpose of this control is not to be used on official websites or to be totally precise, but to see how a custom Ajax-enabled ASP.NET control works in reality. I can say in advance that it works great!

Using the Code

Any Ajax control consists of two main parts: server side (ASP.NET) and client side (JavaScript). To be fully Ajax-enabled, a control would normally bring any UI rendering resulting from user input -- in our case, displaying a payment schedule table -- to the client side.

To minimize bandwidth, the control should also exchange only data between the JavaScript and server side code, no mark-up. This means that the UI is rendered using not HTML, but JavaScript, calling webpage DOM methods. Well, HTML can be built by JavaScript too, but this is not that interesting or effective, right?

Server Side

The main server side method is buildScheduleForClient. This method returns a JSON-encoded array of data to be rendered on the client side, according to the aforementioned assumptions. This function is called from ICallbackEventHandler.GetCallbackResult(), which itself is being called by the ASP.NET Ajax architecture each time a particular control initiates callback.

C#
private string buildScheduleForClient(int iMonthCount, 
    double dInterestRate, double dLoanAmount)
{
    StringBuilder sb = new StringBuilder();

    sb.Append("[");

    sb.Append(String.Format("['{0}','{1}','{2}','{3}','{4}']",
        _COL_MONTH, _COL_TOTAL_PAY, _COL_INTEREST_PAY, 
        _COL_LOAN_PAY, _COL_BAL));

    double dDiscountRate = 
        LoanMath.CalculateDiscountRate(dInterestRate / 1200, iMonthCount);
    double dMonthPayment = dLoanAmount / dDiscountRate;
    double dMonthPaymentRounded = Math.Round(dMonthPayment, 2);
    double balance = dLoanAmount;

    for (int i = 1; i <= iMonthCount; i++)
    {
        double dInterestPayment = (dInterestRate / 1200) * balance;
        double dCreaditRepayment = (dMonthPayment - dInterestPayment);
        balance -= dCreaditRepayment;

        sb.Append(String.Format(",['{0}','{1}','{2}','{3}','{4}']", 
            i.ToString(), dMonthPaymentRounded.ToString(), 
            Math.Round(dInterestPayment, 2).ToString(), 
            Math.Round(dCreaditRepayment, 2).ToString(), 
            Math.Ceiling(balance).ToString()));

        if (i % 12 == 0)
        {
            sb.Append(String.Format(",['Year {0}, repayed {1}%']", 
                (i / 12),Math.Round((100 - ((balance / dLoanAmount) * 100)),
                1)));
        }
    }

    sb.Append("]");
    return sb.ToString();
}

One more notable thing about the server side is how to include all related CSS and JavaScript material. JavaScript inclusion is easier:

  • Add the *.js file to the project
  • Change its "Build Action" to "Embedded Resource"
  • Add an attribute with media type information -- i.e. [assembly: WebResource("LoanCalculationControl.LoanCalculation.js", "application/x-javascript")] -- to the AssemblyInfo file
  • Add the following code to override the OnPreRender method of the control:
    Page.ClientScript.RegisterClientScriptResource(this.GetType(), 
        "LoanCalculationControl.LoanCalculation.js");

Adding CSS is a bit more complicated. The code in the OnPreRender method should be different and you should check if any other instance of your control has already added a CSS reference to the page:

C#
// add css reference
string cssUrl = Page.ClientScript.GetWebResourceUrl(this.GetType(), 
    "LoanCalculationControl.LoanCalculation.css");
LoanHtmlLink cssLink = new LoanHtmlLink();
cssLink.Href = cssUrl;
cssLink.Attributes.Add("rel", "stylesheet");
cssLink.Attributes.Add("type", "text/css");

bool cssAdded = false;

foreach(Control control in this.Page.Header.Controls)
{
    if (control.Equals(cssLink))
    {
        cssAdded = true;
        break;
    }
 }

if(!cssAdded)
    this.Page.Header.Controls.Add(cssLink);

LoanHtmlLink is just a class inheriting from HtmlLink and overriding the Equals method.

Client Side

As it very often is with Ajax, the client side is more interesting (and challenging). The client side begins by including a proper handler with the onClick event of the button. ASP.NET offers a convenient method for outputting proper JavaScript for callback invocation:

Page.ClientScript.GetCallbackEventReference(this, null, 
    "LoanCalculation.UpdateUI", String.Format("'{0}'", 
    this.UniqueID), "LoanCalculation.CallbackError", true)

In the method arguments, we list a JavaScript function to handle callback results (LoanCalculation.UpdateUI), context -- in our case, control Id, which is part of all UI element Ids to make it possible to include multiple instances of the control in one page -- and a JavaScript function to handle server errors in the UI.

During tests, I've learned that this is not enough to have a correct callback from the client. I just couldn't get form data to be transferred with the callback. That is, data from the input controls was present, but it was empty. Browsing through the ASP.NET Ajax infrastructure-supporting JavaScript, I learned that we also need to call the function WebForm_InitCallback, which populates the __theFormPostData variable with all necessary input data.

So, all necessary callback initialization was brought inside one function, LoanCalculation.InitRequest:

C#
InitRequest: function(context)
{
    __theFormPostData = '';
    WebForm_InitCallback();
        
    var divContents = document.getElementById(context+'_results');
          
    var oldtable = document.getElementById(context+'_results_table');
    if(oldtable!=null)
        divContents.removeChild(oldtable);
            
    divContents.innerHTML = "Requesting data...";
}

The main function on the client side is LoanCalculation.UpdateUI:

C#
UpdateUI: function(strData, context)
{
    var divContents = document.getElementById(context+'_results');

    var tbl = document.createElement('table');
    tbl.className = 'LoanCalculationTable';
    tbl.id = context+'_results_table';
 
    var arData = eval(strData);
    var cell;
    var row;
    var normalCellCount = 0;
          
    for(var i=0;i<arData.length;i++)
    {     
        row = tbl.insertRow(i);
        if(i==0)
            normalCellCount = arData[i].length;
              
        for(var m=0;m<arData[i].length;m++)
        {
            cell = row.insertCell(m);
             
            if(i==0)
                cell.className = 'LoanCalculationTDFirst';
            else
                cell.className = 'LoanCalculationTD';                  
                var textNode = document.createTextNode(arData[i][m]);
                cell.appendChild(textNode);
        }
        if(m<normalCellCount)
        {
            cell.colSpan = (normalCellCount-m+1);
            cell.className = 'LoanCalculationTDFirst';
        }
    }
    divContents.innerHTML = "";
    divContents.appendChild(tbl);
}

This function evaluates the received string, which builds the JavaScript array. The array is parsed and the data is used to dynamically build a schedule table.

One more interesting thing is that with ASP.NET Ajax, you don't actually need to catch all exceptions on the server side. Exceptions can be handled in the UI. This control even throws an exception explicitly if the input data exceeds the allowed boundaries. The following is the JavaScript function to display a server error to the users:

C#
CallbackError: function(error,context)
{
     var divContents = document.getElementById(context+'_results');
     if(divContents!=null)
         divContents.innerHTML = ""+error+"";
}

Points of Interest

The resulting control does not claim to display correct results -- the actual formula can be much more complicated -- but it offers an example of how great the Ajax concept is and how great it works with ASP.NET.

History

  • 16 August, 2007 -- Original version posted

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here