Extending Service Methods: Part 3
Returning single bits of data is nice, as seen in the previous examples. Doing it async is even better - but what we really want to do is make this useful for business, and in business we use objects. In this example I'll demonstrate how to use serialization methods, to return an object (serialized to an array) to our calling JavaScript proxy - and then bind that to area's of our page. The goal of course being to do something useful in a business application, giving the user a nice Ajaxian style experience, and gathering / displaying the information we need at the appropriate time.
Problem:
We are going to mock up an application what will check and validate a credit card number. Calling a fake credit card validation webservice. Typically these can take some time, and depending on your processing company, they may or may not return useful information that you can provide to your user. Most Credit Card processing gateways are webservice's themselves, or some type of Over the Wire API you're going to call. So we actually end up with 2 layers.
1st Layer = Our proxy method calling our same domain webservice.
2nd Layer = Our in domain webservice calling the Credit Card Gateways API or Webservice.
The importance of an understandable UI and proper information presentation in these steps can make or break your site. People are very very very nervous about their credit cards, and rightfully so. They are VERY likely to click away from your site if there seems to be a tricky step, or something that doesn't work, or hangs the user up at a time they wouldn't expect.
Ok, so lets begin. Lets setup our Page UI first, this will give us the ability to gather the information we need to process a credit card.
<table style="width: 500px"> <tr> <td> <asp:Label ID="Label1" runat="server" Text="Name On Card :"></asp:Label> </td> <td> <asp:TextBox ID="TextBox1" runat="server" Width="245px"></asp:TextBox> </td> </tr> <tr> <td> <asp:Label ID="Label2" runat="server" Text="Card Number :"></asp:Label> </td> <td> <asp:TextBox ID="TextBox2" runat="server" Width="245px"></asp:TextBox> </td> </tr> <tr> <td> <asp:Label ID="Label3" runat="server" Text="Card Exp :"></asp:Label> </td> <td> <asp:TextBox ID="TextBox3" runat="server"></asp:TextBox> </td> </tr> <tr> <td> <asp:Label ID="Label4" runat="server" Text="Billing Zipcode :"></asp:Label> </td> <td> <asp:TextBox ID="TextBox4" runat="server"></asp:TextBox> </td> </tr> <tr> <td colspan="2" style="text-align:right"> <asp:Button ID="Button1" runat="server" Text="Process" /> </td> </tr> </table>
No Magic yet right? I know what you're thinking...but I like tables! I really do. Ok, now with a pencil and paper I had already decided that I was going to provide meaningful errors to the user AS they typed. So what we are going to do is wire up a web service, that is familiar with each of the different fields we are trying to send to it, so lets do that now.
''Declare our Webservice class callable by our JavaScript proxy
<System.Web.Script.Services.ScriptService()> _
<WebMethod()> _ Public Function ProcessCard(ByVal name As String, ByVal cardnum As String, ByVal exp As String, ByVal zipcode As String) As String ''Perform checks to make sure the three things we can verify look as they should. ''These checks are not business secure and only for the purpose of the example 'Declare our return string to give the user instant feedback Dim SB As New StringBuilder 'Valdiate that CC number at least looks like a credit card number Dim pattern As String = "^3(?:[47]\d([ -]?)\d{4}(?:\1\d{4}){2}|0[0-5]\d{11}|[68]\d{12})$|^4(?:\d\d\d)?([ -]?)\d{4}(?:\2\d{4}){2}$|^6011([ -]?)\d{4}(?:\3\d{4}){2}$|^5[1-5]\d\d([ -]?)\d{4}(?:\4\d{4}){2}$|^2014\d{11}$|^2149\d{11}$|^2131\d{11}$|^1800\d{11}$|^3\d{15}$" Dim ccRegex As Regex = New Regex(pattern, RegexOptions.IgnoreCase) Dim ccMatch As Match = ccRegex.Match(cardnum) If Not ccMatch.Success Then SB.Append("Credit Card Number Invalid <br>") End If 'Validate the expiration date Dim strongDate As Date = Nothing Date.TryParse(exp, strongDate) If strongDate = Nothing Then SB.Append("Expiration Date is Invalid<br>") End If 'Validate the ZipCode If zipcode.Length < 5 Or zipcode.Length > 9 Then SB.Append("Zipcode is invalid") End If If SB.Length = 0 Then 'Sleep the thread to simulate the call to the web service that would process the card. Threading.Thread.Sleep(5000) SB.Append("<br><br><span style=""color:green; font-weight:bold"">Approved</span>") SB.Append("<br><br><button onClick=""docontinue()"">Continue</button>") End If Return SB.ToString End Function
Now in most cases you'd be parsing the return code you'd get from your credit card payment gateway, which usually returns as a hex value or parameterized argument but for this example we don't have a real gateway available to use so we had to mock one up a little bit. Next we'll be displaying this data in our web form through the async call. What we want to accomplish is a streamlined user interface, where the processing is actually happening in realtime.
We need to make our Scriptmanager look like this:
<asp:ScriptManager ID="ScriptManager1" runat="server"> <Services> <asp:ServiceReference Path="~/WebService1.asmx" /> </Services> </asp:ScriptManager>
Remember, what we are after in this section is the PATH to the webservice file, not the class name. Next we need to setup a JavaScript callable method, that we are going to use in our form as we enter data.
<script type="text/javascript" language="Javascript"> function inlineValidation() { var strName = document.getElementById("TextBox1").value; var strCardNum = document.getElementById("TextBox2").value; var strExp = document.getElementById("TextBox3").value; var strZip = document.getElementById("TextBox4").value; document.getElementById("btn1").style.display = "none"; document.getElementById("progress").style.width = "10000px"; document.getElementById("progress").style.height = "10000px"; var ret = Sandbox.WebService1.ProcessCard(strName, strCardNum, strExp, strZip, onComplete, onAError, onTimeout); } function onComplete(result){ document.getElementById("progress").style.width = "0px"; document.getElementById("progress").style.height = "0px"; document.getElementById("resultdiv").innerHTML = result; } function onTimeout(result){ alert("Timeout"); } function onAError(result){ alert("Error"); } function docontinue(){ window.open("http://www.asp.net","ASP.NET"); } </script>
Now before you flame me - I know it's full of holes. But the example should hold true. Good luck and feel free to send me an email, or comment here on my blog if you have problems or issues.
Good luck -
Bryan Sampica ( Freakyuno )