第一部分: 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部分.

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

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

    最近使用vscode比较多. 学习了一下如何在mac上使用vscode开发asp.netcore项目. 这里是我写的关于vscode的一篇文章: https://www.cnblogs.com/cgz ...

  2. 用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 由于 ...

  3. [译]基于ASP.NET Core 3.0的ABP v0.21已发布

    基于ASP.NET Core 3.0的ABP v0.21已发布 在微软发布仅仅一个小时后, 基于ASP.NET Core 3.0的ABP v0.21也紧跟着发布了. v0.21没有新功能.它只是升级到 ...

  4. 基于ASP.NET Core 3.0快速搭建Razor Pages Web应用

    前言 虽然说学习新的开发框架是一项巨大的投资,但是作为一个开发人员,不断学习新的技术并快速上手是我们应该掌握的技能,甚至是一个.NET Framework开发人员,学习.NET Core 新框架可以更 ...

  5. 基于ASP.NET Core 6.0的整洁架构

    大家好,我是张飞洪,感谢您的阅读,我会不定期和你分享学习心得,希望我的文章能成为你成长路上的垫脚石,让我们一起精进. 本节将介绍基于ASP.NET Core的整洁架构的设计理念,同时基于理论落地的代码 ...

  6. 打造静态分析器(二)基于Asp.Net Core 3.0的AspectCore组件检测

    上一篇,我们打造了一个简单的分析器,但是我们实际使用分析器就是为了对项目做分析检测,增加一些非语法的自检的 比如Asp.Net Core 3.0的替换依赖注入检测 设计分析 我们创建一个默认的Asp. ...

  7. 基于Asp.Net Core 5.0依赖Quartz.Net框架编写的任务调度web管理平台

    源码地址: https://github.com/246850/Calamus.TaskScheduler 演示地址:http://47.101.47.193:1063/ 1.Quartz.NET框架 ...

  8. ASP.NET Core 监听SQL Server数据库的实时信息

    1.开发环境: 开发工具:Visual Studio 2019 数据库:SQL Server2012 开发环境:.Net Core 3.1 2.使用技术: Signalr:实现消息推送 SqlDepe ...

  9. Asp.Net Core采用MailKit部署到Linux Docker连接邮件服务器报错

    前段时间看文章了解到发邮件的SmtpClient已经过时了,微软官方推荐大家用其他解决方案,例如MailKit. https://docs.microsoft.com/zh-cn/dotnet/api ...

随机推荐

  1. Python使用requests模块访问HTTPS网站报错`certificate verify failed`

    使用requests模块访问HTTPS网站报错: SNIMissingWarning: An HTTPS request has been made, but the SNI (Subject Nam ...

  2. java面向对象——类

    一.类 类(class)是构造对象的模板或蓝图.由类构造(construct)对象的过程称为创建类的实例(instance). 用 java 编写的所有代码都位于某个类的内部.标准的Java 库提供了 ...

  3. linux_操作系统

    如何查询操作系统版本? cat /etc/redhat-release 什么是操作系统? -- win10,linux都是os,应用软件和硬件打交道中间桥梁软件,管理 硬件+软件 资源,计算机系统基础 ...

  4. 自己用的一套reset.css,打算整理一下方便以后用,持续更新中,各位大神,不喜勿喷

    *{margin: 0; padding: 0;border:none;}img{vertical-align: top;width: 100%;border: none;}ul,li{list-st ...

  5. ssm web.xml配置解析

    以下为web.xml的配置<?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi=& ...

  6. 带有 thead、tbody 以及 tfoot 元素的 HTML 表格

    设置样式: <head><style type="text/css">thead {color:green}tbody {color:blue;height ...

  7. MySQL权限详解

    MySQL权限级别介绍 MySQL权限级别 全局性的管理权限,作用于整个MySQL实例级别 数据库级别的权限,作用于某个指定的数据库上或者所有的数据库上 数据库对象级别的权限,作用于指定的数据库对象上 ...

  8. echarts中视觉映射器(visualMap)与时间轴(timeline)混用的实现方法

    1.简述 echarts中的 timeline 组件,提供了在多个 ECharts option 间进行切换.播放等操作的功能. 与其他组件些不同,它需要操作『多个option』. 所以除了基准的ba ...

  9. 解决苹果电脑(mac)管理员账户变成了普通用户后不能解锁用户与群组的问题

    亲们,我先说说前因,然后再说一下解决方法. 前因 今天不知怎么就想把苹果电脑原来的名字给改一下,于是就做了下面的操作(你们不要这样做) 1.系统偏好设置→用户与组群→当前管理员用户→(右键)高级选项 ...

  10. Spring学习之装配Bean

    通过注解配置的bean,默认是单例 @Autowired private CodeTypeService codeTypeService; 测试:在Controller层调用Service层对象Cod ...