使用 .NET 6 构建 Angular 13 应用程序(全球市场)- 第二部分





5.00/5 (16投票s)
使用 Angular 13 和 .NET 6 构建一个简单的金融应用程序
引言
在第一部分中,我们使用 Visual Studio 2022 创建了一个 Angular 13 前端和 .NET 6 后端。本文将继续构建这个金融应用程序,在后端调用 Yahoo Finance API,并使用 Angular Material 增强前端。
Yahoo Finance API
Yahoo Finance API 是一系列库/API/方法,用于获取各种金融市场和产品的历史和实时数据,如Yahoo Finance所示。
其部分产品包括加密货币、普通货币、股票和债券的市场数据、基本面和期权数据,以及市场分析和新闻。使用 Yahoo Finance API 的一个好理由是它是完全免费的。而且,它简单易用。
在使用此 API 之前,您需要登录 Yahoo Finance 以获取自己的 API 密钥。
Global Market API
回到 GlobalMarketAPI
项目。删除默认创建的 WeatherForecastController.cs 和 WeatherForecast.cs 文件。我们将使用 Yahoo Finance API 构建 Finance Controller。
查看Yahoo Finance API 规范,我们使用 http Get /v6/finance/quote 来获取股票、加密货币、期货等的实时报价数据。
Finance Controller
右键单击“Controllers”文件夹以添加新控制器。
选择“API Controller – Empty”以添加一个空的控制器。
将其命名为 FinanceController.cs
,将创建一个空的 API 控制器,其中包含以下代码:
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace GlobalMarketAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class FinanceController : ControllerBase
{
}
}
Quote Response Model
根据 Yahoo Finance API 规范,我们获取 Get Quote API 的响应 JSON。
根据规范,我们首先添加 Quote
类,该类包含 ShortName
、FullExchangeName
、QuoteType
、RegularMarketPrice
、RegularMarketDayHigh
、RegularMarketDayLow
、Bid
、Ask
属性。
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; }
}
}
然后添加 QuoteResult
类,该类包含 Quote
列表和 error string
。
namespace GlobalMarketAPI.Models
{
public class QuoteResult
{
public List<Quote>? Result { get; set; }
public string? Error { get; set; }
}
}
最后是 YahooQuoteResponse
类,它包含 QuoteResult
。
namespace GlobalMarketAPI.Models
{
public class YahooQuoteResponse
{
public QuoteResult? QuoteResponse { get; set; }
}
}
Yahoo Finance Settings
调用 Yahoo Finance API 时,我们需要 API URL 和 API 密钥。我们将这些信息放在应用程序设置中。在 appsettings.json 中添加 Yahoo Finance 设置。
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"YahooFinanceSettings": {
/* Please replace with your own api key */
"APIKey": "****************",
"BaseURL": "https://yfapi.net"
}
Base URL 是一个常量字符串,https://yfapi.net。请将 API 密钥替换为您自己的 API 密钥。
创建 YahooFinanceSettings
类。
namespace GlobalMarketAPI.Settings
{
public class YahooFinanceSettings
{
public string? APIKey { get; set; }
public string? BaseURL { get; set; }
}
}
在 Program.cs 中注入 Yahoo Finance 设置。
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
现在创建 Yahoo Finance Service。报价 URL 是 /v6/finance/quote。
我们可以这样编写“get quote”函数:
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);
添加 Finance Service 接口
using GlobalMarketAPI.Models;
namespace GlobalMarketAPI.Services
{
public interface IFinanceService
{
Task<Quote> GetQuote(string symbol);
}
}
添加 Finance Service 类
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;
}
}
}
}
在 Program.cs 中注入 FinanceService
实例。
builder.Services.AddTransient<IFinanceService, FinanceService>();
Http Get Quote Endpoint
现在我们可以在 Finance controller 中编写 HttpGet Quote
端点。
[HttpGet]
[Route("quote")]
public async Task<Quote> GetQuote([FromQuery] string symbol)
{
return await _financeService.GetQuote(symbol);
}
每个控制器方法的路由模板是前缀加上 Route
属性中指定的 string
。对于 GetQuote
方法,route
模板包含字符串“quote
”,并且 URL 包含 string Symbol
作为查询字符串参数。
现在点击“Start”按钮运行解决方案。GlobalMarket
和 GlobalMarketAPI
都将启动。
您应该能看到 GlobalMarketAPI
控制台窗口。
然后我们可以检查 Swagger UI 来验证 API 端点。
https://:5219/swaggerGlobal Market Frontend
我们想做什么?
交易小部件在我们的前端运行得非常好。现在我们增加一点趣味性。我们想要一个符号下拉列表。当我们在下拉列表中选择一个符号时,调用后端 API 获取该符号的实时报价,同时重新加载该符号的交易小部件。
集成后端 API 项目
更改反向代理配置以连接后端 Finance Controller。
更新 GlobalMarket/projects/trading 目录下的 proxy.config.js 文件。
const PROXY_CONFIG = [
{
context: [
"/api",
],
target: "https://:7219",
secure: false
}
]
module.exports = PROXY_CONFIG;
虽然我们可以在 context 中添加多个 URL,但如果您不想每次都更改此代理配置文件,可以添加父 URL,例如“/api”。
现在我们开始使用 Angular Material 来样式化前端。
Angular Material
Angular 团队构建和维护了常见的 UI 组件以及帮助您构建自己的自定义组件的工具。Angular Material 是 Angular 应用程序的 Material Design UI 组件。
右键单击 Visual Studio 2022 Solution Explorer 中的 GlobalMarket
项目,然后单击“Open in Terminal”。
运行以下命令来安装 Angular Material。
ng add @angular/material
- 选择一个预建主题名称,或者选择“custom”来自定义主题。
- 设置全局 Angular Material 排版样式。
- 为 Angular Material 设置浏览器动画。
当然,我们还需要 Bootstrap,这是最受欢迎的 CSS 库。
npm install bootstrap --save
安装后,我们在 GlobalMarkt/projects/trading/src/styles.css 中导入 Bootstrap 和 Angular Material 的 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 提供了高质量的组件。您可以在此链接处查看。在使用这些组件之前,需要导入组件模块。以下是我们前端使用的组件:
mat-card
<mat-card>
是一个内容容器,用于在一个单一主题的上下文中展示文本、照片和操作。mat-form-field
<mat-form-field>
是一个组件,用于包装几个 Angular Material 组件,并应用通用的文本字段样式,例如下划线、浮动标签和提示消息。mat-select
<mat-select>
是一个表单控件,用于从一组选项中选择一个值,类似于原生的<select>
元素。mat-chip-list
<mat-chip-list>
以单独的、可访问键盘的芯片形式显示值的列表。mat-progress-bar
<mat-progress-bar>
是一个水平进度条,用于指示进度和活动。mat-divider
<mat-divider>
组件允许对带有各种方向选项的线条分隔符进行 Material 样式化。
在 app.module.ts 中导入这些模块。
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 { }
我们需要使用 mat-form-field
和 mat-select
,因此需要导入 FormsModule
和 ReactiveFormsModule
。
现在使用这些组件来修改 app.component.html。
整个页面分为两个 Card。上面的 Card 是一个 Symbol Card,另一个 Card 显示交易小部件。
<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>
显示 Symbol 下拉列表
打开 AppComponent
类文件并定义 symbols 数组。
symbols = ['MSFT',
'AAPL',
'AMZN',
'TSLA',
'WTC',
'BTCUSD',
'ETHUSD',
'CLN2022'
];
打开 AppComponent
模板文件以添加 <mat-select>
。使用 ngFor
显示列表,并将选定的符号值绑定到 widgetConfig.symbol
。
<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>
当选定的符号更改时,我们希望重新加载交易视图小部件。打开 AppComponent
类文件以添加 OnSymbolChange
事件。
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();
}
在 Angular 中,调用 ngOnInit
会刷新组件。
显示获取报价结果
添加一个新的 app.model
类(app.model.ts),然后定义 Quote
接口和 DataItem
接口。
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;
}
打开 AppComponent
类文件以调用后端获取报价 API。
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));
打开 AppComponent 模板文件以添加 <mat-chip-list>
来显示获取报价结果。
<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>
元素可以通过拖放改变顺序。在 AppComponent
类中添加 drop
事件。
drop(event: CdkDragDrop<DataItem[]>) {
moveItemInArray(this.data, event.previousIndex, event.currentIndex);
}
显示进度条
打开 AppComponent 模板文件以添加 <mat-progress-bar>
。
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isProgressing">
</mat-progress-bar>
打开 AppComponent
类文件以设置 isProgressing
。在调用 API 之前,将 isProgessing
设置为 true
,然后在订阅回调中将 isProgressing
设置为 false
。
this.isProgressing = true;
this.http.get<Quote>(`/finance/quote?symbol=
${this.widgetConfig.symbol}`).subscribe(result => {
...
this.isProgressing = false;
}, error => console.error(error));
Global Market 全新外观
现在,经过后端和前端的更改后,我们的应用程序焕然一新。按 F5 或点击顶部菜单栏上的“Start”按钮。如我们之前配置的,GlobalMarketAPI
(后端) 和 GlobalMarket
(前端) 都将启动。
您可以拖放“Price”、“Day High”、“Day Low”、“Ask”、“Bid”来随意更改它们的顺序。
在符号下拉列表中将符号更改为 AAPL – Apple。
然后您可以看到 Apple 公司的交易图表和报价信息。
正如您所知,加密货币最近暴跌。所以我真的很想看看比特币的交易图表。在下拉列表中将符号更改为“BTCUSD”(比特币)。
哎呀,真是场悲剧。
但是等等,为什么没有报价信息?让我们来调试一下。
调试 Angular 前端和 .NET 后端
在前端,我们在调用 get quote API 和订阅回调处设置了断点。
在后端,我们在 finance controller 的 GetQuote
函数处设置了断点。
现在将符号更改为 BTCUSD
。
第一个断点被触发。您可以看到我们正确地传递了符号“BTCUSD
”。
按 F5 或点击菜单栏上的“Continue”让它继续执行。
finance controller 的 GetQuote
处的断点被触发。您还可以看到符号“BTCISD
”已正确传递。
让我们按 F11 进入。
Yahoo Finance API URL 也正确。我们在这里设置一个断点来捕获异常。
按 F10 单步执行。
没有发生异常,但结果显示为空。这意味着 Yahoo Finance 找不到关于此符号的任何信息。为什么?这是因为 Yahoo Finance 的符号并不都与 Trading View 的符号相同。例如,Yahoo Finance 中“BTCUSD
”的符号是“BTC-USD
”,而“ETHUSD
”的符号是“ETH-USD
”等等。
为了解决这个问题,我们需要在 Trading View 符号和 Yahoo Finance 符号之间添加映射。
我们可以将此映射添加到 GlobalMarketAPI
的应用程序设置中。
"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
– 广度科技全球BTCUSD
– 比特币ETHUSD
– 以太坊CLN2022
– 2022 年 7 月原油
现在在 GlobalMarketAPI
的 YahooFinaceSettings.cs 中添加字典。它将在程序启动时加载。
namespace GlobalMarketAPI.Settings
{
public class YahooFinanceSettings
{
public string? APIKey { get; set; }
public string? BaseURL { get; set; }
public Dictionary<string, string>? SymbolMapInfo
{
get;
set;
}
}
}
打开 FinanceService
类,添加 symbol map 字典。
private readonly Dictionary<string, string> _symbolMap = new Dictionary<string, string>();
在构造函数中设置此字典。
if (settings.SymbolMapInfo != null && settings.SymbolMapInfo.Count > 0)
{
_symbolMap = settings.SymbolMapInfo.ToDictionary(x => x.Key, x => x.Value);
}
如果符号有映射,则替换该符号。
symbol = _symbolMap.ContainsKey(symbol) ? _symbolMap[symbol] : symbol;
现在再次调试。
Symbol
是映射后的符号,“BTC-USD
”。
Yahoo Finance API 返回结果。
按“F5”继续。Angular http client 回调函数处的断点被触发。
按“F5”继续。最终,获取到比特币报价信息。
如何使用源代码
下载并解压源代码,使用 Visual Studio 2022 打开 GlobalMaket.sln。
然后,在 **Solution Explorer** 中右键单击 GlobalMarket
项目,选择“Open in Terminal”。
需要先构建 tradingview-widget
库。
npm install
ng build tradingview-widget
结论
接续第一部分,在本文中,我们集成了 Angular 前端和 ASP.NET Core Web API,并使用 Angular Material 对前端进行了样式化。最后,我们向您展示了在 Visual Studio 2022 中轻松地一起调试前端和后端的方法。您可以从github获取所有源代码。祝您编码愉快!
历史
- 2022 年 5 月 16 日:初始版本