ASP.NET Website #5: Currency Converter

This is the fifth website of my project where I practise using elements of ASP.NET in order to exercise my world-changing ideas :-)

Looking at it now, you might think I had to pay hard earned pounds for my colours and so stuck with white but actually, the emphasis on this project was using web services and in particular, ASMX. I first got a taste of these when I watched the Pluralsight course Becoming a .NET Developer in which the author used one for his examples. You can see what I thought of that course here but one of the best things I got from it was a taste for web services which I think are amazing. I literally could not wait to try one out; it was just a case of finding which one looked most useful.

How To Use It

Using this is really easy in that all you need to do (especially if you are British) is to change up-to two fields and click a button. Let’s run through a specific example to illustrate this, though.

Imagine you are heading to America this year and want to take along £1,000 as spending money. The first thing you do is type in 1000 into the Amount box, which is the first one you can see under the instructions. Next, since you are British, you can leave the GBP drop down as it is, and then simply select USD in the second one. In the screenshot above, it’s the one with EUR. Finally, click on Convert Currency and you will be presented with something like this:

The really neat thing is that this is connecting to a live service so each time you run it, the currency exchange will likely change. How’s that for up-to-date?

Using a Master Page

For this project, I fancied using a Master page. Strictly speaking, it wasn’t necessary since there would only ever be one page being used, but it gives a flavour for what is possible. Ideally, though, master pages would be used to house navigation menus and titles across a whole site, for example. To set it up, first right click on your project and choose Add….New Item….Web Forms Master Page and name it. In mine, I added the title and credits, like this:

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
<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Currency.Master.cs" Inherits="W5_Currency.Master" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>W5 - Currency Converter - logicalmoon.com</title>
<asp:ContentPlaceHolder ID="head" runat="server">
</asp:ContentPlaceHolder>
</head>
<body>
<script src="Scripts/jquery-2.1.3.min.js"></script>
<link href="CSS/StyleSheet.css" rel="stylesheet" />
<div class="headings">
<h1>Currency Converter</h1>
<h4>Type in your amount of currency, choose the current currency and the one to exchange into, then press <em>Convert Currency</em>.</h4>
</div>
<form id="form1" runat="server">
<div class="main-buttons">
<asp:ContentPlaceHolder ID="cph" runat="server"></asp:ContentPlaceHolder>
</div>
</form>
<footer class="credits">
<p><small>Created by <a href="https://www.logicalmoon.com/">Stephen Moon</a></small></p>
</footer>
</body>
</html>

The interesting part is here from line 20:

1
<asp:ContentPlaceHolder ID="cph" runat="server"></asp:ContentPlaceHolder>

This basically means that at this point in any page (which uses the master), place the content with an ID of cph. Mine was created by adding a new Web Form that was associated with a master page - Add….New Item….Web Form With Master Page. Once you have named your new page, you will get another dialog where you choose the master page to use. Naturally, I used the one I had just created. Do do it in this order though because the master page needs to exist first. You might also want to change the ID fields in both files - I chose cph and ignored the head one. In the new web form, I created my buttons and input fields, like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<%@ Page Title="" Language="C#" MasterPageFile="~/Currency.Master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="W5_Currency.Default" %>

<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="cph" runat="server">
Amount: <asp:TextBox ID="currencyAmount" runat="server" ViewStateMode="Inherit" CausesValidation="True" ValidationGroup="DigitsOnly" Width="70px">1</asp:TextBox>
<asp:DropDownList ID="currencyA" runat="server"></asp:DropDownList>
<asp:DropDownList ID="currencyB" runat="server"></asp:DropDownList>
&nbsp;<asp:Button ID="buttonConvert" runat="server" OnClick="buttonConvert_Click" Text="Convert Currency" />
<asp:RangeValidator ID="amountValidator" runat="server" ControlToValidate="currencyAmount" ErrorMessage="Integers greater than zero please." MaximumValue="1000000" MinimumValue="1" Type="Integer" ForeColor="Red">*</asp:RangeValidator>
<asp:RequiredFieldValidator ID="requiredValidator" runat="server" ControlToValidate="currencyAmount" ErrorMessage="You must enter a value for the amount." ForeColor="Red">*</asp:RequiredFieldValidator>
<asp:Label ID="resultAmount" runat="server"></asp:Label>
<br />
<br />
<asp:ValidationSummary ID="validationSummary" runat="server" />
</asp:Content>

