RESTful API ASP.NET Core 标签
- 一句话的事儿:说白了就是通过API创建一些别人不关心的内部逻辑只在乎功能的那些玩意,与此同时,RESTful只是Roy Fielding博士定义的一种API风格,普遍被行业接受而已。
Web API 与RESTful API 简介
先决条件
预备知识:ASP.NET Core 和 C#
工具:Visual Studio 2019最新版(VSCode、VS for Mac,Rider等也凑合),POSTMAN
Web API
Web API通常是指“使用HTTP协议并通过网络调用的API”,由于它使用了HTTP协议,所以需要通过URI信息来指定端点。
API是Application Programming Interface的缩写,是软件的外部接口。也就是说,针对某个软件,人们可以知道它的外部功能,但并不知道(也不需要知道)它的内部运作细节,为了从外部调用某些功能,需要指定软件的调用规范等信息,这样的规范就是API。
所以Web API就是一个Web系统,通过访问URI可以与其进行信息交互。
大多数的 Web API 并不是 RESTful API
REST一词是在2000年首次出现的,它是由Roy Fielding博士在《架构风格以及基于网路的软件架构设计》这篇论文中提到的。他为REST风格的API制定了一套约束规范或者叫架构风格。
所以准确的说,只有符合了Roy Fielding架构风格的Web API才能称作是RESTful API。但是在实际开发中,有时候也有不完全符合Roy Fielding架构风格的情形出现,针对这点我将会在稍后的文章中介绍。
MVC模式与RESTful API
本系列文章中我将使用ASP.NET Core 3.0 MVC 来构建 RESTful API。
MVC(Model-View-Controller)我认为是一种主要用来构建UI的架构模式。对于MVC模式其实有很多种解释存在,但是无论那种解释,它们都会强调松耦合和关注点分离(separation of concerns)。
也是由于这两点的存在,程序的可测试性会大大提高,程序各部分的可复用性也很高。
更多关于MVC的介绍,可以看一下微软的官方文档:https://docs.microsoft.com/zh-cn/aspnet/core/mvc/overview?view=aspnetcore-3.0
注意:MVC不是一个完整的应用程序架构,我认为它主要是用在展示层。所以实现UI就是MVC的一部分工作。
如何把MVC映射到API
我认为API同样可以看作是UI,它就是为API消费者所提供的UI。
让我们把MVC的三部分分别对应到API:
- Model,它负责处理程序数据的逻辑。这里的Model可以包含在当前级别获取从存储获取数据的逻辑。但是有一些Model不包含任何逻辑,例如API所使用的DTO(Data transfer objects),这类的Model会被串行化到响应的body里面。
- View,它是程序里负责展示数据的那部分。在构建API的时候,View就是数据或资源的展示。现在通常使用JSON格式。
Controller,它负责View和Model之间的交互。包括处理用户输入,用API的术语来讲,和API交互的“用户”就是指API的消费者,这类用户通常是另一个程序,例如Angular的SPA程序。
下面看看MVC这三部分的依赖关系:
Controller和View依赖于Model,Controller依赖于View,这也是分离的一个好处。
换句话说,Controller会选取适当的View来展现给用户,并一同把用户请求的Model数据带回去。
当API消费者发出请求的时候,在Controller上面的Action将会被触发,Controller会把接收到的输入数据发送给负责业务处理逻辑或数据访问逻辑的那部分程序。然后Controller会把Model返回给View,这里的View就是资源的展示(通常是JSON格式)。
接下来我们就是用这套概念和ASP.NET Core 3.0 来创建RESTful API。
但是请注意,通过ASP.NET Core MVC或API模板建立出来的新项目,我们并不会直接得到RESTful(REST架构风格)的API。我们必须在这个基础上,自己构建RESTful的API,因为之前已经提到了,标准的RESTful API有很多约束和规范。
创建ASP.NET Core 3.0 Web API项目
- 创建项目,选择 Create a new project
- 搜索ASP.NET Core—>点击Click
- 创建项目的解决方案名称为Routine,项目名称为Routine.API —>点击Create
- 相关项目的配置选项 —>点击Create
- 创建成功
- 删除两个默认的Controller,即weatherforecastcontroller,weatherforecast
- ASP.NET Core 请求管道
更改Properties配置
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"Routine.API": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "weatherforecast",
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
此时启动一下程序
- 这是因为我们之前把weather删除的原因,导致找不到对应的controller
- 增加轻量级数据库SQL Lite,通过NUGET安装
- 接着安装Microsoft.EntityFrameCore.Tools用于迁移使用
- 安装结果如下:
- 创建实体数据文件夹Entities —>并创建实体Company,Employee,Gender (目的是创建数据源模型)
Company
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Routine.API.Entities
{
public class Company
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Introduction { get; set; }
public ICollection<Employee> Employees { get; set; }
}
}
Employee
using System;
using System.Security.AccessControl;
namespace Routine.API.Entities
{
public class Employee
{
public Guid Id { get; set; }
public Guid CompanyId { get; set; }
public string EmployeeNo { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Gender Gender { get; set; }
public DateTime DateOfBirth { get; set; }
public Company Company { get; set; }
}
}
Gender
namespace Routine.API.Entities
{
public enum Gender
{
男 = 1,
女 = 2
}
}
- 创建好数据模型,就需要把这些数据存放到容器中,所以创建文件夹Data,并创建齐操作类RoutineDbContext ```csharp using Microsoft.EntityFrameworkCore; using Routine.API.Entities; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks;
namespace Routine.API.Data
{
public class RoutineDbContext : DbContext
{
public RoutineDbContext(DbContextOptions
}
public DbSet<Company> Companies { get; set; }
public DbSet<Employee> Employees { get; set; }
//做一些限制 override OnModelCreating
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Company>().Property(x => x.Name).IsRequired().HasMaxLength(100);
modelBuilder.Entity<Company>().Property(x => x.Introduction).IsRequired().HasMaxLength(500);
modelBuilder.Entity<Employee>().Property(x => x.EmployeeNo).IsRequired().HasMaxLength(10);
modelBuilder.Entity<Employee>().Property(x => x.FirstName).IsRequired().HasMaxLength(50);
modelBuilder.Entity<Employee>().Property(x => x.LastName).IsRequired().HasMaxLength(50);
modelBuilder.Entity<Employee>()
.HasOne(x => x.Company)
.WithMany(x => x.Employees)
.HasForeignKey(x => x.CompanyId).OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Company>().HasData(
new Company
{
Id = Guid.Parse("A732F0A4-B087-4783-954C-735205FA9A6E"),
Name = "Micorsoft",
Introduction = "Greate Company"
},
new Company
{
Id = Guid.Parse("C2A2C0B9-17A0-4246-B6AE-40E24D98FEB8"),
Name = "Google",
Introduction = "Don't be evil."
},
new Company
{
Id = Guid.Parse("EEEE8651-338A-46AE-9D82-CFE486F54F3D"),
Name = "Alipapa",
Introduction = "Fubao Company"
});
}
}
}
16. 创建好数据模型还得具体操作,这里采用接口的方式ICompanyRepository,实现接口是类CompanyRepository
```csharp
using Routine.API.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Routine.API.Services
{
interface ICompanyRepository
{
Task<IEnumerable<Company>> GetCompaniesAsync();
Task<Company> GetCompanyAsync(Guid companyId);
Task<IEnumerable<Company>> GetCompaniesAsync(IEnumerable<Guid> companyIds);
void AddCompany(Company company);
void UpdateCompany(Company company);
void DeleteCompany(Company company);
Task<bool> CompanyExistsAsync(Guid companyId);
Task<IEnumerable<Employee>> GetEmployeesAsync(Guid companyId);
Task<Employee> GetEmployeeAsync(Guid companyId, Guid employId);
void AddEmployee(Guid companyId, Employee employee);
void UpdateEmployee(Employee employee);
void DeleteEmployee(Employee employee);
Task<bool> SaveAsync();
}
}
using Routine.API.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Routine.API.Entities;
using Microsoft.EntityFrameworkCore;
namespace Routine.API.Services
{
public class CompanyRepository : ICompanyRepository
{
private readonly RoutineDbContext _context;
public CompanyRepository(RoutineDbContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
}
public async Task<IEnumerable<Company>> GetCompaniesAsync()
{
return await _context.Companies.ToListAsync();
}
public async Task<Company> GetCompanyAsync(Guid companyId)
{
if (companyId == Guid.Empty)
{
throw new ArgumentNullException(nameof(companyId));
}
return await _context.Companies.FirstOrDefaultAsync(x => x.Id == companyId);
}
//通过多个companyIds获取多个公司
public async Task<IEnumerable<Company>> GetCompaniesAsync(IEnumerable<Guid> companyIds)
{
if (companyIds == null)
{
throw new ArgumentNullException(nameof(companyIds));
}
return await _context.Companies
.Where(x => companyIds.Contains(x.Id))
.OrderBy(x => x.Name)
.ToListAsync();
}
public void AddCompany(Company company)
{
if (company == null)
{
throw new ArgumentNullException(nameof(company));
}
company.Id = Guid.NewGuid();
foreach (var employee in company.Employees)
{
employee.Id = Guid.NewGuid();
}
_context.Companies.Add(company);
}
public void UpdateCompany(Company company)
{
//_context.Entry(company).State == EntityState.Modified;
}
public void DeleteCompany(Company company)
{
if (company == null)
{
throw new ArgumentNullException(nameof(company));
}
_context.Companies.Remove(company);
}
public async Task<bool> CompanyExistsAsync(Guid companyId)
{
if (companyId == Guid.Empty)
{
throw new ArgumentNullException(nameof(companyId));
}
return await _context.Companies.AnyAsync(x => x.Id == companyId);
}
public async Task<IEnumerable<Employee>> GetEmployeesAsync(Guid companyId)
{
if (companyId == Guid.Empty)
{
throw new ArgumentNullException(nameof(companyId));
}
return await _context.Employees
.Where(x => x.CompanyId == companyId)
.OrderBy(x => x.EmployeeNo)
.ToListAsync();
}
//获取公司某一位员工
public async Task<Employee> GetEmployeeAsync(Guid companyId, Guid employeeId)
{
if (companyId == Guid.Empty)
{
throw new ArgumentNullException(nameof(companyId));
}
if (employeeId == Guid.Empty)
{
throw new ArgumentNullException(nameof(employeeId));
}
return await _context.Employees
.Where(x => x.CompanyId == companyId && x.Id == employeeId)
.FirstOrDefaultAsync();
}
//添加员工
public void AddEmployee(Guid companyId, Employee employee)
{
if (companyId == Guid.Empty)
{
throw new ArgumentNullException(nameof(companyId));
}
if (employee == null)
{
throw new ArgumentNullException(nameof(employee));
}
employee.CompanyId = companyId;
_context.Employees.Add(employee);
}
//更新员工信息
public void UpdateEmployee(Employee employee)
{
//_context.Entry(employee).State = EntityState.Modified;
}
public void DeleteEmployee(Employee employee)
{
_context.Employees.Remove(employee);
}
public async Task<bool> SaveAsync()
{
return await _context.SaveChangesAsync() >= 0;
}
}
}
- 实现了对数据的操作,还需要对其进行容器注册 ```csharp using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Routine.API.Data; using Routine.API.Services;
namespace Routine.API { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; }
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddScoped<ICompanyRepository, CompanyRepository>();
services.AddDbContext<RoutineDbContext>(option =>
{
option.UseSqlite("Data Source=routine.db");
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
} ``` :::info 特别值得说明的是,为了演示需要,采用每一次改动后,均会做删除数据库并再次迁移数据的处理哦! :::
- 最后还要说明的是,初次启用,需要手动迁移 —
- 搜索Package manager
- add-migration initial
- 此时运行程序 —>不要关心报错,只要终端显示的log级别是info就不要关心
- 紧接着解决方案中会生成数据库文件
- 最后可以检查这个数据库中的数据是否有正确插入,这里主要插入了三个公司。推荐使用JetBrains的DG,由于时间太晚了,明天晚上再更新吧,晚安,又是一天新的开始,加油(⊙o⊙)
更新,下载DataGrip
- 本文作者:GeekPower - Felix Sun
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!