App-Idea 4: Dollar to Cents App

Our next app will take a dollar value input string, convert it to the number of cents, and then determine how much of each type of coin that represents (Dollar to Cents app). The coin conversion should output the fewest coins possible. And, we will investigate building a table dynamically on a Blazor page to show the coins.

We will add this page to the existing Blazor.AppIdeas.Converters app (rather than creating a separate full project). You can find the source for the Blazor.AppIdeas.Converters on GitHub. And the running sample of the Blazor app online.

We will start by loading Blazor.AppIdeas.Converters solution (created in App-Idea 1: Bin2Dec) into Visual Studio.

Model Classes

First, we need a way to differentiate between coin types. We will do that by defining an enum with an entry for each type. And, we are going to use the coin value as the type value, so that we can use that in our math operations. Let’s create the CoinType enum in the Blazor.AppIdeas.Converters project and Models folder.

namespace Blazor.AppIdeas.Converters.Models
{
    public enum CoinType
    {
        Penny = 1,
        Nickel = 5,
        Dime = 10,
        Quarter = 25
    }
}

Then, we need a class to represent each of the coin results in our conversion. This model class holds the data for the amount and display name for each coin calculation. Let’s create the CoinResult class to represent that in the Blazor.AppIdeas.Converters project and Models folder.

namespace Blazor.AppIdeas.Converters.Models
{
    public class CoinResult
    {
        public CoinResult(CoinType type, string name, int amount)
        {
            Type = type;
            DisplayName = name;
            Amount = amount;
        }

        public CoinType Type { get; private set; }

        public string DisplayName { get; private set; }

        public int Amount { get; private set; }
    }
}

Our final model class will perform the math operations to break down the total cent value into its corresponding coin representation. Let’s create the CoinCalculator class in the Blazor.AppIdeas.Converters project and Models folder.

using System;
using System.Collections.Generic;

namespace Blazor.AppIdeas.Converters.Models
{
    public static class CoinCalculator
    {
        private static readonly IDictionary<CoinType, string> _coinTypeNameMap = new Dictionary<CoinType, string>
        {
            { CoinType.Penny, "Pennies (1¢)" },
            { CoinType.Nickel, "Nickels (5¢)" },
            { CoinType.Dime, "Dimes (10¢)" },
            { CoinType.Quarter, "Quarters (25¢)" },
        };

        public static IList<CoinResult> CalculateCoinBreakdown(int cents)
        {
            var result = new List<CoinResult>();
            if (cents < 0)
                return result;

            result.Add(CalculateCoinResult(cents, CoinType.Quarter));
            cents %= (int)CoinType.Quarter;

            result.Add(CalculateCoinResult(cents, CoinType.Dime));
            cents %= (int)CoinType.Dime;

            result.Add(CalculateCoinResult(cents, CoinType.Nickel));
            cents %= (int)CoinType.Nickel;

            result.Add(CalculateCoinResult(cents, CoinType.Penny));

            return result;
        }

        private static CoinResult CalculateCoinResult(int cents, CoinType type)
        {
            return new CoinResult(
                type,
                _coinTypeNameMap[type],
                CalculateCoinAmount(cents, type));
        }

        private static int CalculateCoinAmount(int cents, CoinType type) =>
            (int)Math.Floor(cents / (decimal)type);
    }
}