Checking Ranges

It’s worth mentioning bounds checking. I didn’t want the users to be able to put anything useless into the input fields because obviously, that would only cause me problems later. Controlling the drop downs was easy because you can limit choices to what is available; I’ll come back to those. The place where you type in your currency amount, though, was open to misuse so let’s explain how I handled that. The key controls I needed to focus on were:

1
2
3
<asp:RangeValidator>
<asp:RequiredFieldValidator>
<asp:ValidationSummary>

Let’s tackle each in turn.

RangeValidator

1
<asp:RangeValidator ID="amountValidator" runat="server" ControlToValidate="currencyAmount" ErrorMessage="Integers greater than zero please." MaximumValue="1000000" MinimumValue="1" Type="Integer" ForeColor="Red">*</asp:RangeValidator>

This control ensures that the minimum value allowed is “1” and the maximum, “1000000” which equates to 1 million units of currency. Based on my previous holidays, that upper range should cater for at least the next few years. You can see that it is referencing currencyAmount - that’s the text box where the user types the amount in. It also uses a foreground colour of red, but why? Have you ever seen those input forms where you sign up for a site or a service, and when you make a mistake, it shows you where something is incomplete or wrong using a red asterisk? That’s the style I wanted to mimic here. If you look just beyond the colour reference (Red), you can actually see mine. Provided you enter values between 1 and a million, it won’t complain. However, if your input is out of that range, it will inform you.

RequiredFieldValidator

Here’s where I use the RequiredFieldValidator:

1
<asp:RequiredFieldValidator ID="requiredValidator" runat="server" ControlToValidate="currencyAmount" ErrorMessage="You must enter a value for the amount." ForeColor="Red">*</asp:RequiredFieldValidator>

Again, it points to currencyAmount and uses the colour red for effect. Note the ErrorMessage. I didn’t talk about that (directly) in the previous control’s description, but that will be useful when we come to the next control.

ValidationSummary

Any error messages are collated here, in addition to the asteriskes I mentioned earlier:

1
<asp:ValidationSummary ID="validationSummary"  runat="server" />

This allows you to be a bit more descriptive about what is going on because naturally, a red asterisk only tells you that there is a problem - not what.

The Web Service

The meat of the web application! I won’t go into any detail about how you add an ASMX service because I have already done that for the Currency Exchange Service. Instead, let’s talk about how I use it to fill the drop down controls and actually derive the exchange rates.

Populating the Drop Down Controls

Each drop down was created using this kind of reference:

1
<asp:DropDownList ID="currencyA" runat="server"></asp:DropDownList>

but what about populating them? If you use the web application, you will notice that there are an awful lot of currencies. In my Default.aspx.cs file (the code-behind), inside the Page_Load() method, I could have done something like this:

1
2
3
4
5
6
if (!Page.IsPostBack)
{
currencyA.Items.Add("GBP");
currencyA.Items.Add("USD");
currencyA.Items.Add("EUR");
}

And it would have worked, but it does seem quite labour intensive, especially when there are dozens of currencies. Alternatively, I could perhaps populate a text file or XML file with a list - they are readily available and could just be copy/pasted before being read into the application. Again though, that seems like unnecessary work. Fortunately (and preferably!), as part of the  exchange service, there are an in-built list of currencies accessible by using a pre-generated Enum:

