您当前的位置: 首页 >  ar

寒冰屋

暂无认证

  • 2浏览

    0关注

    2286博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Angular 7和 .NET Core 2.2——全球天气(第2部分)

寒冰屋 发布时间:2019-03-02 21:04:28 ,浏览量: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

https://www.codeproject.com/KB/api/1276248/weather-app-icon.png

介绍

在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。

https://www.codeproject.com/KB/api/1276248/1-1.png

在Add Scaffold对话框中,选择Web API Controller - Empty。单击添加。

https://www.codeproject.com/KB/api/1276248/1-2.png

在“添加控制器”对话框中,将控制器命名为“ CitiesController”。单击添加。

https://www.codeproject.com/KB/api/1276248/1-3.png

脚手架在Controllers文件夹中创建名为CitiesController.cs的文件。

https://www.codeproject.com/KB/api/1276248/1-4.png

暂时离开控制器,稍后再回来。

使用EntiftyFrameworkCore添加数据库持久性

实体框架(EF)Cor​​e是流行的实体框架数据访问技术的轻量级、可扩展和跨平台版本。

EF Core可以作为对象关系映射器(O / RM),使.NET开发人员能够使用.NET对象使用数据库,并且无需他们通常需要编写的大多数数据访问代码。

在解决方案管理器中,添加新项目。

https://www.codeproject.com/KB/api/1276248/2-1.png

选择“类库(.NET Core)”模板并将项目命名为“ Weather.Persistence”。单击“确定”。Weather.Persistence项目在GlobalWeather解决方案下创建。

https://www.codeproject.com/KB/api/1276248/2-2.png

删除Class1.cs。右键单击Weather.Persistence项目以选择“管理Nuget包”。

https://www.codeproject.com/KB/api/1276248/2-3.png

在Nuget Window中,为Entity Framework Core安装依赖包。它们是Microsoft.EntityFrameworkCore,Microsoft.EntityFrameworkCore.Design,Microsoft.EntityFrameworkCore.Relational和Microsoft.EntityFrameworkCore.SqlServer。

此外,我们还为依赖注入,应用程序配置和日志记录安装了一些额外的包。它们是Microsoft.Extensions.DependencyInjection,Microsoft.Extensions.Options.ConfigurationExtensions和Serilog。

https://www.codeproject.com/KB/api/1276248/2-4.png

创建数据库上下文

使用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命令。生成的数据上下文和模型类如下所示:

https://www.codeproject.com/KB/api/1276248/2-7.png

你可以看到,它生成了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程序集,定义服务inter­face,但不需要引用直接实现。

依赖注入注册客户端请求的类型(通常为interface)与将返回的类型之间的关联。此外,依赖注入通常确定返回类型的生存期,具体而言,是否将在所有类型请求之间共享单个实例,每个请求的新实例或其间的内容。

对依赖注入的一个特别常见的需求是在单元测试中。所需要的只是单元测试“配置”DI框架以返回模拟服务。

提供“服务”的实例而不是让客户端直接实例化是依赖注入的基本原则。

要利用.NET Core DI框架,您只需要引用Microsoft.Extensions.DependencyInjection.AbstractionsNuGet包。这提供访问IServiceCollection interface,其暴露了System.IService­Provider,在其中你可以调用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和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(
        _ => { // 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。

https://www.codeproject.com/KB/api/1276248/6-1.png

在Visual Studio中,将断点放在CitiesController.cs中。一个在Get,另一个在Post。

https://www.codeproject.com/KB/api/1276248/6-2.png

在Country字段中,选择Australia,然后输入Geelong。然后点击转到(Go)按钮,你可以看到在Search函数中的updateLastAccessedCity被中断。

https://www.codeproject.com/KB/api/1276248/6-3.png

点击“继续”。

然后在CitiesController中的Post方法被中断。

https://www.codeproject.com/KB/api/1276248/6-4.png

单击“继续”或按F5。Geelong保存到数据库中。

https://www.codeproject.com/KB/api/1276248/6-5.png

刷新Chrome。在ngOnInit中的getLastAccessedCity被中断。

https://www.codeproject.com/KB/api/1276248/6-6.png

点击“继续 ”,在CitiesContoller中的Http Get方法被中断。

https://www.codeproject.com/KB/api/1276248/6-7.png

结论

构建一个出色的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

关注
打赏
1665926880
查看更多评论
立即登录/注册

微信扫码登录

0.1846s