创建Web API项目

打开Visual Studio,首先要选择ASP.NET Core 3.1。
这里选择了API这个模板 ,取消使用HTTPS和Docker。

查看json配置文件

  1. appsettings.json
  2. appsettings.Development.json
    注意:您需要知道appsettings.json 和 appsettings.Development.json之间的关系

参考
在 ASP.NET Core 中使用多个环境
https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/environments?WT.mc_id=DT-MVP-5003302&view=aspnetcore-3.1

删掉默认创建的示例文件:WeatherForecastController和WeatherForecast

Program.cs

Program类下的Main方法就是整个程序的入口,Main方法负责配置和运行整个Web程序。
由于这是一个Web项目,所以我们还需要一个宿主(Host),这个宿主就是由下面的CreateHostBuilder方法来负责创建的。该方法首先会创建出一个实现了IHostBuilder接口的类(HostBuilder)的实例,然后调用它的Build方法来创建宿主(类型为Host,实现了IHost接口),最后调用宿主上面的Run方法来运行程序。
我们暂时不修改这里面的代码,所以一切都会按照项目模板默认的配置进行,注意到下面的方法里我们使用到了Startup这个类:

Startup.cs

我们看到IConfiguration被注入了,这样就允许我们使用配置信息了,例如appsettings.json里面的配置信息。

ConfigureServices方法

这个方法负责向服务容器里面注册服务,已注册的服务可以通过依赖注入的方式在整个应用程序的其它地方进行使用。这里的服务是一个比较广义的概念,它就是一个在整个程序中做一些通用性操作的组件。

Configure方法

这个方法使用到了在ConfigureServices方法里面注册和配置的服务,所以这个方法是在ConfigureServices方法之后被调用的。
Configure方法是用来指定ASP.NET Core Web程序是如何响应每一个HTTP请求的。换句话说,也就是我们在这里配置请求的管道,配置的方法就是在这里添加很多中间件(Configure方法里面每一个app.UseXxx就是添加一个中间件,可以查看中间件的官方文档来了解更多)。
在开发环境的时候,如果有异常发生,那么会展示一个异常页面

if (env.IsDevelopment())
 {
     app.UseDeveloperExceptionPage();
 }

app.UseAuthorization(),它会为整个Web程序添加授权的能力。当你需要考虑API安全性的时候,这点就很重要了。通常授权配置是在ConfigureServices方法里完成的,而我现在没有对授权进行配置,但是app.UseAuthorization()仍然会允许API可以被匿名的访问。

修改项目启动配置(不使用IIS启动WebAPI,使用SelfHost启动)

我喜欢使用控制台启动Web程序,这样可以很直观的看到Log信息。为达到这个目的,可以修改launchSettings.json文件:(把使用IIS启动项去掉) ,然后右键项目属性,在调试里面把启动项改为项目

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0VZaR0sd-1580486627721)(D:\note\webapi\pic\1.png)]

{
  "$schema": "http://json.schemastore.org/launchsettings.json",
  "profiles": {
    "Routine.Api": {
      "commandName": "Project",
      "launchBrowser": true,
      "launchUrl": "api/companies",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      },
      "applicationUrl": "http://localhost:5000"
    }
  }
}

添加SQLite数据库

想要做RESTful API的话,我们还需要数据,这里我准备采用SQLite来作为数据存储,使用Entity Framework Core 作为 ORM来与数据库进行交互。

  1. 安装EntityFramework Sqlite和Microsoft.EntityFrameworkCore.Tools包
  2. 创建Entity: Company Employee
using System;
using System.Collections.Generic;

namespace Routine.Api.Entity
{
    public class Company
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public string Introduction { get; set; }
        public ICollection<Employee> Employees { get; set; }
    }
}

using System;

namespace Routine.Api.Entity
{
    public class Employee
    {
        public Guid Id { get; set; }
        public string EmployeeNo { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public Gender Gender { get; set; }
        public DateTimeOffset DateOfBirth { get; set; }
        public Guid CompanyId { get; set; }
        public Company Company { get; set; }
    }
    public enum Gender
    {
        男 = 1,
        女 = 2
    }
}
  1. 创建DbContext
using Microsoft.EntityFrameworkCore;
using Routine.Api.Entity;
using System;

namespace Routine.Api.Data
{
    public class RoutineDbContext : DbContext
    {
        public RoutineDbContext(DbContextOptions<RoutineDbContext> options) : base(options)
        {

        }
        public DbSet<Company> Companies { get; set; }
        public DbSet<Employee> Employees { get; set; }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Company>()
                .Property(x => x.Name).IsRequired().HasMaxLength(100);
            modelBuilder.Entity<Company>()
                .Property(x => x.Introduction).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.Cascade);