1
2
3
4
5
6
7
8
9
public enum Currency {

/// <remarks/>
AFA,

/// <remarks/>
ALL,

...

But how do I get to those? I still don’t want to type them all in, not least because that could be error-prone. Here’s one option:

1
2
3
4
foreach (Currency ccy in Enum.GetValues(typeof(Currency)))
{
currencyA.Items.Add(ccy.ToString());
}

That looks a lot better and reasonably quick. Still, if you look at the actual drop down control, you will realise that it is bindable and as such, will accept a list. So, of course, there’s a better way! (Groan….I  know….why didn’t I just get to the point, originally!?)

1
2
3
4
5
6
7
8
9
10
private void BindDropDownListData()
{
var items = Enum.GetNames(typeof(Currency));
Array.Sort(items);

currencyA.DataSource = items;
currencyA.DataBind();
currencyB.DataSource = items;
currencyB.DataBind();
}

Here, I’ve separated the statements into their own method, grabbed the currencies as a string array and sorted it. This is then assigned to the control’s data source before refreshing. Come to think of it, that’s pretty refreshing too - at least in terms of effort.

Performing The Currency Exchange

So far, we have our main screen. We have populated drop downs, a text box to enter amounts and a button to press to make it all happen. What we need to do next is actually convert the amounts between currencies. Here’s some code in the code-behind that kicks everything off:

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
protected void buttonConvert_Click(object sender, EventArgs e)
{
// Check to make sure no-one has tried to subvert the Javascript
Page.Validate();
if (Page.IsValid)
{
resultAmount.Text = ConvertCurrency();
}
}

private string ConvertCurrency()
{
var cs = new CurrencyService();
int amount = 0;
if (int.TryParse(currencyAmount.Text, out amount) == true)
{
return String.Format("You will receive: {0:0.00} {1}",
cs.ExchangeCurrency(amount, currencyA.SelectedValue, currencyB.SelectedValue),
currencyB.SelectedValue);
}
else
{
return "Could not convert amount. Sorry.";
}
}

Lines 17-19 (highlighted) show where I call my wrapper, CurrencyService, to get the exchange rate calculation done. You can see that I am passing in strings which represent the currency; that’s fine because I am certain they will be valid and it matches what is on the front-end, but you will see that I will need to convert them back to enumerated types, below. I’m also being careful about the currency amount. It should be fine, but it doesn’t hurt to double-check.

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
public class CurrencyService
{
public double GetRate(string currencyA, string currencyB)
{
return GetRate(StringCurrencyToEnumCurrency(currencyA), StringCurrencyToEnumCurrency(currencyB));
}

public double ExchangeCurrency(int amountFrom, string currencyA, string currencyB)
{
return ExchangeCurrency(amountFrom, StringCurrencyToEnumCurrency(currencyA), StringCurrencyToEnumCurrency(currencyB));
}

private double GetRate(Currency currencyA, Currency currencyB)
{
var converter = new CurrencyConverter.CurrencyConvertorSoapClient();
return converter.ConversionRate(currencyA, currencyB);
}
private double ExchangeCurrency(int amountFrom, Currency currencyA, Currency currencyB)
{
double rate = GetRate(currencyA, currencyB);
return (double)amountFrom * rate;
}

private Currency StringCurrencyToEnumCurrency(string currency)
{
return (Currency)Enum.Parse(typeof(Currency), currency);
}
}

Firstly, I didn’t make this a static class, just in case I needed to call this multiple times, from multiple places within the same application. But largely, you can see it has no real state to speak of. I also only expose two methods: GetRate provides an exchange rate and ExchangeCurrency goes a step further and actually converts the amount, too. I use the StringCurrencyToEnumCurrency method to convert between strings to enumerated types and then just call the ConversionRate method of the Soap Client, which does all the hard work. That’s more or less it!

What Did I Learn From This?

  • I now realise that I didn’t need to remove the second endpoint in my config file - I just needed to use the overloaded method which refers to a named one. I worked that out by the time I wrote my guide for connecting to an ASMX service!
  • I had a hard time lining up each asterisk. Ideally, I wanted them to populate the same column of a page, but because in some places two might occur, even if only one is shown, it is placed where it would go were there both. I.e. given this space “XX”, even if the required validator fires (which is the 2nd control), the red asterisk still appears here: “X*”. I don’t know of an (easy) way around this.

What Would I Do Differently Next Time?

  • Looking at the code now, I think I should have put some guard clauses into the CurrencyService class.
  • It would also be nice to try something like this with WCF or perhaps even create my own service.
  • What happens if the service changes? Or goes down. This is only a simple example, but more error-checking wouldn’t hurt.

Please take a look at the Currency Converter application source in the GitHub repository for more information.


Hi! Did you find this useful or interesting? I have an email list coming soon, but in the meantime, if you ready anything you fancy chatting about, I would love to hear from you. You can contact me here or at stephen ‘at’ logicalmoon.com