目录
介绍
API控制器
添加CitiesController
使用EntiftyFrameworkCore添加数据库持久性
创建数据库上下文
将Serilog添加到.NET Core应用程序
安装库
在您的应用程序中配置Serilog
为.NET Core App添加连接字符串
ASP.NET Core中的配置
将配置绑定到您的类
添加DbContextFactory类
IDbContextFactory接口
DbContextFactory类
添加泛型存储库类
IRepository接口
Repository 类
Async和Await
添加特定的CityRepository类
ICityRepository接口
CityRepository类
使用.NET Core进行依赖注入
注入DbContextFactory和CityRepository
services.AddTransient,service.AddScoped和service.AddSingleton之间的区别
添加CityService类
ICityService接口
CityService类
依赖注入CityService
在CitiesController中调用CityService
从Angular前端调用API
天气组件
在构造函数中导入CityServer和Inject
保存用户选择的城市
从ngOnInit获取最后访问的城市
从前端到后端的调试
结论
- 下载源代码 - 2.5 MB
在Angular 7 和 .Net Core 2.2——全球天气(第1部分)中,我们讨论了如何使用.NET Core 2.2逐步构建Angular 7应用程序。在本文中,我们将创建.NET Core API以保存用户选择的位置,并在用户再次访问时填充最新位置。
API控制器与ASP.NET相比,ASP.NET Core为开发人员提供了更好的性能,并且是为跨平台执行而构建的。使用ASP.NET Core,您的解决方案在Linux上也可以像在Windows上一样工作。
在Web API中,controller是处理HTTP请求的对象。我们将添加一个controller,其可以返回并保存最新访问城市的内容。
添加CitiesController首先,删除ValuesController,这是使用项目模板自动创建的。在解决方案资源管理器中,右键单击ValuesController.cs,然后将其删除。
然后在解决方案资源管理器中,右键单击Controllers文件夹。选择Add,然后选择Controller。
在Add Scaffold对话框中,选择Web API Controller - Empty。单击添加。
在“添加控制器”对话框中,将控制器命名为“ CitiesController”。单击添加。
脚手架在Controllers文件夹中创建名为CitiesController.cs的文件。
暂时离开控制器,稍后再回来。
使用EntiftyFrameworkCore添加数据库持久性实体框架(EF)Core是流行的实体框架数据访问技术的轻量级、可扩展和跨平台版本。
EF Core可以作为对象关系映射器(O / RM),使.NET开发人员能够使用.NET对象使用数据库,并且无需他们通常需要编写的大多数数据访问代码。
在解决方案管理器中,添加新项目。
选择“类库(.NET Core)”模板并将项目命名为“ Weather.Persistence”。单击“确定”。Weather.Persistence项目在GlobalWeather解决方案下创建。
删除Class1.cs。右键单击Weather.Persistence项目以选择“管理Nuget包”。
在Nuget Window中,为Entity Framework Core安装依赖包。它们是Microsoft.EntityFrameworkCore,Microsoft.EntityFrameworkCore.Design,Microsoft.EntityFrameworkCore.Relational和Microsoft.EntityFrameworkCore.SqlServer。
此外,我们还为依赖注入,应用程序配置和日志记录安装了一些额外的包。它们是Microsoft.Extensions.DependencyInjection,Microsoft.Extensions.Options.ConfigurationExtensions和Serilog。
使用EF Core,可以使用模型执行数据访问。模型由实体类和派生上下文组成,它们表示与数据库的会话,允许您查询和保存数据。
您可以从现有数据库生成模型,手动编写模型以匹配数据库,或使用EF迁移从模型创建数据库。
这里我们使用Database First,从现有数据库生成模型。
在Microsoft SQL Server Management Studio中创建Weather数据库。然后,运行以下脚本:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Cities](
[Id] [nvarchar](255) NOT NULL,
[Name] [nvarchar](255) NOT NULL,
[CountryId] [nvarchar](255) NOT NULL,
[AccessedDate] [datetimeoffset](7) NOT NULL
PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
现在,它已准备好创建Entity Framework数据上下文和数据模型。下面是dbcontext scaffold命令,它将自动创建dbContext类和数据模型类。
dotnet ef dbcontext scaffold "Server=.\sqlexpress;Database=Weather;
Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -o Models -c "WeatherDbContext" -f
在我们运行dbcontext scaffold命令之前,我们需要考虑数据模型的复数和单一命名问题。
通常,我们创建具有复数名称的表,如“ Cities”。作为一个数据集,命名Cities是有意义的,但如果我们将模型类命名为“ Cities” 则没有任何意义。预期的模型类名称应为“City”。如果我们不做任何事情,只需立即运行scaffold命令。生成的数据上下文和模型类如下所示:
你可以看到,它生成了Cities模型类。然后看看WeatherDbContext类。
public partial class WeatherDbContext : DbContext
{
public WeatherDbContext()
{
}
public WeatherDbContext(DbContextOptions options)
: base(options)
{
}
public virtual DbSet Cities { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
#warning To protect potentially sensitive information in your connection string,
you should move it out of source code. See http://go.microsoft.com/fwlink/?LinkId=723263
for guidance on storing connection strings.
optionsBuilder.UseSqlServer("Server=.\\sqlexpress;Database=Weather;
Trusted_Connection=True;");
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasAnnotation("ProductVersion", "2.2.1-servicing-10028");
modelBuilder.Entity(entity =>
{
entity.Property(e => e.Id)
.HasMaxLength(255)
.ValueGeneratedNever();
entity.Property(e => e.CountryId)
.IsRequired()
.HasMaxLength(255);
entity.Property(e => e.Name)
.IsRequired()
.HasMaxLength(255);
});
}
}
}
DbSet也被称为Cities。多丑啊!
但实际上,Entity Framework Core 2支持多元化和单一化。
有一个新的IPluralizer interface。当EF生成数据库(dotnet ef数据库更新)或实体从中生成类(Scaffold-DbContext)时,它可用于复数表名。使用它的方法有点棘手,因为我们需要一个类实现IDesignTimeServices,这些类将由这些工具自动发现。
有一个Nuget包Inflector实现IPluralizer interface。
对我来说,我将Inflector中的Pluaralizer.cs 添加到我们的持久性项目中。
public class MyDesignTimeServices : IDesignTimeServices
{
public void ConfigureDesignTimeServices(IServiceCollection services)
{
services.AddSingleton();
}
}
public class Pluralizer : IPluralizer
{
public string Pluralize(string name)
{
return Inflector.Pluralize(name) ?? name;
}
public string Singularize(string name)
{
return Inflector.Singularize(name) ?? name;
}
}
public static class Inflector
{
#region Default Rules
static Inflector()
{
AddPlural("$", "s");
AddPlural("s$", "s");
AddPlural("(ax|test)is$", "$1es");
AddPlural("(octop|vir|alumn|fung)us$", "$1i");
AddPlural("(alias|status)$", "$1es");
AddPlural("(bu)s$", "$1ses");
AddPlural("(buffal|tomat|volcan)o$", "$1oes");
AddPlural("([ti])um$", "$1a");
AddPlural("sis$", "ses");
AddPlural("(?:([^f])fe|([lr])f)$", "$1$2ves");
AddPlural("(hive)$", "$1s");
AddPlural("([^aeiouy]|qu)y$", "$1ies");
AddPlural("(x|ch|ss|sh)$", "$1es");
AddPlural("(matr|vert|ind)ix|ex$", "$1ices");
AddPlural("([m|l])ouse$", "$1ice");
AddPlural("^(ox)$", "$1en");
AddPlural("(quiz)$", "$1zes");
AddSingular("s$", "");
AddSingular("(n)ews$", "$1ews");
AddSingular("([ti])a$", "$1um");
AddSingular("((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$", "$1$2sis");
AddSingular("(^analy)ses$", "$1sis");
AddSingular("([^f])ves$", "$1fe");
AddSingular("(hive)s$", "$1");
AddSingular("(tive)s$", "$1");
AddSingular("([lr])ves$", "$1f");
AddSingular("([^aeiouy]|qu)ies$", "$1y");
AddSingular("(s)eries$", "$1eries");
AddSingular("(m)ovies$", "$1ovie");
AddSingular("(x|ch|ss|sh)es$", "$1");
AddSingular("([m|l])ice$", "$1ouse");
AddSingular("(bus)es$", "$1");
AddSingular("(o)es$", "$1");
AddSingular("(shoe)s$", "$1");
AddSingular("(cris|ax|test)es$", "$1is");
AddSingular("(octop|vir|alumn|fung)i$", "$1us");
AddSingular("(alias|status)$", "$1");
AddSingular("(alias|status)es$", "$1");
AddSingular("^(ox)en", "$1");
AddSingular("(vert|ind)ices$", "$1ex");
AddSingular("(matr)ices$", "$1ix");
AddSingular("(quiz)zes$", "$1");
AddIrregular("person", "people");
AddIrregular("man", "men");
AddIrregular("child", "children");
AddIrregular("sex", "sexes");
AddIrregular("move", "moves");
AddIrregular("goose", "geese");
AddIrregular("alumna", "alumnae");
AddUncountable("equipment");
AddUncountable("information");
AddUncountable("rice");
AddUncountable("money");
AddUncountable("species");
AddUncountable("series");
AddUncountable("fish");
AddUncountable("sheep");
AddUncountable("deer");
AddUncountable("aircraft");
}
#endregion
private class Rule
{
private readonly Regex _regex;
private readonly string _replacement;
public Rule(string pattern, string replacement)
{
_regex = new Regex(pattern, RegexOptions.IgnoreCase);
_replacement = replacement;
}
public string Apply(string word)
{
if (!_regex.IsMatch(word))
{
return null;
}
return _regex.Replace(word, _replacement);
}
}
private static void AddIrregular(string singular, string plural)
{
AddPlural("(" + singular[0] + ")" +
singular.Substring(1) + "$", "$1" + plural.Substring(1));
AddSingular("(" + plural[0] + ")" +
plural.Substring(1) + "$", "$1" + singular.Substring(1));
}
private static void AddUncountable(string word)
{
_uncountables.Add(word.ToLower());
}
private static void AddPlural(string rule, string replacement)
{
_plurals.Add(new Rule(rule, replacement));
}
private static void AddSingular(string rule, string replacement)
{
_singulars.Add(new Rule(rule, replacement));
}
private static readonly List _plurals = new List();
private static readonly List _singulars = new List();
private static readonly List _uncountables = new List();
public static string Pluralize(this string word)
{
return ApplyRules(_plurals, word);
}
public static string Singularize(this string word)
{
return ApplyRules(_singulars, word);
}
#if NET45 || NETFX_CORE
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
private static string ApplyRules(List rules, string word)
{
string result = word;
if (!_uncountables.Contains(word.ToLower()))
{
for (int i = rules.Count - 1; i >= 0; i--)
{
if ((result = rules[i].Apply(word)) != null)
{
break;
}
}
}
return result;
}
public static string Titleize(this string word)
{
return Regex.Replace(Humanize(Underscore(word)), @"\b([a-z])",
delegate(Match match) { return match.Captures[0].Value.ToUpper(); });
}
public static string Humanize(this string lowercaseAndUnderscoredWord)
{
return Capitalize(Regex.Replace(lowercaseAndUnderscoredWord, @"_", " "));
}
public static string Pascalize(this string lowercaseAndUnderscoredWord)
{
return Regex.Replace(lowercaseAndUnderscoredWord, "(?:^|_)(.)",
delegate(Match match) { return match.Groups[1].Value.ToUpper(); });
}
public static string Camelize(this string lowercaseAndUnderscoredWord)
{
return Uncapitalize(Pascalize(lowercaseAndUnderscoredWord));
}
public static string Underscore(this string pascalCasedWord)
{
return Regex.Replace(
Regex.Replace(
Regex.Replace(pascalCasedWord, @"([A-Z]+)([A-Z][a-z])", "$1_$2"), @"([a-z\d])([A-Z])",
"$1_$2"), @"[-\s]", "_").ToLower();
}
public static string Capitalize(this string word)
{
return word.Substring(0, 1).ToUpper() + word.Substring(1).ToLower();
}
public static string Uncapitalize(this string word)
{
return word.Substring(0, 1).ToLower() + word.Substring(1);
}
public static string Ordinalize(this string numberString)
{
return Ordanize(int.Parse(numberString), numberString);
}
public static string Ordinalize(this int number)
{
return Ordanize(number, number.ToString());
}
#if NET45 || NETFX_CORE
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
private static string Ordanize(int number, string numberString)
{
int nMod100 = number % 100;
if (nMod100 >= 11 && nMod100 _dbContextFactory?.DbContext;
///
/// Get Entity
///
///
///
public async Task GetEntity(object id)
{
var entity = await DbContext.FindAsync(id);
return entity;
}
///
/// Add Entity
///
///
///
public async Task AddEntity(TEntity entity)
{
try
{
var result = await DbContext.AddAsync(entity);
await DbContext.SaveChangesAsync();
return result.Entity;
}
catch (Exception ex)
{
Logger.Error(ex, "Unhandled Exception");
throw;
}
}
///
/// Update Entity
///
///
///
public async Task UpdateEntity(TEntity entity)
{
DbContext.Update(entity);
await DbContext.SaveChangesAsync();
return entity;
}
///
/// Delete Entity
///
///
///
public async Task DeleteEntity(object id)
{
var entity = await DbContext.FindAsync(id);
if (entity != null)
{
DbContext.Remove(entity);
await DbContext.SaveChangesAsync();
}
return true;
}
}
Async和Await
我们在Entity Framework Core查询中使用async和await模式查询和保存数据。通过使用异步编程,您可以避免性能瓶颈并提高应用程序的整体响应能力。
异步对于可能阻塞的活动(例如Web访问)至关重要。访问Web资源有时会很慢或延迟。如果在同步过程中阻止此类活动,则整个应用程序必须等待。在异步过程中,在潜在阻塞任务完成之前,应用程序可以继续执行不依赖于Web资源的其他工作。
异步对于访问UI线程的应用程序尤其有用,因为所有与UI相关的活动通常共享一个线程。如果在同步应用程序中阻止了任何进程,则会阻止所有进程。您的应用程序停止响应,您可能会认为它失败了,而不是等待。
添加特定的CityRepository类泛型Repository类仅具有实体dataset的通用方法和属性。有时,某些dataset需要一些更具体的方法和属性。对于这些实体,我们需要创建派生自泛型repository类的repository子类。
我们需要做的任务是获取并保存最后访问的城市。所以我们需要向CityRepository class中添加InsertOrUpdateCityAsync和GetLastAccessedCityAsync方法。
我们在Weather.Persistence项目的Repositories 文件夹中增加ICityRepository interface和CityRepository class。
ICityRepository接口public interface ICityRepository : IRepository
{
Task GetLastAccessedCityAsync();
Task InsertOrUpdateCityAsync(City city);
}
CityRepository类
public class CityRepository : Repository, ICityRepository
{
public CityRepository(IDbContextFactory dbContextFactory, ILogger logger) :
base(dbContextFactory, logger)
{
}
///
/// GetLastAccessedCityAsync
///
/// City
public async Task GetLastAccessedCityAsync()
{
var city = await DbContext.Cities.OrderByDescending(x=>x.AccessedDate).FirstOrDefaultAsync();
return city;
}
///
/// InsertOrUpdateCityAsync
///
///
///
public async Task InsertOrUpdateCityAsync(City city)
{
var entity = await GetEntity(city.Id);
if (entity != null)
{
entity.Name = city.Name;
entity.CountryId = city.CountryId;
entity.AccessedDate = city.AccessedDate;
await UpdateEntity(entity);
}
else
{
await AddEntity(city);
}
}
}
使用.NET Core进行依赖注入
为了解决对服务实现的引用进行硬编码的问题,依赖注入提供了一个间接级别,使得客户端(或应用程序)不是直接使用new运算符实例化服务,而是要求服务集合或“工厂”实例。此外,你不是要求服务集合获取特定类型(从而创建紧密耦合的引用),而是要求一个interface,期望服务提供者实现该interface。
结果是,虽然客户端将直接引用abstract程序集,定义服务interface,但不需要引用直接实现。
依赖注入注册客户端请求的类型(通常为interface)与将返回的类型之间的关联。此外,依赖注入通常确定返回类型的生存期,具体而言,是否将在所有类型请求之间共享单个实例,每个请求的新实例或其间的内容。
对依赖注入的一个特别常见的需求是在单元测试中。所需要的只是单元测试“配置”DI框架以返回模拟服务。
提供“服务”的实例而不是让客户端直接实例化是依赖注入的基本原则。
要利用.NET Core DI框架,您只需要引用Microsoft.Extensions.DependencyInjection.AbstractionsNuGet包。这提供访问IServiceCollection interface,其暴露了System.IServiceProvider,在其中你可以调用GetService。type参数,TService,标识要检索的服务的类型(通常为interface),因此应用程序代码获取实例。
注入DbContextFactory和CityRepository在Weather.Persistence项目的Repositories 文件夹中添加RespositoryInjectionModule static class。这个 static class为IServiceCollection添加了扩展方法。
public static class RepositoryInjectionModule
{
///
/// Dependency inject DbContextFactory and CustomerRepository
///
///
///
public static IServiceCollection InjectPersistence(this IServiceCollection services)
{
services.AddScoped();
services.AddTransient();
return services;
}
}
然后添加services.InjectPersistence()到Startup.cs的ConfigureService中。
public void ConfigureServices(IServiceCollection services)
{
services.Configure(Configuration);
//Inject logger
services.AddSingleton(Log.Logger);
services.InjectPersistence();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "WeatherClient/dist";
});
}
services.AddTransient,service.AddScoped和service.AddSingleton之间的区别
为每个注册的服务选择适当的生命周期。可以使用以下生命周期配置ASP.NET Core服务:
瞬态对象总是不同的; 为每个控制器和每个服务提供一个新实例。
范围内的对象在请求中是相同的,但在不同的请求中是不同的。
单例对象对于每个对象和每个请求都是相同的。
添加CityService类我不想直接从API控制器调用存储库,最佳做法是添加服务。然后从服务调用存储库。
右键单击GlobalWeather项目以添加新文件夹“Services”。添加ICityService interface和CityService class到这个文件夹。
ICityService接口public interface ICityService
{
Task GetLastAccessedCityAsync();
Task UpdateLastAccessedCityAsync(City city);
}
CityService类
public class CityService : ICityService
{
private readonly ICityRepository _repository;
private readonly ILogger _logger;
public CityService(ICityRepository repository, ILogger logger)
{
_repository = repository;
_logger = logger;
}
///
/// GetLastAccessedCityAsync
///
/// City
public async Task GetLastAccessedCityAsync()
{
var city = await _repository.GetLastAccessedCityAsync();
return city;
}
///
/// UpdateLastAccessedCityAsync
///
///
///
public async Task UpdateLastAccessedCityAsync(City city)
{
city.AccessedDate = DateTimeOffset.UtcNow;
await _repository.InsertOrUpdateCityAsync(city);
}
}
依赖注入CityService
添加ServiceInjectionModule static class到Services 文件夹。与之前相同,这个static class为IServiceCollection添加了另一种扩展方法。
public static class ServiceInjectionModule
{
///
/// Dependency inject services
///
///
///
public static IServiceCollection InjectServices(this IServiceCollection services)
{
services.AddTransient();
return services;
}
}
然后添加services.InjectServices ()到Startup.cs的ConfigureService中。
public void ConfigureServices(IServiceCollection services)
{
services.Configure(Configuration);
//Inject logger
services.AddSingleton(Log.Logger);
services.InjectPersistence();
services.InjectServices();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "WeatherClient/dist";
});
}
在CitiesController中调用CityService
现在是时候更新CitiesController了。
首先,在构造函数中注入CityService和Logger实例。
public CitiesController(ICityService service, ILogger logger)
{
_service = service;
_logger = logger;
}
添加HttpGet以获取上次访问的city。
// GET api/cities
[HttpGet]
public async Task Get()
{
var city = await _service.GetLastAccessedCityAsync();
return city;
}
添加HttpPost以保存city。
[HttpPost]
public async Task Post([FromBody] City city)
{
await _service.UpdateLastAccessedCityAsync(city);
}
从Angular前端调用API
现在,我们需要回到Angular前端来调用CityAPI来保存并获取最后一次访问的city。
首先,我们需要创建一个model类来映射json。
在src/app/shared/models/ 文件夹下创建一个名为city-meta-data的文件。定义CityMetaData class并导出它。该文件应如下所示:
import { City } from './city';
export class CityMetaData {
public id: string;
public name: string;
public countryId: string;
public constructor(city: City) {
this.id = city.Key;
this.name = city.EnglishName;
this.countryId = city.Country.ID;
}
}
打开src/app/app.constants.ts下的app.constants.ts。添加一个新常量,即City API网址。你应该知道,这个网址是一个相对网址。相对的URL确保它适用于任何环境。
static cityAPIUrl = '/api/cities';
在src/app/shared/services/文件夹中创建一个service调用city。
ng generate service city
在src/app/city.service.ts中的命令生成骨架CityService class。
然后在CityService class中添加getLastAccessedCity和updateLastAccessedCity方法。
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { Constants } from '../../../app/app.constants';
import { City } from '../models/city';
import { CityMetaData } from '../models/city-meta-data';
import { catchError, map, tap } from 'rxjs/operators';
import { ErrorHandleService } from './error-handle.service';
@Injectable({
providedIn: 'root'
})
export class CityService {
constructor(
private http: HttpClient,
private errorHandleService: ErrorHandleService) { }
getLastAccessedCity(): Observable {
const uri = decodeURIComponent(`${Constants.cityAPIUrl}`);
return this.http.get(uri)
.pipe(
map(res => {
const data = res as CityMetaData;
const city = {
Key: data.id,
EnglishName: data.name,
Type: 'City',
Country:
{
ID: data.countryId,
EnglishName: ''
}
};
return city;
}),
tap(_ => console.log('fetched the last accessed city')),
catchError(this.errorHandleService.handleError('getLastAccessedCity', null))
);
}
updateLastAccessedCity(city: City) {
const uri = decodeURIComponent(`${Constants.cityAPIUrl}`);
var data = new CityMetaData(city);
return this.http.post(uri, data)
.pipe(
catchError(this.errorHandleService.handleError('updateLastAccessedCity', []))
);
}
}
天气组件
打开src/app/weather/weather.component.ts下的weather.component.ts。
在构造函数中导入CityServer和Injectconstructor(
private fb: FormBuilder,
private locationService: LocationService,
private currentConditionService: CurrentConditionsService,
private cityService: CityService) {
}
保存用户选择的城市
添加UpdateLastAccessedCity方法。
async updateLastAccessedCity(city: City) {
const promise = new Promise((resolve, reject) => {
this.cityService.updateLastAccessedCity(city)
.toPromise()
.then(
_ => { // Success
resolve();
},
err => {
console.error(err);
//reject(err);
resolve();
}
);
});
await promise;
}
得到city之后调用它。
async search() {
this.weather = null;
this.errorMessage = null;
const searchText = this.cityControl.value as string;
if (!this.city ||
this.city.EnglishName !== searchText ||
!this.city.Key ||
!this.city.Country ||
!this.city.Country.ID) {
await this.getCity();
await this.updateLastAccessedCity(this.city);
}
await this.getCurrentConditions();
}
从ngOnInit获取最后访问的城市
添加getLastAccessedCity方法。
async getLastAccessedCity() {
const promise = new Promise((resolve, reject) => {
this.cityService.getLastAccessedCity()
.toPromise()
.then(
res => { // Success
const data = res as City;
if (data) {
this.city = data;
}
resolve();
},
err => {
console.error(err);
//reject(err);
resolve();
}
);
});
await promise;
if (this.city) {
const country = this.countries.filter(x => x.ID === this.city.Country.ID)[0];
this.weatherForm.patchValue({
searchGroup: {
country: country,
city: this.city.EnglishName
}
});
}
}
获取最后一次访问的city后,修补响应式表单字段。
从ngOnInit调用getLastAccessedCity。
async ngOnInit() {
this.weatherForm = this.buildForm();
await this.getCountries();
await this.getLastAccessedCity();
this.errorMessage = null;
if (this.weatherForm.valid)
await this.search();
else {
this.errorMessage = "Weather is not available. Please specify a location.";
}
}
从前端到后端的调试
好。现在我从头到尾展示整个工作流程。
在Chrome中,我们将断点放在WeatherComponent。其一是在第43行,ngOnInit的getLastAccessedCity。另一个是行231, Search的updateLastAccessedCity。
在Visual Studio中,将断点放在CitiesController.cs中。一个在Get,另一个在Post。
在Country字段中,选择Australia,然后输入Geelong。然后点击转到(Go)按钮,你可以看到在Search函数中的updateLastAccessedCity被中断。
点击“继续”。
然后在CitiesController中的Post方法被中断。
单击“继续”或按F5。Geelong保存到数据库中。
刷新Chrome。在ngOnInit中的getLastAccessedCity被中断。
点击“继续 ”,在CitiesContoller中的Http Get方法被中断。
构建一个出色的API依赖于伟大的架构。在本文中,我们构建了一个.NET Core 2.2 Web API,并介绍了.NET Core基础知识,如Entity Framework Core,依赖注入以及Angular和.NET Core的完全集成。现在您知道从ASP.Net Core构建Web API有多容易。
下一篇:Angular 7和.NET Core 2.2——全球天气(第3部分)
原文地址:https://www.codeproject.com/Articles/1276248/Angular-7-with-NET-Core-2-2-Global-Weather-Part-2