            //添加种子数据
            //种子数据
            modelBuilder.Entity<Company>().HasData(
                new Company
                {
                    Id = Guid.Parse("bbdee09c-089b-4d30-bece-44df5923716c"),
                    Name = "Microsoft",
                    Introduction = "Great Company"
                },
                new Company
                {
                    Id = Guid.Parse("6fb600c1-9011-4fd7-9234-881379716440"),
                    Name = "Google",
                    Introduction = "Don't be evil"
                },
                new Company
                {
                    Id = Guid.Parse("5efc910b-2f45-43df-afee-620d40542853"),
                    Name = "Alipapa",
                    Introduction = "Fubao Company"
                }
            );
            modelBuilder.Entity<Employee>().HasData(
                new Employee
                {
                    Id = Guid.Parse("ca268a19-0f39-4d8b-b8d6-5bace54f8027"),
                    CompanyId = Guid.Parse("bbdee09c-089b-4d30-bece-44df5923716c"),
                    DateOfBirth = new DateTime(1955, 10, 28),
                    EmployeeNo = "M001",
                    FirstName = "William",
                    LastName = "Gates",
                    Gender = Gender.男
                },
                new Employee
                {
                    Id = Guid.Parse("265348d2-1276-4ada-ae33-4c1b8348edce"),
                    CompanyId = Guid.Parse("bbdee09c-089b-4d30-bece-44df5923716c"),
                    DateOfBirth = new DateTime(1998, 1, 14),
                    EmployeeNo = "M024",
                    FirstName = "Kent",
                    LastName = "Back",
                    Gender = Gender.男
                },
                new Employee
                {
                    Id = Guid.Parse("47b70abc-98b8-4fdc-b9fa-5dd6716f6e6b"),
                    CompanyId = Guid.Parse("6fb600c1-9011-4fd7-9234-881379716440"),
                    DateOfBirth = new DateTime(1986, 11, 4),
                    EmployeeNo = "G003",
                    FirstName = "Mary",
                    LastName = "King",
                    Gender = Gender.女
                },
                new Employee
                {
                    Id = Guid.Parse("059e2fcb-e5a4-4188-9b46-06184bcb111b"),
                    CompanyId = Guid.Parse("6fb600c1-9011-4fd7-9234-881379716440"),
                    DateOfBirth = new DateTime(1977, 4, 6),
                    EmployeeNo = "G007",
                    FirstName = "Kevin",
                    LastName = "Richardson",
                    Gender = Gender.男
                },
                new Employee
                {
                    Id = Guid.Parse("a868ff18-3398-4598-b420-4878974a517a"),
                    CompanyId = Guid.Parse("5efc910b-2f45-43df-afee-620d40542853"),
                    DateOfBirth = new DateTime(1964, 9, 10),
                    EmployeeNo = "A001",
                    FirstName = "Jack",
                    LastName = "Ma",
                    Gender = Gender.男
                },
                new Employee
                {
                    Id = Guid.Parse("2c3bb40c-5907-4eb7-bb2c-7d62edb430c9"),
                    CompanyId = Guid.Parse("5efc910b-2f45-43df-afee-620d40542853"),
                    DateOfBirth = new DateTime(1997, 2, 6),
                    EmployeeNo = "A201",
                    FirstName = "Lorraine",
                    LastName = "Shaw",
                    Gender = Gender.女
                }
            );

        }
    }
}
  1. 创建IRepository和Repository
    IRepository.cs:
using Routine.Api.Entity;
using Routine.Api.Helpers;
using Routine.Api.ResourceParameters;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Routine.Api.Services
{
    public interface ICompanyRepository
    {
        Task<IEnumerable<Employee>> GetEmployeesAsync(Guid companyId, string gender);
        Task<Employee> GetEmployeeAsync(Guid CompanyId, Guid employeeId);
        void AddEmployee(Guid companyId, Employee employee);
        void UpdateEmployee(Employee employee);
        void DeleteEmployee(Employee employee);


        Task<Company> GetCompanyAsync(Guid companyId);
        Task<PagedList<Company>> GetCompaniesAsync(CompanyDtoParameter parameter);
        Task<IEnumerable<Company>> GetCompaniesAsync(IEnumerable<Guid> companyIds);
        void AddCompany(Company company);
        void DeleteCompany(Company company);
        void UpdateCompany(Company company);
        Task<bool> CompanyExistsAsync(Guid companyId);
        Task<bool> SaveAsync();
    }
}

Repository.cs

