首页 文章 精选 留言 我的

精选列表

搜索[Docker],共10000篇文章
优秀的个人博客,低调大师

用VSCode开发一个基于asp.net core 2.0/sql server linux(docker)/ng5/bs4的项目(3)

第一部分:http://www.cnblogs.com/cgzl/p/8478993.html 第二部分:http://www.cnblogs.com/cgzl/p/8481825.html 由于本文主要是讲VSCode开发等, 所以相关等一些angular/.net core的知识就相对少讲点. 我把需求改一下, 如图: 由于efcore目前还是使用中间表来建立多对多的关系, 所以分类标签这个需求就没什么特别的了, 就去掉吧. 还有电视剧分季这个技术上也是重复的, 也删掉. 目前只剩下电视台和电视剧的维护, 还剩下的知识点是: 集合的CRUD操作 对项目结构进行整理, 使用Unit Of Work 以及 Repository 模式 上传文件 由于CRUD画面做起来比较简单, 我相信大家使用该技术的都会, 所以我直接把我写完的代码传上来. 此时页面的操作效果请看视频: 这时的代码: https://pan.baidu.com/s/1egCmuNT0OxJNwkz0OQ72kA 这里面又一个比较常见的问题, 就是 针对集合的增删改: 上述业务的电视剧的增删改就会出现这种情况: 数据库里面原来有4条数据, 而前台操作完, 删除了第3条数据, 并且增加了一条数据, 其余数据的内容可能有变化. 这种集合类增删改的思路应该是这样的: 1.从传进来的集合找出所有新添加的数据(新添加的数据Id通常并不存在, 这是条件), 放在一个临时的集合, 然后添加到context. 2.从数据库集合找出所有需要删除的数据(有一些id在传进来的集合找不到的就是), 放在一个集合, 然后从conetxt一次性移除. 3.两个集合都有的数据就是只需要修改内容的数据, 更新即可. 下面开始实现这个功能: 首先确保Put方法里, 把电视台下所有的电视剧都读取出来: 接下来, 找到MappingProfile.cs, 使用AutoMapper来实现这个功能. 首先要忽略默认的集合映射: 然后调用AfterMap方法做后期手动处理, 代码的意思大概是这样: 或者, 也可以用Linq做一些重构: 回到画面试一下编辑功能: 然后添加和删除: 查看结果: OK. 项目重构 下面, 我们使用Unit Of Work以及Repository Pattern对项目进行重构: 由于这部分也很简单, 并且也不是这篇文章的重点, 我就直接贴出重构后的代码吧: Database/TvRepostiory.cs: using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Tv.Models; namespace Tv.Database { public class TvRepository : ITvRepostitory { private readonly TvContext context; public TvRepository(TvContext context) { this.context = context; } public async Task<List<TvNetwork>> GetTvNetworksAsync() { return await context.TvNetworks.Include(x => x.TvShows).ToListAsync(); } public async Task<TvNetwork> GetTvNetworkByIdAsync(int id, bool includeRelated = true) { if (includeRelated) { return await context.TvNetworks.Include(x => x.TvShows).SingleOrDefaultAsync(x => x.Id == id); } return await context.TvNetworks.FindAsync(id); } public void AddTvNetwork(TvNetwork model) { context.TvNetworks.Add(model); } public void RemoveTvNetwork(TvNetwork model) { context.TvNetworks.Remove(model); } } } Database/ITvRepository.cs: using System.Collections.Generic; using System.Threading.Tasks; using Tv.Models; namespace Tv.Database { public interface ITvRepostitory { Task<List<TvNetwork>> GetTvNetworksAsync(); Task<TvNetwork> GetTvNetworkByIdAsync(int id, bool includeRelated = true); void AddTvNetwork(TvNetwork model); void RemoveTvNetwork(TvNetwork model); } } Database/UnitOfWork.cs: using System.Threading.Tasks; namespace Tv.Database { public class UnitOfWork : IUnitOfWork { private readonly TvContext context; public UnitOfWork(TvContext context) { this.context = context; } public async Task SaveAsync() { await context.SaveChangesAsync(); } } } Database/IUnitOfWork.cs: using System.Threading.Tasks; namespace Tv.Database { public interface IUnitOfWork { Task SaveAsync(); } } Startup.cs: public void ConfigureServices(IServiceCollection services) { services.AddAutoMapper(); // services.AddDbContext<TvContext>(opt => opt.UseSqlServer(Configuration["ConnectionStrings:Default"])); services.AddDbContext<TvContext>(opt => opt.UseSqlServer(Configuration.GetConnectionString("Default"))); services.AddScoped<ITvRepostitory, TvRepository>(); services.AddScoped<IUnitOfWork, UnitOfWork>(); services.AddMvc(); } TvController.cs: using System.Collections.Generic; using System.Threading.Tasks; using AutoMapper; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Tv.Database; using Tv.Models; using Tv.ViewModels; namespace Tv.Controllers { public class TvController : Controller { private readonly ITvRepostitory repostiory; private readonly IUnitOfWork unitOfWork; private readonly IMapper mapper; public TvController(ITvRepostitory repostiory, IUnitOfWork unitOfWork, IMapper mapper) { this.repostiory = repostiory; this.unitOfWork = unitOfWork; this.mapper = mapper; } [HttpGet("api/tvnetworks")] public async Task<IEnumerable<TvNetworkViewModel>> GetTvNetworks() { var models = await repostiory.GetTvNetworksAsync(); var vms = mapper.Map<List<TvNetwork>, List<TvNetworkViewModel>>(models); return vms; } [HttpGet("api/tvnetworks/{id}")] public async Task<IActionResult> Get(int id) { var model = await repostiory.GetTvNetworkByIdAsync(id); var vm = mapper.Map<TvNetwork, TvNetworkViewModel>(model); return Ok(vm); } [HttpPost("api/tvnetworks")] public async Task<IActionResult> Post([FromBody]TvNetworkUpdateViewModel vm) { if (!ModelState.IsValid) { return BadRequest(ModelState); } var model = mapper.Map<TvNetworkUpdateViewModel, TvNetwork>(vm); repostiory.AddTvNetwork(model); await unitOfWork.SaveAsync(); var result = mapper.Map<TvNetwork, TvNetworkViewModel>(model); return Ok(result); } [HttpPut("api/tvnetworks/{id}")] public async Task<IActionResult> Put(int id, [FromBody]TvNetworkUpdateViewModel vm) { if (!ModelState.IsValid) { return BadRequest(ModelState); } var dbModel = await repostiory.GetTvNetworkByIdAsync(id); if (dbModel == null) { return NotFound(); } var model = mapper.Map<TvNetworkUpdateViewModel, TvNetwork>(vm, dbModel); await unitOfWork.SaveAsync(); var result = mapper.Map<TvNetwork, TvNetworkViewModel>(model); return Ok(result); } [HttpDelete("api/tvnetworks/{id}")] public async Task<IActionResult> Delete(int id) { var model = await repostiory.GetTvNetworkByIdAsync(id, includeRelated: false); if (model == null) { return NotFound(); } repostiory.RemoveTvNetwork(model); await unitOfWork.SaveAsync(); return NoContent(); } } } 再操作下画面, 没有任何问题. 今天先写到这, VSCode的开发速度还是非常快的. 还剩下最后一部分--上传文件.下面是我的关于ASP.NET Core Web API相关技术的公众号--草根专栏:

