One Codebase, Every Platform: Build a Real-Time Currency Converter for Mobile, Web, and Desktop

Inspiration Behind the Article

This article builds upon my previous guide on developing cross-platform apps with .NET MAUI Blazor Hybrid. If you’re new to .NET MAUI or Blazor Hybrid, I highly recommend reading that guide first to grasp the foundational concepts and set up your development environment.

In this article, we’ll focus on creating a real-time currency converter app, an excellent practical use case for cross-platform applications. You’ll learn how to design and develop an app that not only fetches live exchange rates but also delivers a consistent user experience across mobile, web, and desktop platforms, all while leveraging a single UI framework: Blazor.

Here’s a preview of the app running on all three platforms web, mobile, and desktop.

Desktop

Image 1. Currency Converter on Desktop, Web, and Mobile

SRS for Currency Converter UI

  • Currency Selection: A dropdown menu with INR set as the default currency.
  • User Input: A numeric input field for entering the number of units.
  • Convert Button: Triggers conversion based on the user input and exchange rates.
  • Exchange Rates Table: Displays the currency, exchange rate, and converted amount.
  • Loading Indicator: Appears while fetching exchange rates.
  • UI Requirements: Left-aligned, responsive, and consistent design across platforms.
  • Performance: Ensures efficient updates without unnecessary API calls.

Before we start building the currency converter app, it’s essential to understand how to create a Blazor Hybrid app. I covered this in my previous article, [Building Cross-Platform Apps with .NET MAUI Blazor Hybrid], so I recommend reviewing it to get started.

For this project, I have named the app ForExFlow. After creating the solution, you will have the following three projects.

  • ForExFlow: The platform-specific project for Desktop and Mobile.
  • ForExFlow.Shared: The shared project contains UI components and business logic.
  • ForExFlow.Web: The web-specific project for running the Blazor Web app.

Solution explorer

Image 2: Solution Explorer - Project Name: ForExFlow

The Service

Create a new folder named "Services" in the "ForExFlow.Shared" project and add the following interface, IForexRateService, and the class, ForexRateService.

IForexRateService: Service interface

Let's start with the service interface. It's quite simple; all we need is a single method that takes a currency as input and returns a dictionary containing country names and their respective exchange rates.

public interface IForexRateService
{
    Task<Dictionary<string, decimal>> GetExchangeRatesAsync(string inputCurrency);
}

Code Snippet 1. Service Interface - IForexRateService.

ForexRateService Class: Fetching Exchange Rates via API

