架构
- RESTful Web API
- Repository
- Controller
创建项目
打开VS 2019,创建新项目ASP.NET Core Web 应用程序,项目名称为ThreeApi,选择.NET Core 3.1的Empty模板。然后把HTTPS配置和Docker选项去掉,点击创建按钮。
基本配置
打开Startup.cs文件,修改代码如下。Web API项目只需要用到Controller注入。
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddControllers(); }
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); }
app.UseRouting();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } }
|
把Three项目中的Models文件夹以及Services文件夹中的IClock.cs、ChinaClock.cs、UtcClock.cs复制到本项目中(注意要修改命名空间)。
IClock在Startup.cs中,进行依赖注入。
services.AddSingleton<IClock, UtcClock>();
|
添加Repository
IDepartmentRepository
项目右键,新建Repositories文件夹。在Repositories文件夹下,添加IDepartmentRepository.cs。
public interface IDepartmentRepository { Task<Department> Add(Department model); Task<IEnumerable<Department>> GetAll(); Task<Department> GetById(int id); }
|
DepartmentRepository
在Repositories文件夹下,添加DepartmentRepository.cs。
public class DepartmentRepository : IDepartmentRepository { private readonly List<Department> _departments = new List<Department>();
public DepartmentRepository() { _departments.Add(new Department { Id = 1, Name = "HR", EmployeeCount = 16, Location = "Beijing" }); _departments.Add(new Department { Id = 2, Name = "G&D", EmployeeCount = 52, Location = "Shanghai" }); _departments.Add(new Department { Id = 3, Name = "Sales", EmployeeCount = 200, Location = "China" }); }
public Task<IEnumerable<Department>> GetAll() { return Task.Run(function: () => _departments.AsEnumerable()); }
public Task<Department> GetById(int id) { return Task.Run(function: () => _departments.FirstOrDefault(x => x.Id == id)); }
public Task<Department> Add(Department department) { department.Id = _departments.Max(x => x.Id) + 1; _departments.Add(department); return Task.Run(function: () => department); } }
|
ISummaryRepository
在Repositories文件夹下,添加ISummaryRepository.cs。这是从DepartmentRepository中分离出来的方法。
public interface ISummaryRepository { Task<CompanySummary> GetCompanySummary(); }
|
SummaryRepository
在Repositories文件夹下,添加SummaryRepository.cs。由于SummaryRepository需要用到DepartmentRepository,因此在构造器中注入。
public class SummaryRepository : ISummaryRepository { private readonly IDepartmentRepository _departmentRepository;
public SummaryRepository(IDepartmentRepository departmentRepository) { _departmentRepository = departmentRepository; }
public Task<CompanySummary> GetCompanySummary() { return Task.Run(function: () => { var all = _departmentRepository.GetAll().Result; return new CompanySummary { EmployeeCount = all.Sum(x => x.EmployeeCount), AverageDepartmentEmployeeCount = (int)all.Average(x => x.EmployeeCount) }; }); } }
|
IEmployeeRepository
在Repositories文件夹下,添加IEmployeeRepository.cs。
public interface IEmployeeRepository { Task<Employee> Add(Employee employee); Task<IEnumerable<Employee>> GetByDepartmentId(int departmentId); Task<Employee> Fire(int id); Task<Employee> GetById(int id); }
|
EmployeeRepository
在Repositories文件夹下,添加EmployeeRepository.cs。
public class EmployeeRepository : IEmployeeRepository { private readonly List<Employee> _employees = new List<Employee>();
public EmployeeRepository() { _employees.Add(new Employee { Id = 1, DepartmentId = 1, FirstName = "Nick", LastName = "Carter", Gender = Gender.男 }); _employees.Add(new Employee { Id = 2, DepartmentId = 1, FirstName = "Michael", LastName = "Jackson", Gender = Gender.男 }); _employees.Add(new Employee { Id = 3, DepartmentId = 1, FirstName = "Mariah", LastName = "Carey", Gender = Gender.女 }); _employees.Add(new Employee { Id = 4, DepartmentId = 2, FirstName = "Axl", LastName = "Rose", Gender = Gender.男 }); _employees.Add(new Employee { Id = 5, DepartmentId = 2, FirstName = "Kate", LastName = "Winslet", Gender = Gender.女 }); _employees.Add(new Employee { Id = 6, DepartmentId = 3, FirstName = "Rob", LastName = "Thomas", Gender = Gender.男 }); _employees.Add(new Employee { Id = 7, DepartmentId = 3, FirstName = "Avril", LastName = "Lavigne", Gender = Gender.女 }); _employees.Add(new Employee { Id = 8, DepartmentId = 3, FirstName = "Katy", LastName = "Perry", Gender = Gender.女 }); _employees.Add(new Employee { Id = 9, DepartmentId = 3, FirstName = "Michelle", LastName = "Monaghan", Gender = Gender.女 }); }
public Task<Employee> Add(Employee employee) { employee.Id = _employees.Max(x => x.Id) + 1; _employees.Add(employee); return Task.Run(function: () => { return employee; }); }
public Task<IEnumerable<Employee>> GetByDepartmentId(int departmentId) { return Task.Run(function: () => _employees.Where(x => x.DepartmentId == departmentId)); }
public Task<Employee> Fire(int id) { return Task.Run(function: () => { var employee = _employees.FirstOrDefault(e => e.Id == id); if (employee != null) { employee.Fired = true; return employee; } return null; }); } public Task<Employee> GetById(int id) { return Task.Run(function: () => { var employee = _employees.FirstOrDefault(e => e.Id == id); if (employee != null) { return employee; } return null; }); } }
|
注入Repository
在Startup类中,添加repository的注入。
services.AddSingleton<IDepartmentRepository, DepartmentRepository>(); services.AddSingleton<IEmployeeRepository, EmployeeRepository>(); services.AddSingleton<ISummaryRepository, SummaryRepository>();
|
添加Controller
DepartmentsController
项目右键,新建Controllers文件夹。在Controllers文件夹下,添加DepartmentsController.cs,并通过AttuibuteRouting设置路由。
[Route(template: "v1/[controller]")] [ApiController] public class DepartmentsController : ControllerBase { private readonly IDepartmentRepository _departmentRepository;
public DepartmentsController(IDepartmentRepository departmentRepository) { _departmentRepository = departmentRepository; }
[HttpGet] public async Task<ActionResult<IEnumerable<Department>>> GetAll() { var departments = await _departmentRepository.GetAll(); if (!departments.Any()) { return NoContent(); }
return Ok(departments); }
[HttpPost] public async Task<ActionResult<Department>> Add([FromBody]Department department) { var added = await _departmentRepository.Add(department);
return Ok(added); } }
|
代码解析:
- 路由属性中,v1代表版本号,controller就是Controller的名称。这里没有指定Action,因为不需要用到Action,是通过动词来区分请求到了哪个方法,如GET或POST动词。
- API Controller前面需要添加ApiController特性,然后必须继承于ControllBase类。而MVC Controller则需要继承于Controller类。
- FromBody指定了从Body传过来的参数。
EmployeesController
在Controllers文件夹下,添加EmployeesController.cs,内容与DepartmentsController类似。
[Route(template: "v1/[controller]")] [ApiController] public class EmployeesController : ControllerBase { private readonly IEmployeeRepository _employeeRepository;
public EmployeesController(IEmployeeRepository employeeRepository) { _employeeRepository = employeeRepository; }
[HttpGet(template: "{departmentId}")] public async Task<IActionResult> GetByDepartmentId(int departmentId) { var employees = await _employeeRepository.GetByDepartmentId(departmentId);
if (!employees.Any()) { return NoContent(); } return Ok(employees); }
[HttpGet(template: "One/{id}", Name = "GetById")] public async Task<IActionResult> GetById(int id) { var result = await _employeeRepository.GetById(id); if (result == null) { return NotFound(); } return Ok(result); }
[HttpPost] public async Task<IActionResult> Add([FromBody] Employee model) { var added = await _employeeRepository.Add(model); return CreatedAtRoute(routeName: "GetById", routeValues: new { id = added.Id }, value: added); }
[HttpPut(template: "{id}")] public async Task<IActionResult> Fire(int id) { var result = await _employeeRepository.Fire(id); if (result != null) { return NoContent(); } return NotFound(); } }
|
代码解析:
- GetById方法和GetByDepartmentId方法,虽然意义不一样,但是路由的路径一样的。因此为了区分,在GetById方法的路由前加上One。Name参数指定了Action的名称,在CreatedAtRoute方法这家拍卖行睡觉哦精辟
- CreatedAtRoute方法是标准写法,它可以指定对于已经创建好的资源,将来可以在哪个Action中获取。routeName参数是可以获取的Action,routeValues是传递的参数,value是添加后的资源。CreateAtRoute返回的状态码是201,而Ok返回的状态码是200。
- HttpPut是对资源做整体的更新。
SummaryController
在Controllers文件夹下,添加SummaryController.cs。内容与上面两个Controller类似。
[Route(template: "v1/[controller]")] [ApiController] public class SummaryController : ControllerBase { private readonly ISummaryRepository _summaryRepository;
public SummaryController(ISummaryRepository summaryRepository) { _summaryRepository = summaryRepository; }
public async Task<IActionResult> Get() { var result = await _summaryRepository.GetCompanySummary(); return Ok(result); } }
|
ApiController Attribute
- Attribute路由
- 对Model自动验证
- 推断绑定源
- [FromBody],[FromForm],[FromRoute],[FromQuery]
- [FromHeader],[FromServices]
FromBody
[HttpPost] public async Task<ActionResult<Department>> Add([FromBody]Department department) { var added = _departmentRepository.Add(department); return Ok(added); }
|
FromRoute
[HttpGet(template: "{departmentId}")] public async Task<IActionResult> GetByDepartmentId([FromRoute]int departmentId) { var employees = await _employeeRepository.GetByDepartmentId(departmentId);
if (!employees.Any()) { return NoContent(); } return Ok(employees); }
|
其他绑定源
- FromBody:参数将以一个整体的josn对象的形式传递。
- FromForm:参数将以表单的形式提交。
- FromQuery:queryString字符串,地址栏参数。
- FromHeader:HTTP请求中的Header。
- FromServices:从DI容器中获取依赖参数。
测试接口
DepartmentsController
GET请求
以项目名称运行项目,打开Postman,使用GET请求如下地址:localhost:5000/v1/Departments,可以看到返回结果为json格式数据。
ASP.NET Core Web API默认情况下只返回json数据。如果想要支持xml格式,则需要
- 在Postman的请求部分,设置Accept的值为application/xml。
- 在Startup类中增加依赖注入AddXmlSerializerFormatters。
public void ConfigureServices(IServiceCollection services) { services.AddControllers() .AddXmlSerializerFormatters(); services.AddSingleton<IClock, UtcClock>(); services.AddSingleton<IDepartmentRepository, DepartmentRepository>(); services.AddSingleton<IEmployeeRepository, EmployeeRepository>(); services.AddSingleton<ISummaryRepository, SummaryRepository>(); }
|
运行项目,使用Postman重新请求,可以得到xml格式数据。
POST请求
请求Body中输入如下json参数,使用POST请求如下地址:localhost:5000/v1/Departments。
{ "Name": "Develop", "Location": "Guangzhou", "EmployeeCount": 300 }
|
可以看到返回的状态码为200,并且部门已经添加。
注意,使用POST请求时,要在Postman的Header中添加参数,Content-Type的值为application/json。
EmployeesController
获取某个部门所有员工
以GET方式请求如下地址:localhost:5000/v1/Employees/1,成功返回部门1的员工数据。
获取单个员工
以GET方式请求如下地址:localhost:5000/v1/Employees/One/1,成功返回Id为1的员工数据。
添加员工
请求Body中输入如下json参数,使用POST请求如下地址:localhost:5000/v1/Employees。
{ "departmentId": 1, "firstName": "Nick", "lastName": "Torres", "gender": 1 }
|
可以看到返回的状态为200,并且员工已经添加。
解雇员工
以PUT方式请求如下地址:localhost:5000/v1/Employees/1,解雇Id为1的员工。
返回结果的状态码为204 No Content,代表请求成功,这是正确的。
再使用GET方式获取Id为1的员工,可以看到员工1已被解雇。
SummaryController
以GET方式请求如下地址:localhost:5000/v1/Summary,成功返回结果。