Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web

Building an Angular 13 Application with .NET 6 (Global Market) - Part 2

5.00/5 (16 votes)
7 Jun 2022CPOL8 min read 21.5K   361  
Build a simple finance application with Angular 13 and .NET 6
This is Part 2 of a series of articles to build a finance app, calling Yahoo Finance API at the backend and enhancing the frontend with Angular Material.

Image 1

Introduction

In Part 1, we created an Angular 13 frontend and .NET 6 backend from Visual Studio 2022. This article continues to build this finance app, calling Yahoo Finance API at the backend and enhance the frontend with Angular Material.

Yahoo Finance API

The Yahoo Finance API is a range of libraries/APIs/methods to obtain historical and real time data for a variety of financial markets and products, as shown on Yahoo Finance.

Some of the offerings include market data on Cryptocurrencies, regular currencies, stocks and bonds, fundamental and options data, and market analysis and news. One good reason to use Yahoo Finance API is because it can be completely free. Also, it’s simple and easy to use.

Before you start using this, you need to sign in to Yahoo finance to get your own API key.

Global Market API

Let’s back to GlobalMarketAPI project. Delete WeatherForecastController.cs and WeatherForecast.cs, which are created by default. We'll build Finance Controller with Yahoo Finance API.

Look Yahoo Finance API specification, we use http Get /v6/finance/quote to get real time quote data for stocks, crypto coins, futures, etc.

Finance Controller

Right click “Controllers” folder to add a new controller.

Image 2

Select “API Controller – Empty” to add an empty controller.

Image 3

Name it as FinanceController.cs, an empty API controller is created with the below code:

C#
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace GlobalMarketAPI.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class FinanceController : ControllerBase
    {
    }
}

Quote Response Model

From Yahoo Finance API specification, we get the response JSON of Get Quote API.

Image 4

Per the specification, we add Quote class first, which has ShortName, FullExchangeName, QuoteType, RegularMarketPrice, RegularMarketDayHigh, RegularMarketDayLow, Bid, Ask.

C#
namespace GlobalMarketAPI.Models
{
    public class Quote
    {
        public string? ShortName { get; set; }

        public string? FullExchangeName { get; set; }

        public string? QuoteType { get; set; }

        public decimal RegularMarketPrice { get; set; }

        public decimal RegularMarketDayHigh { get; set; }

        public decimal RegularMarketDayLow { get; set; }

        public decimal Bid { get; set; }

        public decimal Ask { get; set; }
    }
}

Then add QuoteResult class which has a list of Quote and error string.

C#
namespace GlobalMarketAPI.Models
{
    public class QuoteResult
    {
        public List<Quote>? Result { get; set; }

        public string? Error { get; set; }
    }
}

Finally YahooQuoteResponse class which has QuoteResult.

C#
namespace GlobalMarketAPI.Models
{
    public class YahooQuoteResponse
    {
        public QuoteResult? QuoteResponse { get; set; }
    }
}

Yahoo Finance Settings

When we call Yahoo Finance API, we need API URL and API key. We put these in application settings. Add Yahoo Finance settings in appsettings.json.

JSON
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "YahooFinanceSettings": {
    /* Please replace with your own api key */
    "APIKey": "****************",
    "BaseURL": "https://yfapi.net"
}

Base URL is constant string, https://yfapi.net. Replace API key with your own API key.

Create YahooFinanceSettings class.

C#
namespace GlobalMarketAPI.Settings
{
    public class YahooFinanceSettings
    {
        public string? APIKey { get; set; }
        public string? BaseURL { get; set; }
    }
}

Inject Yahoo finance settings in Program.cs.

C#
using GlobalMarketAPI.Settings;

var builder = WebApplication.CreateBuilder(args);
ConfigurationManager configuration = builder.Configuration;

builder.Services.AddTransient(p =>
{
    YahooFinanceSettings settings = configuration.GetSection
                (nameof(YahooFinanceSettings)).Get<YahooFinanceSettings>();

    return settings;
});

Finance Service

Now create Yahoo Finance Service. The quote URL is /v6/finance/quote.

We can write "get quote" function like this:

C#
var url = $"v6/finance/quote?symbols={symbol}";
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("X-API-KEY", new[] { settings.APIKey });
httpClient.BaseAddress = new Uri(settings.BaseURL ?? "");
var data = await httpClient.GetStringAsync(url);

Add Finance Service Interface:

C#
using GlobalMarketAPI.Models;

namespace GlobalMarketAPI.Services
{
    public interface IFinanceService
    {
        Task<Quote> GetQuote(string symbol);
    }
}

