Angular 7和 .NET Core 2.2——全球天气(第2部分)
目录
介绍
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<WeatherDbContext> options): base(options){}public virtual DbSet<Cities> 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<Cities>(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<IPluralizer, Pluralizer>();}
}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 Rulesstatic 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");}#endregionprivate 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<Rule> _plurals = new List<Rule>();private static readonly List<Rule> _singulars = new List<Rule>();private static readonly List<string> _uncountables = new List<string>();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)]
#endifprivate static string ApplyRules(List<Rule> 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)]
#endifprivate static string Ordanize(int number, string numberString){int nMod100 = number % 100;if (nMod100 >= 11 && nMod100 <= 13){return numberString + "th";}switch (number % 10){case 1:return numberString + "st";case 2:return numberString + "nd";case 3:return numberString + "rd";default:return numberString + "th";}}public static string Dasherize(this string underscoredWord){return underscoredWord.Replace('_', '-');}
}
然后在Powershell中,运行以下命令,转到GlobalWeather\Weather.Persistence文件夹,运行以下命令:
dotnet ef dbcontext scaffold "Server=.\sqlexpress;Database=Weather;
Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -o Models -c "WeatherDbContext" -f
运行此命令后,将在Models文件夹下生成City.cs和WeatherDbContext.cs。
请注意city的模板是“City”不是“Cities”。它比以前好多了。
将Serilog添加到.NET Core应用程序
与其他日志库不同,Serilog使用强大的结构化事件数据构建。Serilog有一个很好的故事,可以使用Serilog.AspNetCore库添加日志记录到ASP.NET核心应用程序,以及大量可用的接收器列表。
安装库
您可以使用包管理器将Serilog NuGet包安装到您的应用程序中。您还需要添加至少一个“接收器”——这是Serilog将写入日志消息的位置。例如,Serilog.Sinks.Console将消息写入控制台。
右键单击GlobalWeather项目以选择“管理Nuget包 ”。
在您的应用程序中配置Serilog
还原软件包后,您可以配置要使用Serilog的应用程序。推荐的方法是首先配置Serilog的static Log.Logger对象,请在配置ASP.NET的核心应用程序之前。
首先在appsettings.json中,使用以下Serilog配置替换默认日志记录。
"Serilog": {"MinimumLevel": "Debug","WriteTo": [{"Name": "File","Args": {"path": "log\\log.txt","rollingInterval": "Day"}}]}
然后,在默认的Startup.cs类中进行一些更改。
在Startup配置的方法中添加以下行来配置Log.Logger。
Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(Configuration).CreateLogger();
在ConfigureServices方法中添加以下行以进行Log.Logger注入。
services.AddSingleton(Log.Logger);
为.NET Core App添加连接字符串
ASP.NET Core中的配置
在.NET Core中,配置系统非常灵活,连接字符串可以存储在appsettings.json中,环境变量,用户密码存储或其他配置源中。现在我们显示存储在appsettings.json中的连接字符串。
在GlobalWeather项目文件夹下打开appsettings.json,并添加以下行:
"DbConnectionString": "Server=.\\sqlexpress;Database=Weather;Trusted_Connection=True;"
ASP.NET Core中的应用程序配置基于配置提供程序建立的键值对。配置提供程序将配置数据从各种配置源读取为键值对。
选项模式是配置概念的扩展。选项使用类来表示相关设置组。
使用无public参数构造函数的options类必须是非抽象的。
为Weather.Persistence项目创建DbContextSettings类。
public class DbContextSettings
{/// <summary>/// DbConnectingString from appsettings.json/// </summary>public string DbConnectionString { get; set; }
}
将配置绑定到您的类
设置ConfigurationBuilder以加载文件。从默认模板创建新的ASP.NET Core应用程序时,ConfigurationBuilder已在Startup.cs中配置以从环境变量appsettings.json加载设置。
要将settings类绑定到您的配置,您需要在Startup.cs的ConfigureServices方法中配置它。
public void ConfigureServices(IServiceCollection services)
{services.Configure<DbContextSettings>(Configuration);
}
因为DBContextSettings是在Weather.Persistence项目中定义的,所以必须向Weather.Persistence项目添加GlobalWeather项目引用。
右键单击GlobalWeather项目的依赖,然后选择“Add reference”。在“参考管理器”窗口中,选择“ Weather.Persistence”,然后单击“确定 ”。
添加Weather.Persistence.Config namespace后,编译错误消失了。
此外,因为我们从appsettings.json中读取连接字符串,我们可以从WeatherDbContext.cs中的OnConfiguring里删除硬编码的连接字符串。
删除以下行:
optionsBuilder.UseSqlServer("Server=.\\sqlexpress;Database=Weather;Trusted_Connection=True;");
添加DbContextFactory类
现在,我们可以在应用程序配置文件中使用连接字符串来创建DBContextFactory。
DbContextFactory类是一个factory创建Db上下文的类,这里是WeatherDbContext。
右键单击Weather.Persistence项目,添加Repositories文件夹。然后添加IDbContextFactory interface和DBContextFactory类。
IDbContextFactory接口
public interface IDbContextFactory
{WeatherDbContext DbContext { get; }
}
DbContextFactory类
public class DbContextFactory : IDbContextFactory, IDisposable
{/// <summary>/// Create Db context with connection string/// </summary>/// <param name="settings"></param>public DbContextFactory(IOptions<DbContextSettings> settings) {var options = new DbContextOptionsBuilder<WeatherDbContext>().UseSqlServer(settings.Value.DbConnectionString).Options;DbContext = new WeatherDbContext(options);}/// <summary>/// Call Dispose to release DbContext/// </summary>~DbContextFactory(){Dispose();}public WeatherDbContext DbContext { get; private set; }/// <summary>/// Release DB context/// </summary>public void Dispose(){DbContext?.Dispose();}
}
添加泛型存储库类
存储库模式是创建企业级应用程序的最流行模式之一。它限制我们直接使用应用程序中的数据,并为数据库操作、业务逻辑和应用程序的UI创建新层。
使用存储库模式有许多优点:
- 您的业务逻辑可以在没有数据访问逻辑的情况下进行单元测试
- 可以重用数据库访问代码。
- 您的数据库访问代码是集中管理的,因此很容易实现任何数据库访问策略,如缓存。
- 实现域逻辑很容易。
- 您的域实体或业务实体使用注释进行强类型化; 还有更多。
一般来说,每个数据集类有一个repository类。如果我们使用泛型repository,它将重用所有公共代码,并减少大多数重复代码。
我们在Weather.Persistence的项目的Repositories 文件夹中增加IRepository interface和Repository class。
IRepository接口
public interface IRepository<T> where T : class
{Task<T> GetEntity(object id);Task<T> AddEntity(T entity);Task<T> UpdateEntity(T entity);Task<bool> DeleteEntity(object id);
}
Repository 类
public class Repository<TEntity> : IRepository<TEntity>where TEntity : class
{private readonly IDbContextFactory _dbContextFactory;protected ILogger Logger;public Repository(IDbContextFactory dbContextFactory, ILogger logger){_dbContextFactory = dbContextFactory;Logger = logger;}protected WeatherDbContext DbContext => _dbContextFactory?.DbContext;/// <summary>/// Get Entity/// </summary>/// <param name="id"></param>/// <returns></returns>public async Task<TEntity> GetEntity(object id){var entity = await DbContext.FindAsync<TEntity>(id);return entity;}/// <summary>/// Add Entity/// </summary>/// <param name="entity"></param>/// <returns></returns>public async Task<TEntity> AddEntity(TEntity entity){try{var result = await DbContext.AddAsync<TEntity>(entity);await DbContext.SaveChangesAsync();return result.Entity;}catch (Exception ex){Logger.Error(ex, "Unhandled Exception");throw;}}/// <summary>/// Update Entity/// </summary>/// <param name="entity"></param>/// <returns></returns>public async Task<TEntity> UpdateEntity(TEntity entity){DbContext.Update<TEntity>(entity);await DbContext.SaveChangesAsync();return entity;}/// <summary>/// Delete Entity/// </summary>/// <param name="id"></param>/// <returns></returns>public async Task<bool> DeleteEntity(object id){var entity = await DbContext.FindAsync<TEntity>(id);if (entity != null){DbContext.Remove<TEntity>(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<City>
{Task<City> GetLastAccessedCityAsync();Task InsertOrUpdateCityAsync(City city);
}
CityRepository类
public class CityRepository : Repository<City>, ICityRepository
{public CityRepository(IDbContextFactory dbContextFactory, ILogger logger) : base(dbContextFactory, logger){}/// <summary>/// GetLastAccessedCityAsync/// </summary>/// <returns>City</returns>public async Task<City> GetLastAccessedCityAsync(){var city = await DbContext.Cities.OrderByDescending(x=>x.AccessedDate).FirstOrDefaultAsync();return city;}/// <summary>/// InsertOrUpdateCityAsync/// </summary>/// <param name="city"></param>/// <returns></returns>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<TService>。type参数,TService,标识要检索的服务的类型(通常为interface),因此应用程序代码获取实例。
注入DbContextFactory和CityRepository
在Weather.Persistence项目的Repositories 文件夹中添加RespositoryInjectionModule static class。这个 static class为IServiceCollection添加了扩展方法。
public static class RepositoryInjectionModule
{/// <summary>/// Dependency inject DbContextFactory and CustomerRepository/// </summary>/// <param name="services"></param>/// <returns></returns>public static IServiceCollection InjectPersistence(this IServiceCollection services){services.AddScoped<IDbContextFactory, DbContextFactory>();services.AddTransient<ICityRepository, CityRepository>();return services;}
}
然后添加services.InjectPersistence()到Startup.cs的ConfigureService中。
public void ConfigureServices(IServiceCollection services)
{services.Configure<DbContextSettings>(Configuration);//Inject loggerservices.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<City> 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;}/// <summary>/// GetLastAccessedCityAsync/// </summary>/// <returns>City</returns>public async Task<City> GetLastAccessedCityAsync(){var city = await _repository.GetLastAccessedCityAsync();return city;}/// <summary>/// UpdateLastAccessedCityAsync/// </summary>/// <param name="city"></param>/// <returns></returns>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
{/// <summary>/// Dependency inject services/// </summary>/// <param name="services"></param>/// <returns></returns>public static IServiceCollection InjectServices(this IServiceCollection services){services.AddTransient<ICityService, CityService>();return services;}
}
然后添加services.InjectServices ()到Startup.cs的ConfigureService中。
public void ConfigureServices(IServiceCollection services)
{services.Configure<DbContextSettings>(Configuration);//Inject loggerservices.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<ActionResult<City>> 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<City> {const uri = decodeURIComponent(`${Constants.cityAPIUrl}`);return this.http.get<CityMetaData>(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和Inject
constructor(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(_ => { // Successresolve();},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 => { // Successconst 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
Angular 7和 .NET Core 2.2——全球天气(第2部分)相关推荐
- Angular 7 和 .Net Core 2.2——全球天气(第1部分)
目录 介绍 设置Angular CLI环境 先决条件 npm包管理器 从Visual Studio 2017创建Asp.Net核心Web项目 使用Angular CLI创建天气客户端 天气信息REST ...
- Angular 7和.NET Core 2.2——全球天气(第3部分)
目录 介绍 单元测试 .NET Core中的单元测试 创建xUnit测试项目 什么是Bddfy? 单元测试存储库通用类 NSubstitute和Shouldly 运行和调试单元测试 API控制器的单元 ...
- 深入研究 Angular 和 ASP.NET Core 3.0
本文要点: 可以把多个 Angular 应用程序集成到 ASP.NET 网站中 把 Angular 代码打包成 Web 组件是引导 Angular 应用程序的好方法 可以把用 Angular 编写的 ...
- MeteoEarth全球天气
MeteoEarth全球天气 MeteoEarth是一款专业的天气软件,程序采用3d旋转地球设计,对天气.压力.降水.温度.锋利.运量都能做全方位的展示. 1.跟踪飓风和台风与风暴追踪器 2.访问数以 ...
- 全球天气网(tianqi.com)天气预报调用插件
全球天气网(tianqi.com)天气预报调用插件,本插件完全免费,具有如下特色: 1.中国全部市县区及全球2500多个主要城市实时和5天预报天气: 2.自动识别访问者ip所在城市判断城市,也可定制默 ...
- 有什么免费好用的全球天气api?
简单介绍几个,选你觉得合适的就行.(下面推荐的国内外的都有,访问速度会有些差别) 高德天气 API -天气查询-API文档-开发指南-Web服务 API | 高德地图API 知心天气 API -Hyp ...
- 单卡30秒预测未来10天全球天气,大模型“风乌”效果超DeepMind,来自上海人工智能实验室...
允中 发自 凹非寺 量子位 | 公众号 QbitAI 预测未来10天全球天气,仅需30秒. 这一成果来自全球中期天气预报大模型"风乌",这也是全球气象有效预报时间首次突破10天,并 ...
- 使用Angular和ASP.net Core的Raw Websockets迷你游戏
目录 介绍 计划 你需要什么 服务端 SquareService.cs angular应用 square.ts square-change-request.ts socket-message.ts A ...
- 【HMS Core 6.0全球上线】华为钥匙环服务,打造跨应用跨形态无缝登录体验
华为钥匙环服务(Keyring),是HMS Core在安全领域开放的全新服务,为全球开发者提供用户认证凭据(以下简称"凭据")本地存储和跨应用.跨形态共享能力,帮助您在安卓应用.快 ...
最新文章
- 整理《Mastering OpenCV with Practical Computer Vision Projects》中第8章用Eigenfaces或Fisherfaces进行人脸识别操作流程
- Atitit JAVA p2p设计与总结 JXTA 2
- iphone导出通讯录到安卓_如何把旧手机的便签数据转移到新的iPhone手机上?
- 遗传算法求解极大值问题
- 我用过的一些web.xml配置
- 关于 Hybris (SAP Commerce Cloud)产品的客户群
- FreeRTOS中断配置与临界段
- 技术分享丨数据仓库的建模与ETL实践技巧
- 怎么用deveco studio升级鸿蒙,华为鸿蒙DevEco studio2.0的安装和hello world运行教程
- 数据结构和算法系列13 五大查找之哈希查找
- 【网络安全工程师面试合集】—不要随便浏览一些奇怪的小网站哦
- js微信抢红包脚本代码_如何利用JavaScript来实现微信抢红包功能的示例代码
- Audio bringup I2C调试(三十一)
- 计算机组成第五章课后答案,计算机组成原理第5章部分习题参考答案
- JavaScript的js文件压缩和格式化工具
- 敏感词检测软件-在线敏感词批量检测免费
- 简单的数据库造数据方法
- 树莓派linux谷歌浏览器设置,可以挂在墙上的树莓派谷歌日历,树莓谷歌
- Linux 命令(184)—— at 命令
- 手冢国光对战幸村精市,谁才是最强初中生?
热门文章
- 怎样用springboot开发cs_SpringBoot如何进行学习!
- docker 镜像修改的配置文件自动还原_所以到底该如何修改 docker 容器的端口映射!!!...
- 年味PSD素材|非常喜庆吉祥手工剪纸
- 合成创意海报灵感|比技术更重要的是创意!
- 可爱圣诞节手绘手帐素材,增添情趣
- opengl 如何加阴影_零基础如何2个月上岗C++工程师(内附资料)
- 检查图层当中是否存在高程基准(C++)ArcObject
- EPTP 和 EPT 分页结构条目的格式
- Linux学习教程,Linux入门教程(超详细)| 网址推荐
- mybatis 详解------ 一级缓存、二级缓存(九)