using Microsoft.EntityFrameworkCore;
using Routine.Api.Data;
using Routine.Api.Entity;
using Routine.Api.Helpers;
using Routine.Api.ResourceParameters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Routine.Api.Services
{
    public class CompanyRepository : ICompanyRepository
    {
        private readonly RoutineDbContext context;

        public CompanyRepository(RoutineDbContext context)
        {
            this.context = context ?? throw new ArgumentNullException(nameof(context));
        }
        public void AddCompany(Company company)
        {
            if (company == null)
            {
                throw new ArgumentNullException(nameof(company));
            }
            company.Id = Guid.NewGuid();
            if (company.Employees != null)
            {
                foreach (var employee in company.Employees)
                {
                    employee.Id = Guid.NewGuid();
                }
            }

            context.Companies.Add(company);
        }

        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 Task<bool> CompanyExistsAsync(Guid companyId)
        {
            if (companyId == Guid.Empty)
            {
                throw new ArgumentNullException(nameof(companyId));
            }
            return context.Companies.AnyAsync(x => x.Id == companyId);
        }

        public void DeleteCompany(Company company)
        {
            if (company == null)
            {
                throw new ArgumentNullException(nameof(company));
            }
            context.Companies.Remove(company);
        }

        public void DeleteEmployee(Employee employee)
        {
            if (employee == null)
            {
                throw new ArgumentNullException(nameof(employee));
            }
            context.Employees.Remove(employee);
        }



        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(a => a.Name)
                .ToListAsync();
        }

        public Task<Company> GetCompanyAsync(Guid companyId)
        {
            if (companyId == Guid.Empty)
            {
                throw new ArgumentNullException(nameof(companyId));
            }
            return context.Companies
                .FirstOrDefaultAsync(a => a.Id == companyId);
        }

        public async Task<PagedList<Company>> GetCompaniesAsync(CompanyDtoParameter parameter)
        {
            if (parameter == null)
            {
                //return await context.Companies.ToListAsync();
                throw new ArgumentNullException(nameof(parameter));
            }

            //if (string.IsNullOrEmpty(parameter.CompanyName)&&
            //    string.IsNullOrEmpty(parameter.SearchTerm))
            //{
            //    return await context.Companies.ToListAsync();
            //}

            var queryExpression = context.Companies as IQueryable<Company>;
            if (!string.IsNullOrWhiteSpace(parameter.CompanyName))
            {
                queryExpression = queryExpression.Where(x => x.Name == parameter.CompanyName.Trim());
            }
            if (!string.IsNullOrWhiteSpace(parameter.SearchTerm))
            {
                queryExpression = queryExpression.Where(x => x.Name == parameter.SearchTerm.Trim() ||
                     x.Introduction.Contains(parameter.SearchTerm.Trim()));
            }
            //分页
            //queryExpression = queryExpression
            //    .Skip(parameter.PageSize * (parameter.PageNumber - 1))
            //    .Take(parameter.PageSize);
            //return await queryExpression.ToListAsync();
            return await PagedList<Company>.CreateAsync(queryExpression, parameter.PageNumber, parameter.PageSize);
        }

        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 async Task<IEnumerable<Employee>> GetEmployeesAsync(Guid companyId, string gender)
        {
            if (companyId == Guid.Empty)
            {
                throw new ArgumentNullException(nameof(companyId));
            }
            if (string.IsNullOrWhiteSpace(gender))
            {
                return await context.Employees
                    .Where(x => x.CompanyId == companyId)
                    .OrderBy(x => x.EmployeeNo)
                    .ToListAsync();
            }

            //判断Gender是否转换成功
            Gender gender1;
            var isGender = Enum.TryParse<Gender>(gender.Trim(), out gender1);
            if (isGender)
            {
                return await context.Employees
                .Where(x => x.CompanyId == companyId && x.Gender == gender1)
                .OrderBy(x => x.EmployeeNo)
                .ToListAsync();
            }
            else
            {
                throw new ArgumentNullException(nameof(gender));
            }

        }

        public async Task<bool> SaveAsync()
        {
            return await context.SaveChangesAsync() >= 0;
        }

        public void UpdateCompany(Company company)
        {
            context.Entry<Company>(company).State = EntityState.Modified;
        }

        public void UpdateEmployee(Employee employee)
        {

        }
    }
}
  1. 在Startup的ConfigureServices中配置DbContext和Repository依赖注入
services.AddScoped<ICompanyRepository, CompanyRepository>();
services.AddDbContext<RoutineDbContext>(optionsAction: option =>
{
     option.UseSqlite(connectionString: "Data Source=routine.db");
});
  1. 在Controller中使用Repository
//配置公司员工一对多关系
//配置级联删除,当公司有员工时,不允许删除
modelBuilder.Entity<Employee>()
    .HasOne(x => x.Company)
    .WithMany(x => x.Employees)
    .HasForeignKey(x => x.CompanyId)
    .OnDelete(DeleteBehavior.Restrict);

这里使用DeleteBehavior.Restrict,删除的时候,当公司下面有员工的时候会无法删除
7. 添加种子数据
为了演示方便,我让数据库在每次程序启动的时候都会被删掉并重新进行迁移,同时设置好种子数据。

using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Routine.Api.Data;
using System;

namespace Routine.Api
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = CreateHostBuilder(args).Build();
            //为了获取dbcontext服务,因为这里还无法使用构造函数依赖注入服务
            using (var scope = host.Services.CreateScope())
            {
                try
                {
                    RoutineDbContext dbContext = scope.ServiceProvider.GetService<RoutineDbContext>();
                    dbContext.Database.EnsureDeleted();
                    dbContext.Database.Migrate();
                }
                catch (Exception ex)
                {
                    var logger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>();
                    logger.LogError(ex, message: "Database Migration Error!");
                }
            }
            host.Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}
  1. 添加数据迁移
Add-Migration Initial
  1. 运行程序,可以看到一下程序运行的过程中使用的SQL语句