Add Finance Service Class:

C#
using GlobalMarketAPI.Models;
using Newtonsoft.Json;
using GlobalMarketAPI.Settings;

namespace GlobalMarketAPI.Services
{
    public class FinanceService : IFinanceService
    {
        private readonly HttpClient _httpClient;
        const string QuoteURL = "v6/finance/quote";
        
        public FinanceService(YahooFinanceSettings settings)
        {
            _httpClient = new HttpClient();
            _httpClient.DefaultRequestHeaders.Add
                 ("X-API-KEY", new[] { settings.APIKey });
            _httpClient.BaseAddress = new Uri(settings.BaseURL ?? "");
        }

        public async Task<Quote> GetQuote(string symbol)
        {
            var url = QuoteURL + $"?symbols={symbol}";
            try
            {
                var data = await _httpClient.GetStringAsync(url);
                var result = JsonConvert.DeserializeObject<YahooQuoteResponse>(data);
                return result?.QuoteResponse?.Result?.FirstOrDefault() ?? new Quote();
            }
            catch (Exception)
            {
                throw;
            }
        }
    }
}

Inject FinanceService instance in Program.cs:

C#
builder.Services.AddTransient<IFinanceService, FinanceService>();

Http Get Quote Endpoint

Now we can write HttpGet Quote endpoint in Finance controller.

C#
[HttpGet]
[Route("quote")]
public async Task<Quote> GetQuote([FromQuery] string symbol)
{
    return await _financeService.GetQuote(symbol);
}

The route template for each controller method is the prefix plus the string specified in the Route attribute. For the GetQuote method, the route template includes string "quote", and the URL contains string Symbol as query string parameter.

Now click “Start” button to run the solution. Both GlobalMarket and GlobalMarketAPI are started.

You should be able to see GlobalMarketAPI console window.

Image 5

Then we can check Swagger UI to verify the API endpoint.

http://localhost:5219/swagger

Image 6

Global Market Frontend

What we try to do?

Trading widget is working very well in our frontend. Now we just add a little bit fun. We want to have a symbol dropdown list. When we select a symbol from the dropdown list, call the backend API to get the real time quote of this symbol, in the meantime, re-load trading widget for this symbol.

Integrate Backend API Project

Change reverse proxy configure to connect backend finance controller.

Update proxy.config.js under GlobalMarket/projects/trading.

JavaScript
const PROXY_CONFIG = [
  {
    context: [
      "/api",
    ],
    target: "https://localhost:7219",
    secure: false
  }
]

module.exports = PROXY_CONFIG;

Although we can add multiple URLs in context, if you don’t want to change this proxy config file with every controller, can add the parent URL, such as “/api”.

Now we start to use Angular Material styling frontend.

Angular Material

The Angular team builds and maintains both common UI components and tools to help you build your own custom components. Angular Material is Material Design UI components for Angular applications.

Right click GlobalMarket project in Visual Studio 2022 Solution Explorer, then click “Open in Terminal”.

Running the following command to install Angular Material.

Shell
ng add @angular/material
  • Choose a prebuilt theme name, or "custom" for a custom theme.
  • Set up global Angular Material typography styles.
  • Set up browser animations for Angular Material.

Of course, we need bootstrap as well, the most popular CSS library.

Shell
npm install bootstrap --save

After install, we Import bootstrap and Angular Material CSS style in GlobalMarkt/projects/trading/src/styles.css.

CSS
/* You can add global styles to this file, and also import other style files */

@import "~bootstrap/dist/css/bootstrap.css";

@import '~@angular/material/prebuilt-themes/deeppurple-amber.css';

Angular Material provides high quality components. You can check it out at this link. Before using these components, you need to import component module. The below are components used in our frontend.

  • mat-card

    <mat-card> is a content container for text, photos, and actions in the context of a single subject.

  • mat-form-field

    <mat-form-field> is a component used to wrap several Angular Material components and apply common Text field styles such as the underline, floating label, and hint messages.

  • mat-select

    <mat-select> is a form control for selecting a value from a set of options, similar to the native <select> element.

  • mat-chip-list

    <mat-chip-list> displays a list of values as individual, keyboard accessible, chips.

  • mat-progress-bar

    <mat-progress-bar> is a horizontal progress-bar for indicating progress and activity.

  • mat-divider

    <mat-divider> is a component that allows for Material styling of a line separator with various orientation options.

Import these modules in app.module.ts.