The ForexRateService class is an implementation of the IForexRateService interface, we need this class to fetch the live exchange rates from an API (https://api.exchangerate-api.com/v4/latest).

1. Private Field

  • A private instance of RestClient from RestSharp is used to send HTTP requests.
  • This client will be initialized once and reused for making API calls.
    public class ForexRateService : IForexRateService
    {
        private readonly RestClient _client;
    }
    
    Code Snippet 2. REST Client

2. Constructor

  • The constructor initializes _client with the base URL of the exchange rate API.
  • All requests will be appended with the specific currency to fetch exchange rates accordingly.
    public ForexRateService()
    {
        _client = new RestClient("https://api.exchangerate-api.com/v4/latest/");
    }
    
    Code Snippet 3. Initialization of REST Client.

3. GetExchangeRatesAsync

  • This async method takes a base currency (e.g., "USD", "EUR") and retrieves exchange rates relative to it. Returns a Dictionary<string, decimal>, mapping currency codes (like "INR", "GBP") to their exchange rates.
  • Appends the baseCurrency to the base API URL (e.g., "https://api.exchangerate-api.com/v4/latest/USD").
  • Creates a RestRequest using RestSharp and sends a GET request asynchronously.
  • Uses System.Text.Json to deserialize the API response into a C# object (ExchangeRateResponse - code snippet 5).
    public async Task<Dictionary<string, decimal>> GetExchangeRatesAsync(string baseCurrency)
    {
        var url = $"{baseCurrency}";
        var request = new RestRequest(url, Method.Get);
        var response = await _client.ExecuteAsync(request);
        if (!response.IsSuccessful)
        {
            throw new Exception($"Failed to fetch exchange rates");
        }
        var options = new JsonSerializerOptions
        {
            DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase
        };
        var data = JsonSerializer.Deserialize<ExchangeRateResponse>(response.Content, options);
        if (data == null || data.Rates == null)
        {
            throw new Exception("No exchange rates found in the response.");
        }
        return data.Rates;
    }
    
    Code Snippet 4. GetExchangeRatesAsync: Fetching Exchange Rates from API.

4. ExchangeRateResponse: Mapping API Response.

  • This class represents the structure of the API response.
  • The most important field is Rates, a dictionary storing currency rates.
    private class ExchangeRateResponse
    {
        public string Base { get; set; }
        public string Date { get; set; }
        public Dictionary<string, decimal> Rates { get; set; }
        public string Provider { get; set; }
        public string WarningUpgradeToV6 { get; set; }
        public string Terms { get; set; }
        public int TimeLastUpdated { get; set; }
    }
    
    Code Snippet 5. class ExchangeRateResponse.

The UI: ForexComponent.razor

Create a new component "ForexComponent.razor" under the pages folder in the "ForExFlow.Shared" project.

1. Currency Selection Dropdown

  • A dropdown (<select>) lets users choose the base currency.
  • The first option is always "India (INR)", ensuring INR is selected first.
  • Other currencies are dynamically populated via @foreach.
  • The @onchange="OnBaseCurrencyChange" event fires when the user selects a currency, calling the method that fetches exchange rates for the new selection.
    <div class="input-container">
        <label for="userInputCountry">Country:</label>
        <select id="userInputCountry" @onchange="OnBaseCurrencyChange">
            <option value="INR">India (INR)</option>
            @foreach (var currency in Currencies.Where(c => c != "INR"))
            {
                <option value="@currency">@currency</option>
            }
        </select>
    </div>
    
    Code Snippet 6. Currency Selection Dropdown.

2. User Input for Units

  • A number input field (<input type=" number">) allows users to enter an amount to convert.
  • Uses @bind="userInput" to dynamically bind the input to the user-input variable.
  • A "Convert" button triggers ConvertRates() when clicked, updating the state.
  • The input and button are wrapped inside the div. units and buttons for styling consistency.
    <div class="input-container">
        <label for="userInput">Units:</label>
        <div class="units-and-button">
            <input type="number" id="userInput" @bind="userInput" placeholder="Enter units" />
            <button @onclick="ConvertRates">Convert</button>
        </div>
    </div>
    
    Code Snippet 7. User Input for Units

3. Exchange Rates Table

  • If ExchangeRates is not null, a table displays exchange rates.
  • Columns
    • Currency: The currency code (e.g., USD, EUR).
    • Rate: The exchange rate for the selected currency.
    • Amount: The converted value, is calculated using MultiplyRate(rate.Value).
  • If ExchangeRates is null, a "Loading exchange rates..." message is shown.
    @if (ExchangeRates != null)
    {
        <div class="table-container">
            <table class="table">
                <thead>
                    <tr>
                        <th>Currency</th>
                        <th>Rate</th>
                        <th>Amount</th>
                    </tr>
                </thead>
                <tbody>
                    @foreach (var rate in ExchangeRates)
                    {
                        <tr>
                            <td>@rate.Key</td>
                            <td>@rate.Value</td>
                            <td>@MultiplyRate(rate.Value)</td>
                        </tr>
                    }
                </tbody>
            </table>
        </div>
    }
    else
    {
        <p>Loading exchange rates...</p>
    }
    
    Code Snippet 8. Exchange Rates Table.

Code-Behind Logic: ForexComponent.razor.cs

1. Dependency Injection: Injecting the Forex Service

  • The IForexRateService is injected into the component.
  • This service fetches exchange rates from an API.
    public partial class ForexComponent : ComponentBase
    {
         [Inject]
         private Services.IForexRateService ForexService { get; set; }
    }
    Code Snippet 9. Dependency Injection: Injecting the Forex Service.

2. Variables

  • ExchangeRates: Stores exchange rates retrieved from the API.
  • BaseCurrency: Tracks the currently selected base currency (default: "USD").
  • Currencies: A list of available currencies.
  • userInput: Stores the user’s input value for conversion (default: 1).
    private Dictionary<string, decimal> ExchangeRates;
    private string BaseCurrency = "USD";
    private List<string> Currencies = new List<string> { "USD", "EUR", "GBP", "JPY", "AUD" };
    private decimal userInput = 1;
    Code Snippet 10. Variables.

3. Methods

  • OnInitializedAsync(): Calls FetchExchangeRates() to get the initial exchange rates.
  • FetchExchangeRates(): Calls the ForexService to get the latest exchange rates for BaseCurrency.
  • OnBaseCurrencyChange(): Updates BaseCurrency when the user selects a different currency. Fetches new exchange rates for the updated selection.
  • MultiplyRate(): Converts the entered units by multiplying the exchange rate.
  • ConvertRates(): The Convert button doesn't need to re-fetch exchange rates, so it just calls StateHasChanged(), forcing Blazor to recalculate and update the UI.
    protected override async Task OnInitializedAsync()
    {
        await FetchExchangeRates();
    }
    private async Task FetchExchangeRates()
    {
        ExchangeRates = await ForexService.GetExchangeRatesAsync(BaseCurrency);
    }
    private async Task OnBaseCurrencyChange(ChangeEventArgs e)
    {
        BaseCurrency = e.Value?.ToString() ?? "USD";
        await FetchExchangeRates();
    }
    private decimal MultiplyRate(decimal rate)
    {
        return rate * userInput;
    }
    private void ConvertRates()
    {
        StateHasChanged();
    }
    
    Code Snippet 11. Methods.

Mobile and Desktop app

This is the beauty of this architecture; we are done with the core code. Now, we just need to reference the code.

  • Expand the ForExFlow app and open MauiProgram.cs.
  • Simply register the forex service by adding it to the dependency injection container.
    builder.Services.AddScoped<IForexRateService, ForexRateService>();
    Code Snippet 12. Registering forex service.

This will ensure that the ForexRateService is available to be injected into components throughout the mobile and desktop app. Once the service is registered, the app is ready to fetch and display exchange rates across both platforms.

 Mobile app

Image 3. Mobile app: Currency Converter

Desktop app

Image 4. Desktop app: Currency Converter

Web app

To achieve the same functionality in the web app, expand the ForExFlow.Web project and open the Program.cs file. Add the same line of code to register the Forex service as we did for the mobile and desktop apps. This ensures that the web application also has access to the exchange rate service.

Webapp

Image 5. Webapp: Currency Converter

Conclusion

In this article, we built a cross-platform currency converter using the MAUI Blazor Hybrid Web App, which allows it to run on the web, desktop, and mobile. We implemented a service to fetch real-time exchange rates.

By leveraging Blazor’s component-based architecture and .NET MAUI’s cross-platform capabilities, we could develop a unified codebase for multiple platforms.


Similar Articles