优秀的个人博客,低调大师

用VSCode开发一个基于asp.net core 2.0/sql server linux(docker)/ng5/bs4的项目(2)

第一部分:http://www.cnblogs.com/cgzl/p/8478993.html 为Domain Model添加约束 前一部分, 我们已经把数据库创建出来了. 那么我们先看看这个数据库. 可以在项目里面建立一个database.sql, 并且建立一个数据库连接的profile(参考上一篇文章), 连接成功后执行下面语句: SELECT TABLE_NAME FROM tvdb.INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'; 右侧结果可以看到建立的table, 其中一个是迁移表, 另外两个是Domain Model所对应的业务表. 使用下面的sql语句查询表的字段定义: select * from information_schema.columns where table_name = 'TvNetworks'; select * from information_schema.columns where table_name = 'TvShows'; 从结果的CHARACTER_MAXIMUM_LENGTH字段可以看出, 目前name字段的类型都是nvarchar(max): 这可能不是我们想要的, 所以就需要为Domain Model的相应属性添加一些约束. 打开TvNetwork和TvShow, 为name属性添加约束: using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel.DataAnnotations; namespace Tv.Models { public class TvNetwork { public TvNetwork() { TvShows = new Collection<TvShow>(); } public int Id { get; set; } [Required] [StringLength(50)] public string Name { get; set; } public ICollection<TvShow> TvShows { get; set; } } } using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel.DataAnnotations; namespace Tv.Models { public class TvNetwork { public TvNetwork() { TvShows = new Collection<TvShow>(); } public int Id { get; set; } [Required] [StringLength(50)] public string Name { get; set; } public ICollection<TvShow> TvShows { get; set; } } } EF Core其他的约束属性请参考文档, 这里就不介绍了. 这种对Domain Model进行约束的方法使用的是DataAnnotation, 而我个人更喜欢使用FluetApi, 不过在这篇文章里这个不是重点. 然后添加migrations: dotnet ef migrations add AddConstraints 看一下生成的migration文件: 没问题, 可以执行dotnet ef database update了. 执行成功后, 可以看到表的字段约束已经添加成功了: 为数据库添加种子数据. 添加种子数据的方法有很多, 可以写一个方法然后在Startup里面调用. 这里我使用添加migration的方式: 命令行添加一个空的migration: dotnet ef migrations add SeedData 打开这个migration文件, 添加如下代码: using Microsoft.EntityFrameworkCore.Migrations; using System; using System.Collections.Generic; namespace Tv.Migrations { public partial class SeeData : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.Sql("INSERT INTO TvNetworks (Name) VALUES ('Netflix')"); migrationBuilder.Sql("INSERT INTO TvNetworks (Name) VALUES ('HBO')"); migrationBuilder.Sql("INSERT INTO TvNetworks (Name) VALUES ('CBS')"); migrationBuilder.Sql("INSERT INTO TvNetworks (Name) VALUES ('NBC')"); migrationBuilder.Sql("INSERT INTO TvShows (Name, TvNetworkId) VALUES ('House of Cards', (SELECT Id FROM TvNetworks WHERE Name='Netflix'))"); migrationBuilder.Sql("INSERT INTO TvShows (Name, TvNetworkId) VALUES ('Altered Carbon', (SELECT Id FROM TvNetworks WHERE Name='Netflix'))"); migrationBuilder.Sql("INSERT INTO TvShows (Name, TvNetworkId) VALUES ('Marvel''s Daredevil', (SELECT Id FROM TvNetworks WHERE Name='Netflix'))"); migrationBuilder.Sql("INSERT INTO TvShows (Name, TvNetworkId) VALUES ('Game of Thrones', (SELECT Id FROM TvNetworks WHERE Name='HBO'))"); migrationBuilder.Sql("INSERT INTO TvShows (Name, TvNetworkId) VALUES ('Silicon Valley', (SELECT Id FROM TvNetworks WHERE Name='HBO'))"); migrationBuilder.Sql("INSERT INTO TvShows (Name, TvNetworkId) VALUES ('Veep', (SELECT Id FROM TvNetworks WHERE Name='HBO'))"); migrationBuilder.Sql("INSERT INTO TvShows (Name, TvNetworkId) VALUES ('NCIS', (SELECT Id FROM TvNetworks WHERE Name='CBS'))"); migrationBuilder.Sql("INSERT INTO TvShows (Name, TvNetworkId) VALUES ('The Big Bang Theory', (SELECT Id FROM TvNetworks WHERE Name='CBS'))"); migrationBuilder.Sql("INSERT INTO TvShows (Name, TvNetworkId) VALUES ('Criminal Minds', (SELECT Id FROM TvNetworks WHERE Name='CBS'))"); migrationBuilder.Sql("INSERT INTO TvShows (Name, TvNetworkId) VALUES ('Friends', (SELECT Id FROM TvNetworks WHERE Name='NBC'))"); migrationBuilder.Sql("INSERT INTO TvShows (Name, TvNetworkId) VALUES ('Chicago Fire', (SELECT Id FROM TvNetworks WHERE Name='NBC'))"); migrationBuilder.Sql("INSERT INTO TvShows (Name, TvNetworkId) VALUES ('Will & Grace', (SELECT Id FROM TvNetworks WHERE Name='NBC'))"); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.Sql("DELETE FROM TvNetworks WHERE Name IN ('Netflix', 'HBO', 'CBS', 'NBC')"); } } } 然后执行 dotnet ef database update. 成功后可以查看到数据: 建立Web Api 在Controllers文件夹下建立TvController.cs. 需要注入TvContext, 这时候聚焦到context变量上使用cmd+. 这个快捷键 生成一个field: 随后, 就会生成一个field: 完成后到代码如下: using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Tv.Database; using Tv.Models; namespace Tv.Controllers { public class TvController : Controller { private readonly TvContext context; public TvController(TvContext context) { this.context = context; } [HttpGet("/api/tvnetworks")] public async Task<IEnumerable<TvNetwork>> GetTvNetworks() { return await context.TvNetworks.Include(x => x.TvShows).ToListAsync(); } } } 这部分代码所涉及到的asp.net core的知识请参考我写的这个系列文章:http://www.cnblogs.com/cgzl/p/7637250.html 运行项目: dotnet watch run, 这时我们需要使用postman来测试这个api. 以前postman是chrome浏览器的一个扩展应用, 由于被墙, 可能会安装不上, 而现在postman是一个独立的应用了, 应该都能下载安装了:https://www.getpostman.com/ 由于以前讲过postman, 所以这里我就不用postman了. Rest Client 我使用vscode扩展rest client来测试api. rest client简介部分可以参考这个文章:http://www.cnblogs.com/cgzl/p/8450409.html 建立一个httptest文件, 打开文件, 使用命令面板 输入查找这个命令: 然后选择http: 在文件中写下api的uri: http://localhost:5000/api/tvnetworks 然后你会发现, 该uri的上方有一个send request 按钮: 点击这个按钮, 发送请求. 尽管请求返回结果是200, 但是你也可以发现结果并不正确, 看一下终端命令行: 确实是发生了异常, 因为一个Tvnetwork有个导航属性是多个TvShow, 而一个TvShow还有一个反向导航属性是TvNetwork, 所以dbcontext查询出来在进行json转化的时候, 会无限循环下去, 就引起了self referencing loop. 所以web api 不应该把Domain Model直接暴露出去, 应该使用ViewModel或者叫Dto... 建立ViewModel 建立ViewModels/TvNetworkViewModel.cs 和 TvShowViewModel.cs: using System.Collections.Generic; using System.Collections.ObjectModel; namespace Tv.ViewModels { public class TvNetworkViewModel { public TvNetworkViewModel() { TvShows = new Collection<TvShowViewModel>(); } public int Id { get; set; } public string Name { get; set; } public ICollection<TvShowViewModel> TvShows { get; set; } } } namespace Tv.ViewModels { public class TvShowViewModel { public int Id { get; set; } public string Name { get; set; } public int TvNetworkId { get; set; } } } 注意TvShowViewModel里面并没有反向的TvNetWork属性, 这也保证了不会发生上面的自身循环引用异常. 接下来需要做的就是在Controller里面把Domain Model的属性传递给ViewModel, 没人会去手写这个映射的过程, 所以应该使用AutoMapper等类似的库 AutoMapper 首先添加AutoMapper, 一共有两个包: dotnet add package AutoMapper dotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection 别忘了还要执行dotnet restore. 安装成功后, 在Startup.cs里面注册AutoMapper: 此外, AutoMapper还需要知道Domain Model和ViewModel的对应关系和方向. 建立Mapping/MappingProfile.cs: using AutoMapper; using Tv.Models; using Tv.ViewModels; namespace Tv.Mapping { public class MappingProfile : Profile { public MappingProfile() { CreateMap<TvNetwork, TvNetworkViewModel>(); CreateMap<TvShow, TvShowViewModel>(); } } } 然后在Controller里面需要注入AutoMapper: using System.Collections.Generic; using System.Threading.Tasks; using AutoMapper; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Tv.Database; using Tv.Models; using Tv.ViewModels; namespace Tv.Controllers { public class TvController : Controller { private readonly TvContext context; private readonly IMapper mapper; public TvController(TvContext context, IMapper mapper) { this.context = context; this.mapper = mapper; } [HttpGet("/api/tvnetworks")] public async Task<IEnumerable<TvNetworkViewModel>> GetTvNetworks() { var models = await context.TvNetworks.Include(x => x.TvShows).ToListAsync(); var vms = mapper.Map<List<TvNetwork>, List<TvNetworkViewModel>>(models); return vms; } } } 差不多了, 再次测试一下这个api: 没毛病!!! 建立Angular5项目 按照第一部分的操作安装好angular cli之后 (https://github.com/angular/angular-cli), 就可以打开命令行建立angular 客户端项目了. 使用: ng new tv-client 创建一个名字为tv-client的angular项目. 此时, cli会通过npm自动安装依赖的包. 安装好所有的包之后, 就可以进入该目录 cd tv-client 并用 vscode打开该目录: code+. 这个项目里面, 我们主要是在src/app里面写代码, 也会简单修改一下angular-cli.json文件. 运行angular项目: 可以使用ng server或者npm start命令运行angular项目: 最好还是使用npm start, 因为ng server以后会需要添加一些参数. 所以npm start, 看看效果: 打开浏览器 http://localhost:4200, ok, 项目建立成功了. 由于已经存在种子数据了, 那么就可以查询列表了. 创建TvNetwork列表: 首先把当前目录切换到app下: 根据文档, 使用下面命令创建一个名为tv-network-list.ts的component, 并且在app模块进行注册, 如果不存在components文件夹则创建这个文件夹. ng g c components/TvNetworkList -m=app 生成文件如下: 并且已经在app.module进行了注册: 然后我们再创建两个component. 创建TvNetwork表单: 根据文档, 使用下面命令创建一个名为tv-network-form.ts的component, 并且在app模块进行注册, 如果不存在components文件夹则创建这个文件夹. ng g c components/TvNetworkForm -m=app 上面这个命令使用的都是缩写. 完整的写法如下: ng generate component components/TvNetworkForm --module=app 生成的文件如下: 再建立一个home component: ng g c components/home -m=app 那么, 如何访问这个form? 这就需要建立路由了, 不过首先先把bootstrap 4 安装上, 项目根目录执行以下命令: npm install --save bootstrap jquery popper.js 安装好之后, 需要把bootstrap的css文件添加到angular-cli.json文件里: 下面添加导航栏, 请参考bootstrap4文档:http://getbootstrap.com/docs/4.0/components/navbar/ 修改app.component.html如下: <nav class="navbar navbar-expand-lg navbar-dark bg-dark"> <a class="navbar-brand" href="#">Tv</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav mr-auto"> <li class="nav-item active"> <a class="nav-link" href="#">Home <span class="sr-only">(current)</span> </a> </li> </ul> </div> </nav> 然后运行npm start, 结果如下图就说明bootstrap4安装好了: 建立angular 路由: 参考官方文档:https://angular.io/tutorial/toh-pt5 执行命令: ng g m appRouting -flat -m=app 这会建立一个app-routing.module.ts模块, 并且不会创建自己的文件夹, 同样也会注册到app模块. 修改app-routing到代码如下: import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { HomeComponent } from './components/home/home.component'; import { TvNetworkFormComponent } from './components/tv-network-form/tv-network-form.component'; import { TvNetworkListComponent } from './components/tv-network-list/tv-network-list.component'; const ROUTES: Routes = [ { path: '', redirectTo: '/home', pathMatch: 'full' }, { path: 'home', component: HomeComponent }, { path: 'tvnetworks', component: TvNetworkListComponent }, { path: 'tvnetworks/new', component: TvNetworkFormComponent }, { path: '**', component: HomeComponent } ]; @NgModule({ imports: [ RouterModule.forRoot(ROUTES) ], exports: [RouterModule] }) export class AppRoutingModule { } 在编写angular的ts代码时, 由于安装了angular插件, 所以智能提示和自动补全和自动引用都是相当好的. 分别设置了5个路由, 默认路由直接跳转到home, 如果没有匹配路由到话也是跳转到home. 然后需要在app.component.html里面加上router-outlet, 并修改navbar里面到链接: <nav class="navbar navbar-expand-lg navbar-dark bg-dark"> <a class="navbar-brand" href="#">Tv</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav mr-auto"> <li class="nav-item"> <a class="nav-link" routerLinkActive="active" routerLink="/home">Home <span class="sr-only">(current)</span> </a> </li> <li class="nav-item"> <a class="nav-link" routerLinkActive="active" routerLink="/tvnetworks">Tv Network <span class="sr-only">(current)</span> </a> </li> <li class="nav-item"> <a class="nav-link" routerLinkActive="active" routerLink="/tvnetworks/new">Add Tv Network <span class="sr-only">(current)</span> </a> </li> </ul> </div> </nav> <div class="container"> <router-outlet></router-outlet> </div> 查看浏览器, 应该是这个效果: 建立Service 为了使用asp.net core到web api, 需要在angular客户端建立http的service. 这里我使用HttpClient. 首先在app.module里面添加引用: import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { HttpClientModule } from '@angular/common/http'; import { AppComponent } from './app.component'; import { TvNetworkFormComponent } from './components/tv-network-form/tv-network-form.component'; import { HomeComponent } from './components/home/home.component'; import { AppRoutingModule } from './/app-routing.module'; @NgModule({ declarations: [ AppComponent, TvNetworkFormComponent, HomeComponent ], imports: [ BrowserModule, AppRoutingModule, HttpClientModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { } 然后使用命令生成service: ng g s services/TvNetwork -m=app 然后编辑tv-network.service.ts, 添加一个获得所有tv network的方法, 返回类型是Observable: import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; @Injectable() export class TvNetworkService { constructor( private http: HttpClient ) { } getTvNetworks () { return this.http.get<any[]>('api/tvnetworks'); } } 随后我们在tv-netowrk-list.component.ts里的ngOnInit方法调用它, 并把结果打印出来: import { Component, OnInit } from '@angular/core'; import { TvNetworkService } from '../../services/tv-network.service'; @Component({ selector: 'app-tv-network-list', templateUrl: './tv-network-list.component.html', styleUrls: ['./tv-network-list.component.css'] }) export class TvNetworkListComponent implements OnInit { tvNetworks: any[]; constructor( private tvNetworkServices: TvNetworkService ) { } ngOnInit() { this.tvNetworkServices.getTvNetworks().subscribe(result => { this.tvNetworks = result; console.log(this.tvNetworks); }, err => { console.error(err); }); } } 然后让我们运行试试: 可以看到发生了错误404, angular客户端并没有找到这个api. 这是因为angular运行的是自己的web服务器端口4200, 而asp.net core也是运行自己服务器端口为5000. 那么可以有多种解决办法: 1. 可以在angular的service的url写成完整的地址, 但是, 由于开发时和生产时的api地址很有可能不一样, 那么这就意味着发布到正式环境之前要把所有services的url地址全部修改一遍, 显然, 这时不可取的. (也许可以定义一个前缀变量, 随着环境改变它的值).2. 由于angular cli其实使用的是webpack, 那么就可以使用proxy. 我们就使用proxy, 参考官方文档:https://github.com/angular/angular-cli/wiki/stories-proxy 在项目根目录建立一个proxy.conf.json文件: { "/api": { "target": "http://localhost:5000", "secure": false } } 这表示所有的以/api开头的请求将会被转发到http://localhost:5000/api这个地址上. 此外还需要修改package.json里面到npm start部分, 把上面的proxy文件添加为参数: 然后重新运行angular项目, 这时只能使用 npm start这个命令, 如果想使用ng serve 命令则必须把后边的参数加上. 重新访问TvNetworks菜单: 这次读取api成功了. 那么接下来我们来完成这个列表页面. cmd+p, 输入 tv list html 打开tv-network-list.component.html. 这里需要画一个table, 别忘了使用zencoding. 表头部分, 按照下面输入然后按Tab: Tbody部分: 最后代码: <table class="table"> <thead class="thead-dark"> <tr> <th scope="col">#</th> <th scope="col">名称</th> <th scope="col">操作</th> </tr> </thead> <tbody> <tr *ngFor="let t of tvNetworks; let i = index"> <th scope="row">{{i+1}}</th> <td>{{t.name}}</td> <td></td> </tr> </tbody> </table> 运行页面: Beautiful. 继续编写表单: 打开tv-network-form.component.html, 请看视频: 最终代码如下: <h1>添加电视台</h1> <form> <div class="form-group"> <label for="name">名称</label> <input type="text" name="name" id="name" class="form-control"> </div> <button class="btn btn-primary">提交</button> </form> 效果如图: 如果您跟着这两篇文章做到现在, 肯定可以感觉到vscode到强大和不同, 它绝不仅仅是个编辑器. 我一直在使用vscode编写前台和python等, 现在也习惯使用vscode编写.net core项目了, Awesome. 今天先写到这, 下一篇是CRUD部分. 下面是我的关于ASP.NET Core Web API相关技术的公众号--草根专栏:

优秀的个人博客,低调大师

将dubbo框架里的服务提供者迁移进k8s里,以docker提供服务时要注意的细节

在将dubbo框架里的服务提供者迁移进k8s时,有几个注意事项: 1, 要为默认情况下,dubbo会将k8s里的pod ip地址作为注册服务的地址,所以这个地址需要被改写(ip测试过,行得通,而dns待测试)。 2, Tomcat启动的端口需要与xxxx-dubbo.xml文件里的定义一致。 3, 同时,要将k8s的service和deploy的容器端口和nodePort端口都映射为同一个端口才可以。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册