TypeScript
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { TradingviewWidgetModule } from 'tradingview-widget';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ClipboardModule } from '@angular/cdk/clipboard';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatCardModule } from '@angular/material/card';
import { MatChipsModule } from '@angular/material/chips'
import { MatDividerModule } from '@angular/material/divider'
import { MatFormFieldModule } from '@angular/material/form-field'
import { MatSelectModule } from '@angular/material/select'
import { MatProgressBarModule } from '@angular/material/progress-bar'

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    TradingviewWidgetModule,
    BrowserAnimationsModule,
    FormsModule,
    ReactiveFormsModule,
    ClipboardModule,
    DragDropModule,
    MatCardModule,
    MatChipsModule,
    MatDividerModule,
    MatFormFieldModule,
    MatSelectModule,
    MatProgressBarModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

We want to use mat-form-field and mat-select, so need import FomrsModule and ReactiveFormsModule.

Now use these components to change app.component.html.

The whole page is divided two Cards. The top one is a Symbol card, and the other one is displaying trading widget.

XML
<mat-card>
  <mat-card-subtitle style="margin-top:-10px">Symbol Quote</mat-card-subtitle>
</mat-card>
<mat-divider></mat-divider>
<mat-card>
  <mat-card-subtitle style="margin-top:-10px">Live Chart</mat-card-subtitle>
  <tradingview-widget [widgetConfig]="widgetConfig"></tradingview-widget>
</mat-card>

Display Symbol Dropdown List

Open the AppComponent class file and define symbols array.

TypeScript
symbols = ['MSFT',
    'AAPL',
    'AMZN',
    'TSLA',
    'WTC',
    'BTCUSD',
    'ETHUSD',
    'CLN2022'
  ];

Open AppComponent template file to add <mat-select>. Using ngFor to display list, and binding the selected symbol value to widgetConfig.symbol.

XML
<mat-form-field appearance="fill">

  <mat-select id="symbol" class="symbol" [ngModel]="widgetConfig.symbol" 
                          (ngModelChange)="onSymbolChange($event)" required>

<mat-option *ngFor="let symbol of symbols" [value]="symbol">{{symbol}}</mat-option>

  </mat-select>

</mat-form-field>

We want to reload trading view widget when selected symbol changed. Open AppComponent class file to add OnSymbolChange event.

TypeScript
onSymbolChange(event: any) {
  this.widgetConfig = {
    symbol: event,
    widgetType: 'widget',
    allow_symbol_change: true,
    height: 560,
    width: 980,
    hideideas: true,
    hide_legend: false,
    hide_side_toolbar: true,
    hide_top_toolbar: false,
    theme: Themes.LIGHT,
  };
  this.ngOnInit();
}

In Angular, calling ngOnInit refreshes the component.

Display Getting Quote Result

Add a new app.model class (app.model.ts), then define Quote interface, and DataItem interface.

TypeScript
export interface Quote {
  shortName: string;
  fullExchangeName: string;
  quoteType: string;
  regularMarketPrice: number;
  regularMarketDayHigh: number;
  regularMarketDayLow: number;
  bid: number;
  ask: number;
}
export interface DataItem {
  name: string;
  value: number;
}

Open AppComponent class file to call backend get quote API.

TypeScript
this.http.get<Quote>(`/finance/quote?symbol=
      ${this.widgetConfig.symbol}`).subscribe(result => {
      this.quote = result;
      this.data = [
        { name: "Price", value: this.quote.regularMarketPrice },
        { name: "Day High", value: this.quote.regularMarketDayHigh },
        { name: "Day Low", value: this.quote.regularMarketDayLow },
        { name: "Ask", value: this.quote.ask },
        { name: "Bid", value: this.quote.bid },
      ];
    }, error => console.error(error));

Open AppComponent template file to add <mat-chip-list> to display get quote result.

XML
<mat-chip-list class="symbol-price-chip"
           cdkDropList
           cdkDropListOrientation="horizontal"
           (cdkDropListDropped)="drop($event)">
    <mat-chip class="symbol-price-box"
            cdkDrag
            *ngFor="let item of data">
    {{item.name}}: {{item.value}}
    </mat-chip>
</mat-chip-list>

<mat-chip> elements can change order with drag-drop. Add drop event in AppComponent class:

TypeScript
drop(event: CdkDragDrop<DataItem[]>) {
  moveItemInArray(this.data, event.previousIndex, event.currentIndex);
}

Display Progress Bar

Open AppComponent template file to add <mat-progress-bar>.

XML
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isProgressing">
</mat-progress-bar>

Open AppComponent class file to set isProgressing. Before calling API, set isProgessing to true, then set isProgressing to false in subscription callback.

