Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

CloudBank

0.00/5 (No votes)
8 Dec 2022 1  
Demo front end to show Blazor UI over Azure functions using Event Sourcing for the data storage
This is a client side web application that sends commands to the Bank Account event sourcing demo application which uses Azure Tables as the storage mechanism for the event streams underlying each bank account.

CloudBank is a demo front end to show how to put a Blazor UI front end over Azure functions using Event Sourcing for the data storage

This client side web application sends commands to the Bank Account event sourcing demo application which uses Azure Tables as the storage mechanism for the event streams underlying each bank account.

You can try it out from any modern web browser at this URL

Using the application

Cloudbank front screen

When the application is launched the front screen as above is shown, with the navigation bar down the left hand side. This is pretty much an out of the box standard Blazor site template.

Creating a new account

Click on the User menu to go to the form that allows you to create a new bank account. You can either type your own choice of bank account number of select the generate random account number button to have the system create one.

Create new bank account

Once you have created a new account you can navigate to it with the navigate to {accountnumber} button.

Getting the account balance

To get the account balance select the first section and it will open up and the button get balance can be used to trigger the Azure function that gets the balance for this bank account.

Get account balance

Because the backing store is event sourced it is just as easy to get the state as at some past date as it is to get the current state so you can enter an as of date and the system will return the account balance as at that date.

Account balance retrieved

Making a deposit

The second expandable section allows you to make a deposit.

Make a deposit

There are parameters to fill in for the deposit amount, deposit source and any commentary and then pressing the submit button will pass these to the Azure function to make the deposit.

Deposit made

When a the deposit is made a message will indicate this.

Making a withdrawal

The third expandable section is for making a withdrawal.

Make withdrawal

This has parameters for ammount and for commentary and again pressing the submit button sends these parameters to the Azure function.

Made Withdrawal

Because the fuunction to make a withdrawal needs to run a projection that checks the current balance (to prevent the account being overdrawn beyond its limit) this function also returns information about the account balance.

Setting an overdraft

The lowest of the expandable section allows for an overdraft to be set.

Setting an overdraft

Pressing the submit button having filled in all the parameters will pass these to the Azure function to execute them.

Overdraft set

Again, because this Azure function has to get the existing overdraft and balance for its validation it can return this information to the front end.

For every Azure function executed the system will also return the total time it took to execute the function and if the function also runs any projections over the event stream it will also return the sequence number of the last event read.

How it works

The back end of this application is running as Azure serverless functions which are backed by event sourcing with the event streams for each account held in an Azure table. This is described in this GitHub project

On the front end the Razor pages inject the standard http client and a RetailBankApi class which is used for all comminication with the Azure functions:

@using CloudBank.Services


@page <span class="pl-pds">"/myaccount"</span>
@page <span class="pl-pds">"/myaccount/{accountnumber}"</span>

@inject HttpClient Http
@inject IRetailBankApi retailBankApi

This RetailBankApi class is injected into every page by use of dependency injection in the Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    <span class="pl-c">// Modal forms</span>
    services.AddBlazoredModal();

    <span class="pl-c">// Bank account API</span>
    services.AddScoped <IRetailBankApi, RetailBankApiNoHttp>();
}

Behind the scenes the RetailBankApiNoHttp class loads the set of Azure functions from a configuration file called bank-api.json (which allows the application to have its back end changed without impacting the deployed front end) - this is loaded in the MainLayout.razor file:

@code
{

    [Parameter]
    public ApiCommand[] apiCommands { get; set; }

    protected override async Task OnInitializedAsync()
    {
        if (null == apiCommands)
        {
            apiCommands = await Http.GetJsonAsync<ApiCommand[]>(<span class="pl-pds">"sample-data/bank-api.json"</span>);
            if (null != apiCommands)
            {
                <span class="pl-c">// fix up the rertail bank api with them...</span>
                retailBankApi.Initialise(apiCommands);
            }
        }
    }
}

To call these from the account actions razore page we have action code wired up for the submit button like:

<button @onclick=<span class="pl-pds">"@(() => GetBalance())"</span> class=<span class="pl-pds">"btn btn-secondary"</span>>
       <span class=<span class="pl-pds">"oi oi-cloud-download"</span> aria-hidden=<span class="pl-pds">"true"</span>></span>
        Get Balance
 </button>

When triggered this calls the Azure function thus:

private async Task GetBalance()
{
    LastFunctionMessage = <span class="pl-pds">$"Getting balance for {accountnumber} at {getbalancePayload.AsOfDate}  "</span>;

    try
    {
        var result = await retailBankApi.GetAccountBalance(Http, accountnumber, getbalancePayload);
        LastFunctionMessage = result.Message;
        LastRunTime = result.ExecutionTime;
        LastSequenceNumber = result.SequenceNumber;
    }
    catch (Exception ex)
    {
        LastFunctionMessage = ex.Message;
    }

}

This sends the command to the Azure function by http:

<span class="pl-c">/// <span class="code-summarycomment"><<span class="pl-ent">summary</span>></span>
</span><span class="pl-c">/// Get the current balance of an account <span class="code-summarycomment"></span>
</span><span class="pl-c">/// <span class="code-summarycomment"></<span class="pl-ent">summary</span>></span>
</span>public async Task<ProjectionFunctionResponse> GetAccountBalance(HttpClient httpclient, string accountnumber,  GetBalanceData payload)
{
    if (null != httpclient)
    {

        string key = <span class="pl-pds">""</span>;
        key = _commands.FirstOrDefault(f => f.CommandName == <span class="pl-pds">"Get Balance"</span>)?.ApiKey;

        HttpRequestMessage message = new HttpRequestMessage()
        {
            Method = HttpMethod.Get,
            RequestUri = new Uri(RetailBankApi.GetBase(), RetailBankApi.GetBalance(accountnumber, payload.AsOfDate , key))
        };


        var response = await httpclient.SendAsync(message);
        if (response.IsSuccessStatusCode)
        {
            var jsonString= await response.Content.ReadAsStringAsync();
            return JsonConvert.DeserializeObject<ProjectionFunctionResponse>(jsonString);
        }
        else
        {
            return new ProjectionFunctionResponse() { Message = <span class="pl-pds">$"Unable to get balance - {response.StatusCode}"</span>, InError = true };
        }
    }

    return new ProjectionFunctionResponse() { Message = <span class="pl-pds">$"Not connected to retail bank API"</span>, InError = true };
}

Deployment

Because this Blazor front end runs entirely on the client it can be hosted on a static website - and the static website functionality that you get with an Azure storage account is perfect for this.

Azure storage static website

You will need to add this static website URL to the CORS settings of your Azure functions application so that it is allowed to be accessed from there:

Azure functions CORS settings

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