The main method, CalculateCoinBreakdown (lines #16-34), goes through each coin type and tries to evenly divide that coin’s value into the number of cents. For example for quarter, it divides the cents amount by the value of CoinType.Quarter (which was defined as 25). That results in the amount of quarters in the specified cents. Then, we take the remainder and go to the next highest coin type, the Dime. Doing this all the way down to Penny, the results are placed in a list of CoinResults for each with the amount of each coin.

The CalculateCoinResult method (lines #36-42) is factory method that knows how to create an individual CoinResult. Given the amount of cents the the coin type, it creates the CoinResult with the specified type, a lookup of the display name using the _coinTypeNameMap (lines #8-14), and the math operation to find the amount of coins of that type.

Finally, the CalculateCoinAmount method performs the actual coin mathematical calculation. It’s just a straight division that calls the Math.Floor method to drop the remainder and get the lowest whole number. It casts the CoinType item to its value to use in the division… since enum item values can be any unique value, we can use it for two purposes: to identify the enum and its coin value.

DollarCentsConverterViewModel

The next layer of our design is a view model that our page communicates with. Let’s create the DollarCentsConverterViewModel class in the Blazor.AppIdeas.Converters project and ViewModels folder.

using Blazor.AppIdeas.Converters.Models;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Blazor.AppIdeas.Converters.ViewModels
{
    public class DollarCentsConverterViewModel
    {
        public string DollarValue { get; set; }

        public int Cents { get; set; }

        public IList<CoinResult> CoinResults { get; private set; } = new List<CoinResult>();

        public bool HasResults => CoinResults.Any();

        public string ErrorMessage { get; set; }

        public bool HasError => !string.IsNullOrEmpty(ErrorMessage);

        public void Convert()
        {
            try
            {
                ErrorMessage = null;
                if (string.IsNullOrEmpty(DollarValue)) throw new FormatException();

                Cents = CalculateCents(DollarValue);
                CoinResults = CoinCalculator.CalculateCoinBreakdown(Cents);
            }
            catch
            {
                ErrorMessage = "The dollar value must be a valid number between 0.00 and 1000.00.";
                CoinResults.Clear();
            }
        }

        private int CalculateCents(string dollarValue)
        {
            decimal dollar = System.Convert.ToDecimal(dollarValue);
            if (dollar < 0.0M || dollar > 1000.0M)
                throw new NotSupportedException();

            return (int)Math.Round(dollar * 100);
        }
    }
}

This class provides the properties and operations that the page will bind with:

  • First is the DollarValue input property (line #10). This will hold the value the user types into the input element.
  • Then, we have 3 output conversion properties: the cents representation, the list of results for each coin type, and a derived HasResults property (that specifies whether any results should be shown).
  • Next, there’s the ErrorMessage and HasError property that we use to handle any errors during our conversion process.
  • The Convert method (line #22-37) performs that actual operation using the value in the DollarValue property.
    • First, it clears any current error messages.
    • Then, it ensures that there is some text in DollarValue.
    • It calculates the amount of cents in the DollarValue.
    • And gets the coin results by calling the CoinCalculator.CalculateCoinBreakdown method (described above).
    • Finally, if there are any errors in the process, it sets the error message.
  • Finally, the CalculateCents method (lines #39-46) converts the text value into a decimal (this is where format errors can be thrown). It ensures that we only have a value within the supported range (0 to 1000). And then multiples the dollar value by 100 to produce the total cents amount.

Page Class

After we complete the classes for our Model and ViewModel, we can construct our page. Create the DollarCentsConverter.razor page in the Blazor.AppIdeas.Converters project and Pages folder.

@page "/dol2cent"
@inject DollarCentsConverterViewModel vm

<div class="row">
    <div class="col-lg-6 col-md-12 pr-lg-0">
        <div class="mt-3 pb-3 container">
            <h3 class="mt-3">Dollar to Cents Converter</h3>
            <hr />
            <form class="form-row">
                <div class="form-group col-12">
                    <label for="dollar">Dollar value:</label>
                    <input id="dollar" type="number" class="form-control"
                            placeholder="Enter dollar value" min="0.00" max="1000.00" step="0.01"
                            @bind-value="@vm.DollarValue" />
                </div>
                @if (vm.HasError)
                {
                    <div class="alert alert-danger col-12 ml-1">
                        <strong>Error:</strong> @vm.ErrorMessage
                    </div>
                }
            </form>
            <div class="text-center">
                <input id="btn-convert" class="btn btn-outline-primary" type="button"
                        value="Convert to Cents" @onclick="vm.Convert">
            </div>
        </div>
    </div>
    @if (vm.HasResults)
    {
    <div class="col-lg-6 col-md-12">
        <div class="mt-3 pb-3 container">
            <h5 class="mt-3">Results: @vm.Cents cents</h5>
            <hr />
            <table class="table table-striped table-bordered col-10 offset-1">
                <thead class="thead-light">
                    <tr>
                        <td><strong>Coins</strong></td>
                        <td class="text-center"><strong>Count</strong></td>
                    </tr>
                </thead>
                <tbody>
                    @foreach (var coin in vm.CoinResults)
                    {
                    <tr>
                        <td>@coin.DisplayName</td>
                        <td class="text-center">@coin.Amount</td>
                    </tr>
                    }
                </tbody>
            </table>
        </div>
    </div>
    }
</div>

@code {
}

This page’s layout is divided into a row with two columns. In large screen sizes, these columns appear side by side. In smaller screens (like tablets and mobile devices), the two divs stack horizontally one after the other. We are using the Bootstrap CSS framework to specify this layout behavior as CSS classes.

The first column is a fixed layout that shows a form to place an input element and the Convert button in raw HTML. There is one dynamic section that uses the view model HasError property to only render an error section when that property is true.

The second column is only rendered when the HasResults property is true. It uses the @ directive to embed C# logic into the page rendering. All of the HTML within that if statement is only rendered when the check is true.

Within that column, we define our first HTML table. We use the table element with the Bootstrap CSS classes for creating the table with a border and striped, alternating lines. The thead element defines the header for the table, and all of the rows are within the tbody element. In the body, we have more C# logic that loops through all of the CoinResults. We create a tr element for each CoinResult and a td element for each property we wish to display.

This is a great example of how to build a table dynamically in Blazor.

Additional Changes

Again, we need to add another NavLink to this page to the NavMenu:

@attribute [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
<div class="top-row pl-4 navbar navbar-dark">
    <a class="navbar-brand" href="">AppIdeas - Converters</a>
    <button class="navbar-toggler" @onclick="ToggleNavMenu">
        <span class="navbar-toggler-icon"></span>
    </button>
</div>

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <ul class="nav flex-column">
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="bin2dec">
                <span class="oi oi-arrow-bottom" aria-hidden="true"></span> Bin2Dec
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="rom2dec">
                <span class="oi oi-move" aria-hidden="true"></span> Roman2Dec
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="number-converter">
                <span class="oi oi-loop-circular" aria-hidden="true"></span> Ultra Number Converter
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="dol2cent">
                <span class="oi oi-dollar" aria-hidden="true"></span> Dollar2Cents
            </NavLink>
        </li>
    </ul>
</div>

@code {
    private bool collapseNavMenu = true;

    private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}

And, we need to add the DollarCentsConverterViewModel class to the app’s dependency injection container:

using Blazor.AppIdeas.Converters.ViewModels;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Net.Http;
using System.Threading.Tasks;

namespace Blazor.AppIdeas.Converters
{
    [ExcludeFromCodeCoverage]
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            builder.RootComponents.Add<App>("#app");

            builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

            // add services and view models to DI container.
            builder.Services.AddTransient<RomanDecimalConverter>();
            builder.Services.AddTransient<NumberConverterViewModel>();
            builder.Services.AddTransient<DollarCentsConverterViewModel>();

            await builder.Build().RunAsync();
        }
    }
}

With these changes complete, we can build and run the converter app. Below is a screenshot of the Dollar to Cents app, try out some different values and see what happens.

Fig 1 – Dollar to Cents Converter

Along with the project code, we also have a full set of unit and Blazor tests. You can review the full set of tests with this commit on GitHub.

In conclusion, we built a converter that translates a dollar value into the corresponding coins of different types. In this project we learned:

  • To apply the MVVM design pattern to richer logic and functionality.
  • To set enum items to specific values.
  • To use Bootstrap CSS classes to build a two-column pages.
  • To use Bootstrap CSS classes to create rich table rendering.
  • To dynamically create an HTML table based on elements in a list.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s