TypeScript
this.isProgressing = true;
this.http.get<Quote>(`/finance/quote?symbol=
     ${this.widgetConfig.symbol}`).subscribe(result => {
  ...
  this.isProgressing = false;
}, error => console.error(error));

Global Market New Look

Now our app has a new look after backend and frontend changes. Press F5 or click “Start” button at the top menu bar. As we configured earlier, both GlobalMarketAPI (backend) and GlobalMarket (frontend) are started.

Image 7

You can drag drop “Price” “Day High” “Day Low” “Ask” “Bid” to change to whatever order you want.

Image 8

Change symbol to AAPL – Apple in symbol dropdown list.

Image 9

Then you can see Apple company trading chart and quote information.

Image 10

As you know, crypto coins crashed recently. So I really want to see Bitcoin trading chart. Change symbol to “BTCUSD” (bitcoin) in dropdown list.

Image 11

Ouch, that’s a tragedy.

But wait, why there is no quote information? Let’s debug.

Debug Angular Frontend and .NET Backend

In frontend, we put breakpoints at calling get quote API and subscription callback.

Image 12

In backend, we put a breakpoint at GetQuote function of finance controller.

Image 13

Now change symbol to BTCUSD.

Image 14

The first breakpoint is triggered. You can see that we’re passing symbol “BTCUSD” correctly.

Press F5 or click “Continue” at menu bar to let it go.

Image 15

The breakpoint at GetQuote of finance controller is triggered. You also can see symbol “BTCISD” passed correctly.

Let’s press F11 step into.

Image 16

The Yahoo Finance API URL is correct as well. Let’s put breakpoint at exception catch.

Press F10 to step over.

Image 17

There is no exception, but result appears nothing. That means Yahoo Finance cannot find anything about this symbol. Why? That because Yahoo Finance symbols are not all the same as trading view symbols. For example, Yahoo Finance symbol for “BTCUSD” is “BTC-USD”, and “ETHUSD” is “ETH-USD”, etc.

To resolve this issue, we need to add a mapping between trading view symbols and Yahoo Finance symbols.

We can add this mapping to GlobalMarketAPI app settings.

C#
"YahooFinanceSettings": {
    /* Please replace with your own api key */
    "APIKey": "******",
    "BaseURL": "https://yfapi.net",
    "SymbolMapInfo": {
      "WTC": "WTC.AX",
      "BTCUSD": "BTC-USD",
      "ETHUSD": "ETH-USD",
      "CLN2022": "CLN22.NYM"
    }
}
  • WTC – Wise Tech Global
  • BTCUSD – Bit Coin
  • ETHUSD – Ethereum
  • CLN2022 – Crude Oil July 2022

Now add dictionary to YahooFinaceSettings.cs in GlobalMarketAPI. It will be loaded at program start.

C#
namespace GlobalMarketAPI.Settings
{
    public class YahooFinanceSettings
    {
        public string? APIKey { get; set; }
        public string? BaseURL { get; set; }
        public Dictionary<string, string>? SymbolMapInfo
        {
            get;
            set;
        }
    }
}

Open FinanceService class, add symbol map dictionary.

C#
private readonly Dictionary<string, string> _symbolMap = new Dictionary<string, string>();

Set this dictionary in constructor.

C#
if (settings.SymbolMapInfo != null && settings.SymbolMapInfo.Count > 0)
{
    _symbolMap = settings.SymbolMapInfo.ToDictionary(x => x.Key, x => x.Value);
}

Replace the symbol if it has a mapping one.

C#
symbol = _symbolMap.ContainsKey(symbol) ? _symbolMap[symbol] : symbol;

Now debug again.

Image 18

Symbol is the mapped one, “BTC-USD”.

Image 19

Yahoo Finance API returns result.

Press “F5” continue. Breakpoint at Angular http client call back function is triggered.

Image 20

Press “F5” to continue. Finally, get bit coin quote information.

Image 21

How to Use the Source Code

Download and extract source code, open GlobalMaket.sln with Visual Studio 2022.

Then, right click GlobalMarket project in Solution Explorer, select “Open in Terminal”.

Need to build tradingview-widget library first.

Shell
npm install

ng build tradingview-widget

Conclusion

Continuing from Part 1, in this article, we integrated Angular frontend with ASP.NET Core Web API and used Angular Material to style the frontend. At last, we showed you how easy it is to debug the frontend and backend together in Visual Studio 2022. You can get all source code from github.  Enjoy coding!

History

  • 16th May, 2022: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)