Restful Api (实现公司和员工管理系统)大佬杨旭

1.MVC映射为API

  • Model,它负责处理数据的逻辑;
  • View,负责展示数据,在API里面一般是Json;
  • Controller, 它负责View和Model之间的交互;

和API交互的用户,称为是Api的消费者:(比如Angular写的单页应用程序)



先看整个目录结构

在这里插入图片描述

2.看 Program 这个类

主要还是创建了一个宿主,建立一个宿主,然后运行:

 public class Program
    {
    
    
        public static void Main(string[] args)
        {
    
    
            CreateHostBuilder(args).Build().Run(); 
             //因为它是一个web程序,所以它需要一个宿主。
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
        //新建的类,它实现IHostBuilder的接口;
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
    
    
                    webBuilder.UseStartup<Startup>();
                });
    }

3.看Starup这个类

注意每个中间件都有可能触发短路机制:

 public class Startup
    {
    
    
        public Startup(IConfiguration configuration)
        //构造函数,引用配置信息。
        {
    
    
            Configuration = configuration;
        }

        public IConfiguration Configuration {
    
     get; }

        public void ConfigureServices(IServiceCollection services)
            //容器,放在这里面的东西,全局都在可以使用依赖注入的方法进行引用
        {
    
    
            services.AddControllers();
        }
        
        //管道里面的每个中间件,都有可能触发短路机制;
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            //配置请求的管道,要在ConfigureService后边引用
        {
    
    
            if (env.IsDevelopment())
            {
    
    
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseAuthorization();
            //这个通常是在ConfigureService配置。要放在http请求之前;

            app.UseEndpoints(endpoints =>
            {
    
    
                endpoints.MapControllers();
                //访问controller
            });
        }
    }

4.更改配置文件,默认自托管

{
    
    
  //删除的时候,第一行和第二行要留着
  "$schema": "http://json.schemastore.org/launchsettings.json",
  "profiles": {
    
    
    "RestfullApi": {
    
    
      "commandName": "Project",
      "launchBrowser": true,
      "launchUrl": "weatherforecast",
      "applicationUrl": "http://localhost:5000",
      "environmentVariables": {
    
    
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

5.建立Model。这里是建立 Entities 类

5.1先建立 Company 类(注意 list 类可以用快捷键来生成)

 public class Company
    {
    
    
        public Guid Id {
    
     get; set; }
        public string Name {
    
     get; set; }
        public string Introduction {
    
     get; set; }
        public List<Employee> Employees {
    
     get; set; } 
        //用快捷键生成 Employee 类
    }

5.2建立 Employee 类(注意指定导航属性和外键等)

 public class Employee
    {
    
    
        public Guid  Id {
    
     get; set; }     //主键
        public Guid CompanyId {
    
     get; set; }   //外键。指向Company的主键 
        public Company Company {
    
     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; }
    }

6.建立DBcontext类

指定 EF core 的类,(建立数据库的表,指定类的成员的限制、一对多的关系、种子数据等)
注意 guid 可以在线生成:

  public class RoutineDbContext : DbContext
    {
    
    
        //base 这个方法的具体用法,回去再查!
        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).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)

 //上边指定的是什么意思:一个employee对应一个Company,同时一个company对应多个Employees

                .HasForeignKey(x => x.CompanyId)//指定外键
                .OnDelete(DeleteBehavior.Restrict);//当删除的时候,如果company下边有员工的话,就无法删除;

            //指定种子数据:
            //Guid有在线生成工具,在线生成两个就行;
            modelBuilder.Entity<Company>().HasData
          (
                new Company
                {
    
    
                    Id = Guid.Parse("d0176f72-33d0-4b48-a5ac-2c748becf3a4"),
                    Name = "Microsoft",
                    Introduction = "Great Company"
                },
                new Company
                {
    
    
                    Id = Guid.Parse("786cb431-4228-4489-8841-0ed6a16e13e4"),
                    Name = "Google",
                    Introduction = "Don't be evil",
                },
                new Company
                {
    
    
                    Id = Guid.Parse("ac531f21-ccc8-4576-977a-5e3217236fa2"),
                    Name = "Alipapa",
                    Introduction = "Taobao Company"
                } ); } }

7.建立 Repository 仓储接口

实现对数据库的增删改查)

7.1接口的类
_context.Companies.Add(company);比如这句话,实际上 context.Companies 就是对对context 里面的 Companies 这个数据表

 public  interface ICompanRepository
    {
    
     
        //repository相当于过去的doc层;

        //针对公司的增删改查;
        Task<List<Company>> GetCompaniesAsync();
        Task<Company> GetCompanyAsync(Guid companyId);
        Task<List<Company>> GetCompaniesAsync(List<Guid> companyids);
        void AddCompany(Company company);
        void UpdateCompany(Company company);
        void DeleteCompany(Company company);
        Task<bool> CompanyExistsAsync(Guid companyId);

        //对员工的增删改查:
        Task<List<Employee>> GetEmployeesAsync(Guid companyId);
        Task<Employee> GetEmployeeAsync(Guid companyId, Guid employeeId);
        void AddEmployee(Guid companyid, Employee employee);
        void UpdateEmployee(Employee employee);
        void DeleteEmployee(Employee employee);

        //保存的动作;
        Task<bool> SavaAsync();
 }

7.2实现这个接口:

 public class CompanyRepository : ICompanRepository
    {
    
    
        //把DBconText,用构造函数的方式传进来;
        private readonly RoutineDbContext _context;
        public CompanyRepository(RoutineDbContext routineDbContext)
        {
    
    
            this._context = routineDbContext ?? 
                throw new ArgumentNullException(nameof(routineDbContext));
            //如果是null的话就抛出一个异常;
        }
        //一般用数据库操作的话,都是异步的;
        //下边对数据库的所有操作。都是基于dbcontext的;

        //获取所有的公司信息;
        public async Task<List<Company>> GetCompaniesAsync()
        {
    
    
            return await _context.Companies.ToListAsync();
        }

        //和上边相当于重载
        public async Task<Company> GetCompanyAsync(Guid companyId)
        {
    
    
            if (companyId==Guid.Empty)//guid判断空
            {
    
    
                throw new ArgumentNullException(nameof(companyId) );
            }
            return await _context.Companies.FirstOrDefaultAsync(x=>x.Id==companyId);
            //注意是恒等
        }
        public async Task<List<Company>> GetCompaniesAsync(List<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();//Id 是当时生成的;
            foreach (var employee in company.Employees)
            {
    
    
                employee.Id = Guid.NewGuid();
            }
            _context.Companies.Add(company);
        }
        public void UpdateCompany(Company company)
        {
    
    
            //这个是显示跟踪的,不用加也行。
           // throw new NotImplementedException();
        }
        public void DeleteCompany(Company company)
        {
    
    
            if (company==null)
            {
    
    
                throw new ArgumentNullException(nameof(company));
            }
            _context.Companies.Remove(company);//删除是remove
        }
            //通过 Id 判断公司存不存在;
        public async Task<bool> CompanyExistsAsync(Guid companyId)
        {
    
    
            if (companyId==Guid.Empty)
            {
    
    
                throw new ArgumentNullException(nameof(companyId));
            }
            //DBcontext怎么判断是否存在:
            return await _context.Companies.AnyAsync(x => x.Id == companyId);
        }
        //根据公司ID寻找所有的员工信息;
        public async Task<List<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(); //只要使用异步就要用await
        }

        //拿这个重点练习练习;
        //获得公司下某一个员工的方法。这两个都要传进来
        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)
        {
    
    
           // 更新员工这个也不用写了;
        }
        public void DeleteEmployee(Employee employee)
        {
    
    
            _context.Employees.Remove(employee);
        }   
        public async Task<bool> SavaAsync()
        {
    
    
           return await _context.SaveChangesAsync()>=0;
           //它这返回的是保存的数量,一般不在这里写;
        }    
    }

8.配置 EF core 实现迁移

8.1首先,引用两个 nuget 包
在这里插入图片描述

8.2 在 starup 类里面配置 EF core 链接字符串(指定数据库的)

  public void ConfigureServices(IServiceCollection services)
            //容器,放在这里面的东西,全局都在可以使用依赖注入的方法进行引用
        {
    
    
            services.AddControllers();

            services.AddScoped<ICompanRepository,CompanyRepository>();
            //把repositroy实现一下   scoped表示每一次http请求都是返回一个实例;

            //对dbcontext有封装好的方法;
            services.AddDbContext<RoutineDbContext>(option =>             
            {
    
       //指定使用的是什么数据库
                option.UseSqlServer  //链接字符串;
                ("Server=(localdb)\\mssqllocaldb;Database=RestfulApi;Integrated Security=True");
            } );}

8.3 在安装包程序管理控制台,执行迁移命名

8.3.1可以先用 get-help entityframework 命令,查看都是有哪些操作
在这里插入图片描述
8.3.2 为了避免每次都执行迁移命令,可在 Main 函数里面,宿主建立后,运行前,自动执行迁移命令;对 Main 函数的更改如下:(但是记住,第一次的迁移命令还是要添加的)

  public static void Main(string[] args)
        {
    
    
           var host = CreateHostBuilder(args).Build();  //因为它是一个web程序,所以它需要一个宿主。

            using (var scope=host.Services.CreateScope())
            {
    
    
                try
                {
    
    
                    var dbContext = scope.ServiceProvider.GetService<RoutineDbContext>(); //把容器内的服务给弄出来;
                    dbContext.Database.EnsureDeleted();//每次都是把容器的东西个删了;
                    dbContext.Database.Migrate();//自动迁移
                }
                catch (Exception e)
                {
    
    
                    //如果发生错误的话,就记录一下日志;
                    var logger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>();
                    logger.LogError(e,"Datebase Migration Error!");
                }
            }
            host.Run();//在这个host运行之前搞点东西。
        }

8.3.3迁移完成后生成相应的数据
在这里插入图片描述

执行 update-database -verbose 可以把执行的明细打印出来

EFcore也可以执行反向工程:即根据已经生成的数据库,反过来生成代码。

9.Rest风格

  1. 资源使用名词,而不是动词;
  2. 用 API 要体现资源的结构和关系;
  3. 建议路由做法: (至于到底是用复数还是单数,这个一直有争议,杨旭一直用复数)

GET api / companies / { companyId } / employees

自定义查询怎么命名:(获取所有的用户信息,并将结果按照年龄从大到小进行排序)

api / users ? orderby=name

10.用 RestfullAPI 实现 Controller 里面的相关方法

注意是如何json序列化的:

 [Route("api/companies")]
    [ApiController]//要求使用属性路由。。。自动http 400 相应。 frombody自动识别;
    public class CompaniesController : ControllerBase
    {
    
    
        private readonly ICompanRepository companRepository;

        public CompaniesController(ICompanRepository companRepository)
        {
    
    
            this.companRepository = companRepository;
        }

        [HttpGet]
        public async Task<IActionResult> GetCompanies()
        {
    
    
            var companies = await companRepository.GetCompaniesAsync();
            return new JsonResult(companies);//Json序列化;
        }

状态码

  1. 1 开头的状态码是信息类的状态码,RestfulAPi不支持这种状态码;
  2. 2XX 的状态码表示请求已经成功。。200—OK。表示请求成功。。201——Created。表示请求成功并创建了资源。。。204——No Content 表示请求成功,但是不返回任何东西。例如删除操作。
  3. 3XX 开头的状态码,表示某个网页的网址已经永久的改变了,绝大多数的Web Api 都不需要这个使用这类的状态码。
  4. 4XX 表示客户端的错误。
  5. 5XX 表示服务器出现了错误,最常用的是500,表示服务器端出现了错误,客户端无能为力,只能以后再试了。

注意错误(error)和故障(Faults)的区别,错误是4开头的,故障是5开头的;

路由:

路由习惯建议使用 [ httpget (“api/companyies”) ] 这种写法
更改启动时的路径,如下图(注意不是直接在http//localhost:5000后边直接加!)
在这里插入图片描述
具体实现代码:

 [Route("api/companies")]
    [ApiController]
    public class CompaniesController : ControllerBase
    {
    
    
        private readonly ICompanRepository companRepository;

        public CompaniesController(ICompanRepository companRepository)
        {
    
    
            this.companRepository = companRepository
                ?? throw  new ArgumentNullException(nameof(companRepository));//是throw一个异常。            ;
        }

        [HttpGet]
        public async Task<IActionResult> GetCompanies()
        {
    
    
            var companies = await companRepository.GetCompaniesAsync();
            //  return new JsonResult(companies);
            return Ok(companies);
        }

        [HttpGet("{companyId}")]
        public async Task<IActionResult> GetCompany(Guid companyId)
        {
    
    
            var company = await  companRepository.GetCompanyAsync(companyId);
            if (company==null)
            {
    
    
                return NotFound();
            }
            return Ok(company);
        }
    }

11.内容协商

Restfull Api 并没有强制要求返回 Json 格式的返回结果,但是一般都默认Json,也可以选择其他的表述格式,如Xml,在多种表述格式中选择最佳的表述,这就是内容协商 。

Accept 当浏览器发送Http请求时,可以在 header 里面指定要返回的媒体类型:

  • application/json
  • application/xml

如果请求的格式不能被服务器支持,则返回 406 状态码

Content-Type 当浏览器发向服务器输入数据时,如 post 时候,需要指定媒体类型,相当于自描述,
用 postman 进行测试,如下:
在这里插入图片描述

让webApi支持XMl格式的文件:

1.因为默认情况,webapi,在返回不支持媒体类型时,返回Json,不报错。
2.让webapi 支持Json格式的文件:

在 services.AddControllers 里面完成相关代码

  services.AddControllers(options =>
            options.ReturnHttpNotAcceptable = true 
            //他默认是关闭的
            ).AddXmlDataContractSerializerFormatters();
            //添加对XML的支持

12.Dto

1.在没有Dto之前,是如何做相关映射的:

  1. IActionResult 的返回类型,能明确尽量明确,这样在做swagger的时候可以更方便;
  2. 注意建立 companyDto1 的时候犯的几个错误;
 public async Task<ActionResult<List<CompanyDto>>> GetCompanies()
        {
    
    
            var companies = await companRepository.GetCompaniesAsync();
            //  return new JsonResult(companies);

            var companyDto1 = new List<CompanyDto>();//这个地方要用 new
            foreach (var company in companies)
            {
    
    
                companyDto1.Add(  //往数组里添加数据使用Add
                    new CompanyDto
                    {
    
    
                        Id = company.Id,
                        CompanyName = company.Name
                    });
            }
          return Ok(companyDto1);
        }

2.使用 AutoMapper 映射器 将 Entity 里面的东西,映射到 Model 里面(Dto),映射相关的文件,具体命名目录如下;

在这里插入图片描述
如何用 AutoMapper 来实现映射:

1.引用 Nuget 包,此处引用第二个,能和 AutoMapper 更好的结合、

在这里插入图片描述

2.建立Mode类。里面建立 CompanyDto 这个类

 public class CompanyDto
    {
    
    
        public Guid Id {
    
     get; set; }
        public string  CompanyName {
    
     get; set; }
    }

相对应的是 Company 这个类

 public class Company
    {
    
    
        public Guid Id {
    
     get; set; }
        public string Name {
    
     get; set; }
        public string Introduction {
    
     get; set; }
        public List<Employee> Employees {
    
     get; set; }
         //用快捷键生成 Employee 类
    }

建立 Profiles 这个文件夹,在文件夹里面建立 CompanyProfile 这个类:具体的代码如下:注意要继承于 Profile 这个类;

 public class CompanyProfile:Profile
    {
    
    
        public CompanyProfile()
        {
    
    
            CreateMap<Company, CompanyDto>()//从Company映射到Dto
                .ForMember(destination => destination.CompanyName,//为每个成员添加配置
                option => option.MapFrom(source => source.Name));
            //如果空引用的话会直接被忽略
            //如果名字一样的话,可以直接映射;
            //上边destination是目的地的意思:
            //source是源头的意思;
        } }}

Startup 类里面实现依赖注入:

 // 对象映射器;
   services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
   //获得当前域的集合

13.获得父子关系

先看创建后,新增和更改的文件夹:
在这里插入图片描述

1.或得父子关系,即根据路由模板
Companyee/{CompanyId}/Employees/{employessId} 来获得相关的信息
2.在 DBContext 里添加种子数据,种子数据要添加到 modelBuilder.Entity().hasData里面。
3.种子数据的 Id 等自动生成的数据都要手动添加

具体代码如下:

  //种子数据要单独往里加
            modelBuilder.Entity<Employee>().HasData(
                 new Employee
                 {
    
    
                     //种子数据中的ID也需要手动赋值
                     Id = Guid.Parse("6137cbc3-660f-4a53-a6cc-3de1a37cb6fa"),
                     CompanyId = Guid.Parse("d0176f72-33d0-4b48-a5ac-2c748becf3a4"),
                     DateOfBirth = new DateTime(1984, 11, 4),
                     EmployeeNo = "G006",
                     FirstName = "Mary",
                     LastName = "King",
                     Gender = Gender.},
                 new Employee
                 {
    
    
                     Id = Guid.Parse("1ad9b4ab-d892-4c1d-a8ea-51bced126d21"),
                     CompanyId = Guid.Parse("d0176f72-33d0-4b48-a5ac-2c748becf3a4"),
                     DateOfBirth = new DateTime(1994, 11, 24),
                     EmployeeNo = "G007",
                     FirstName = "aaa",
                     LastName = "zhao",
                     Gender = Gender.},
                  new Employee
                  {
    
    
                      Id = Guid.Parse("d9fdb98e-2601-4c39-8ed0-65e61fba0a73"),
                      CompanyId = Guid.Parse("d0176f72-33d0-4b48-a5ac-2c748becf3a4"),
                      DateOfBirth = new DateTime(1984, 11, 4),
                      EmployeeNo = "G026",
                      FirstName = "Aary",
                      LastName = "bng",
                      Gender = Gender.},

                   new Employee
                   {
    
    
                       Id = Guid.Parse("50b8ab5f-4a30-4d65-97c7-cf6c783e65bf"),
                       CompanyId = Guid.Parse("786cb431-4228-4489-8841-0ed6a16e13e4"),
                       DateOfBirth = new DateTime(1995, 8, 9),
                       EmployeeNo = "G015",
                       FirstName = "shighy",
                       LastName = "nhoie",
                       Gender = Gender.},
                    new Employee
                    {
    
    
                        Id = Guid.Parse("6782f6a9-3a2c-4316-a402-b66d525fcf0e"),
                        CompanyId = Guid.Parse("786cb431-4228-4489-8841-0ed6a16e13e4"),
                        DateOfBirth = new DateTime(1994, 11, 24),
                        EmployeeNo = "G497",
                        FirstName = "smgo",
                        LastName = "qinghi",
                        Gender = Gender.},
                     new Employee
                     {
    
    
                         Id = Guid.Parse("7adef789-951f-4830-b9b0-4f09441f165c"),
                         CompanyId = Guid.Parse("786cb431-4228-4489-8841-0ed6a16e13e4"),
                         DateOfBirth = new DateTime(1972, 6, 9),
                         EmployeeNo = "G04949",
                         FirstName = "BVer",
                         LastName = "EOheih",
                         Gender = Gender.},
                      new Employee
                      {
    
    
                          Id = Guid.Parse("dc32d25f-a390-4f09-8eac-bf92d8b5e09f"),
                          CompanyId = Guid.Parse("ac531f21-ccc8-4576-977a-5e3217236fa2"),
                          DateOfBirth = new DateTime(1997, 1, 9),
                          EmployeeNo = "G686",
                          FirstName = "shighi",
                          LastName = "Qian",
                          Gender = Gender.},
                    new Employee
                    {
    
    
                        Id = Guid.Parse("2a415a35-936e-4cdf-9239-653e9cdcb463"),
                        CompanyId = Guid.Parse("ac531f21-ccc8-4576-977a-5e3217236fa2"),
                        DateOfBirth = new DateTime(1994, 12, 24),
                        EmployeeNo = "Ghii7",
                        FirstName = "Sun",
                        LastName = "LI",
                        Gender = Gender.},
                     new Employee
                     {
    
    
                         Id = Guid.Parse("f93b3d61-318e-4123-9b84-ceb2759ea445"),
                         CompanyId = Guid.Parse("ac531f21-ccc8-4576-977a-5e3217236fa2"),
                         DateOfBirth = new DateTime(1993, 8, 8),
                         EmployeeNo = "G4987489",
                         FirstName = "BVa",
                         LastName = "Zhang",
                         Gender = Gender.}
                ); 

4.更改了数据库文件以后,在 nuget 管理控制台 做数据迁移,即执行下边两个命令

  1. add-migration addemployeeNuber
  2. update-database

5.做 employee 的DTO。代码如下:

 public class EmployeeDto
    {
    
    
        public Guid Id {
    
     get; set; }     //主键
        public Guid CompanyId {
    
     get; set; }   //外键。指向Company的主键 

        //不能是company,可以是 companyDto 。
       // public Company Company { get; set; }//关联的导航属性;

        public string EmployeeNo {
    
     get; set; }
        public string Name {
    
     get; set; }
        public string  GenderDisplay {
    
     get; set; }    //枚举类型要先写
        public int Age {
    
     get; set; }

    }

6.在 EmploeeProfile 里面做对象映射

  public EmpoyeeProfile()
        {
    
    
            CreateMap<Employee, EmployeeDto>( )
                .ForMember(
                //先写对象,然后写对象怎么来的
                dest=>dest.Name,
                opton=>opton.MapFrom(src=>$"{src.FirstName} {src.LastName}"))
                .ForMember(dest=>dest.GenderDisplay,opt=>opt.MapFrom(src=>src.Gender.ToString()))
                .ForMember(dest=>dest.Age,opt=>opt.MapFrom(src=>DateTime.Now.Year-src.DateOfBirth.Year)) ;
        }

7。建立Controller. 根据 companID 获得员工的所有的员工信息: 根据companyId 和 employeeId 获得单个员工的信息。具体代码如下:(emploeeId一定要加括号)

   //获取某一个员工的根据公司Id 获得公司下的所有员工
        [HttpGet]
        public async Task<ActionResult<List<EmployeeDto>>> GetEmoloyeesForCompany(Guid companyId)
        {
    
    
            if (!await companRepository.CompanyExistsAsync(companyId))
            {
    
    
                return NotFound();
            }
            var employees = await companRepository.GetEmployeesAsync(companyId);
            var employeeDto = mapper.Map<List<EmployeeDto>>(employees);
            //把employee映射成EmployeeDto这种类型
            return Ok(employeeDto);
        }
        //employeeId上要加括号;
        [HttpGet("{employeeId}")]
        public async Task<ActionResult<EmployeeDto>> GetEmoloyeeForCompany(Guid companyId,Guid employeeId)
        {
    
    
            if (!await companRepository.CompanyExistsAsync(companyId))
            {
    
    
                return NotFound();
            }
            var employee = await companRepository.GetEmployeeAsync(companyId,employeeId);
            if (employee == null)
            {
    
    
                return NotFound();
            }
            var employeeDto = mapper.Map<EmployeeDto>(employee);
            //把employee映射成EmployeeDto这种类型
            return Ok(employeeDto);
        }

在这里插入图片描述

14.处理服务器端的故障

1.例如在 Controller 里面抛出一个异常,代码如下,

 public async Task<ActionResult<List<CompanyDto>>> GetCompanies()
        {
    
    
            throw new Exception("这个是个错误");

            var companies = await companRepository.GetCompaniesAsync();

            //  return new JsonResult(companies);

            var companyDto1 = new List<CompanyDto>();
            //这个地方要用 new
            foreach (var company in companies)
            {
    
    
                companyDto1.Add(
                    new CompanyDto
                    {
    
    
                        Id = company.Id,
                        CompanyName = company.Name
            }); }
            var companyDto = mapper.Map< List < CompanyDto >> (companies);

            return Ok(companyDto);
        }

当异常抛出的时候,会出现如下界面,因为这是开发环境,所以可以把异常来抛出,但是在实际的生产环境中,不会抛出这些详细的错误信息,不然会给系统带来安全隐患。
在这里插入图片描述

当把开发环境改成 production 的时候,会返回空白页

在这里插入图片描述
但是我们很多时候,还是希望在其他环境中,能返回一些错误信息。可在Configure 里面进行配置,代码如下

  public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
    
    
            if (env.IsDevelopment())
            {
    
    
                app.UseDeveloperExceptionPage();
            }
            else
            {
    
    
                app.UseExceptionHandler(appBuilder=>
                {
    
    
                    appBuilder.Run(async context =>
                    {
    
    
                        context.Response.StatusCode = 500;
                        await context.Response.WriteAsync(
                        "发生了未处理的异常错误,请联系管理员处理");
                    //这个在实际的生产环境中通常要记录日志
                }); } );
            }

运行效果如下:

在这里插入图片描述


15.Head请求

httpHead 和 httpget 基本上是一样的,只是不返回 body ,使用方法可以直接在 [httpget] 上边直接加一个 [httphead]

1.幂等性

幂等通俗来说是指不管进行多少次重复操作,都是实现相同的结果。

2.HTTP请求的区别

 GET,PUT,DELETE都是幂等操作,而POST不是,以下进行分析:
首先GET请求很好理解,对资源做查询多次,此实现的结果都是一样的。

PUT请求的幂等性可以这样理解,将A修改为B,它第一次请求值变为了B,再进行多次此操作,最终的结果还是B,与一次执行的结果是一样的,所以PUT是幂等操作。

同理可以理解DELETE操作,第一次将资源删除后,后面多次进行此删除请求,最终结果是一样的,将资源删除掉了。

POST不是幂等操作,因为一次请求添加一份新资源,二次请求则添加了两份新资源,多次请求会产生不同的结果,因此POST不是幂等操作。

https://blog.csdn.net/lixiaoer757/article/details/80090848

3. Post - Put - Patch 的区别

  • post:提交
  • Put:修改(未指定的修改会变成默认值,所以一般不用)
  • Patch:局部修改

Post 处理的路径 {api/students} ,
Put 的处理路径是 {api/students/id}
Patch 的处理路径是 {api/students/id}



16.过滤和搜索

1.如何给 Api 传递数据:

Restful 更改后的传递方式(Bingding source Attribute),有四种

  • FromBody 用来推断复杂类型的参数
  • FromForm 用来推断 IFormFile 和 IFormFileCOllection 类型的 Action 参数
  • FromRoute 用来推断 Action 中的参数和路由模板中的参数
  • FromQuery 用来推断其他类型的 Action 参数

1,fromBody:在cation方法传入参数后添加[frombody]属性,参数将以一个整体的josn对象的形式传递。
2,fromform:在cation方法传入参数后添加[frombody]属性,参数将以表单的形式提交。

下边是从路由模板中取得参数的例子

public async Task<ActionResult<CompanyDto>> 
GetCompany([FromRoute]Guid companyId)

从Query中取得的例子

public async Task<ActionResult<CompanyDto>> 
GetCompany([FromQuery]Guid companyId)

2.过滤和搜索的区别
过滤:首先是一个完整的集合,然后根据条件,把不匹配的数据移除。
搜索:首先是一个空的集合,然后根据条件,把匹配的的数据项往里添加。


注意过滤只能对DTO中的字段进行过滤。

3.以 EmployeesController 为例,建立过滤搜索
代码如下:

 public async Task<ActionResult<List<EmployeeDto>>> GetEmoloyeesForCompany
 (Guid companyId,[FromQuery(Name ="gender")]string genderDisplay)
        {
    
    
            if (!await companRepository.CompanyExistsAsync(companyId))
            {
    
    
                return NotFound();
            }
            var employees = await companRepository.GetEmployeesAsync(companyId,genderDisplay);
            var employeeDto = mapper.Map<List<EmployeeDto>>(employees);
            //把employee映射成EmployeeDto这种类型
            return Ok(employeeDto);
        }

4.更改 DBcontext 中相关的代码:即 CompanyRepository 的代码:
注意下边属性的用法:

  • genderDisplay.Trim( )
  • ar gender = Enum.Parse(genderStr)
  • string.IsNullOrWhiteSpace(genderDisplay)
   public async Task<List<Employee>> GetEmployeesAsync
   (Guid companyId,string genderDisplay)
        {
    
    
            if (companyId==Guid.Empty)
            {
    
    
                throw new ArgumentNullException(nameof(companyId));
            }

            //IsNullOrWhiteSpace 比 IsNullOrEmpty 大

            if (string.IsNullOrWhiteSpace(genderDisplay))//如果是空的
            {
    
    
                return await _context.Employees.
                                Where(x => x.CompanyId == companyId).
                                OrderBy(x => x.EmployeeNo).
                                ToListAsync(); //只要使用异步就要用await
            }

            var genderStr = genderDisplay.Trim();//这一行是就修剪掉空白字符串
            var gender = Enum.Parse<Gender>(genderStr);//把genderstr解析成Gender格式
            return await _context.Employees.
                                Where(x => x.CompanyId == companyId && x.Gender==gender).
                                OrderBy(x => x.EmployeeNo).
                                ToListAsync(); //只要使用异步就要用await
        }

.trim() 的含义
在这里插入图片描述
5.用postman测试
在路由中直接加 ? gender=男

在这里插入图片描述

6.搜索: 搜索其实就是检索出来带关键字的结果。

controller 里面的东西不再演示,下边是更改后的 EmployeeRespository()里面的东西。

注意对于 item 的用法,每次 item.where 都相当于做了一次筛选
注意item=item的用法,名字相同的时候,不用写类型:
搜索是对Entity进行的。

   {
    
    
            if (companyId==Guid.Empty)
            {
    
    
                throw new ArgumentNullException(nameof(companyId));
            }

            var items = _context.Employees.Where(x=>x.CompanyId==companyId);
            
            //IsNullOrWhiteSpace 比 IsNullOrEmpty 大
            if (!string.IsNullOrWhiteSpace(genderDisplay))//如果非空
            {
    
    
               genderDisplay = genderDisplay.Trim();//名字相同的话,不要写类型;
                var gender = Enum.Parse<Gender>(genderDisplay);
                items = items.Where(x => x.Gender == gender);
            }
            if (!string.IsNullOrWhiteSpace(q))
            {
    
    
                q = q.Trim();
                items = items.Where( x=>x.EmployeeNo.Contains(q)
                ||x.FirstName.Contains(q)||x.LastName.Contains(q));
            }

            return await 
                items.OrderBy(x => x.EmployeeNo).ToListAsync();
        }

17. 查询参数

当进行查询和搜索的时候,如果一次传入多组参数,每次都需要更改Action参数的方法,会很麻烦,所以直接传入一个类,这样只要更改一个类就可以,不需要对 Controller 中的 Action 参数进行更改。

具体实现如下:以Company(Guid Id)为例

1.预览:具体添加目录如下:
在这里插入图片描述

2.新建 DtoParameters 的类,
如果要有新的查询,就在这里面添加。

 public class CompanyDtoParamaters
    {
    
    
        public string CompanyName {
    
     get; set; }
        public string SearchTerm {
    
     get; set; }
    }

3.在 Controller 的 Action 里面更改传入参数:(注意要声明来自于 【FromQuery】)

 public async Task<ActionResult<List<CompanyDto>>> 
 GetCompanies([FromQuery]CompanyDtoParamaters companyDtoParamaters)
        {
    
    

            var companies = await companRepository.GetCompaniesAsync(companyDtoParamaters);

            //  return new JsonResult(companies);

            var companyDto1 = new List<CompanyDto>();//这个地方要用 new
            foreach (var company in companies)
            {
    
    
                companyDto1.Add(
                    new CompanyDto
                    {
    
    
                        Id = company.Id,
                        CompanyName = company.Name
                    });
            }
            var companyDto = mapper.Map< List < CompanyDto >> (companies);
            return Ok(companyDto);
        }

4.更改 Repository:

 public async Task<List<Company>> GetCompaniesAsync
 (CompanyDtoParamaters companyDtoParamaters)
        {
    
    
            if (companyDtoParamaters==null)
            {
    
    
                throw new ArgumentNullException(nameof(companyDtoParamaters));
            }
            if (string.IsNullOrWhiteSpace(companyDtoParamaters.CompanyName)
                &&string.IsNullOrWhiteSpace(companyDtoParamaters.SearchTerm))
            {
    
    
                 return await _context.Companies.ToListAsync();//遇到TolistAsnyc才会真正的开始查询数据库
            }
            var item = _context.Companies as IQueryable<Company>;//对数据进行过滤和搜索的时候要用
            //IQueryable 以后再查
            if (!string.IsNullOrEmpty(companyDtoParamaters.CompanyName))
            {
    
    
                companyDtoParamaters.CompanyName = companyDtoParamaters.CompanyName.Trim();
                item = item.Where(x=>x.Name==companyDtoParamaters.CompanyName);
            }
            if (!string.IsNullOrEmpty(companyDtoParamaters.SearchTerm))
            {
    
    
                companyDtoParamaters.SearchTerm = companyDtoParamaters.SearchTerm.Trim();
                item = item.Where(x => x.Name.Contains(companyDtoParamaters.SearchTerm)
                &&x.Introduction.Contains(companyDtoParamaters.SearchTerm));
            }
            return await item.ToListAsync(); //Async    前边要加 await 

        }



18. 安全性和幂等性

  • 安全性:方法执行后不会改变资源的表述
  • 幂等性:无论方法执行多少次后都会得到同样的结果。

在这里插入图片描述


各种http请求的区别https://blog.csdn.net/weixin_43740223/article/details/85767838


GET,PUT,DELETE都是幂等操作,而POST不是,以下进行分析:
首先GET请求很好理解,对资源做查询多次,此实现的结果都是一样的。
PUT请求的幂等性可以这样理解,将A修改为B,它第一次请求值变为了B,再进行多次此操作,最终的结果还是B,与一次执行的结果是一样的,所以PUT是幂等操作。
同理可以理解DELETE操作,第一次将资源删除后,后面多次进行此删除请求,最终结果是一样的,将资源删除掉了。
POST不是幂等操作,因为一次请求添加一份新资源,二次请求则添加了两份新资源,多次请求会产生不同的结果,因此POST不是幂等操作。

19.Post .添加 Company

1.先设置 CompanyAddDto :因为 addDto 和entity 和 dto 都不一样,比如在出入的时候,CompanyAddDto都不需要传入参数:设置AddDTo如下:

 public class CompanyAddDto
    {
    
    
        public string Name {
    
     get; set; }
        public string Introduction {
    
     get; set; }
    }

2.建立映射关系:名字都一样,不需要指定

  CreateMap<CompanyAddDto, Company>();

3.在 CompanyController 中设置 Post
注意一下几点

  • 只要有 Async 的方法和属性,前边就一定要有 await
  • 注意最后,添加成功返回 201 状态码,并返回添加成功的Dto,代码怎么写?要先在 httpget 的路由方法中添加名字。[HttpGet("{companyId}",Name = "GetCompany")]//给路由起个名字,而后在CreatAtRoute()方法中添加相关参数。
  • 注意来来回回的映射关系
   [HttpPost]
        public async Task<ActionResult<CompanyDto>> 
        CreatCompany([FromBody]CompanyAddDto companyAddDto)
        {
    
    
            var entityCompany = mapper.Map<Company>(companyAddDto);
            companRepository.AddCompany(entityCompany);
            await companRepository.SavaAsync(); 
            //只要后边有 Async 前边就必须有 awati
            var returnDto = mapper.Map<CompanyDto>(entityCompany);
            //如果返回201的状态码,如何设置?
            //return CreatedResult();

            //如何让其返回路由地址
          //路由的参数,路由的值,还有 values
            return  CreatedAtRoute(nameof(GetCompany), new {
    
     companyId = returnDto.Id },returnDto);
        }  

4.最后测试结果如下:
在这里插入图片描述

20.添加子资源

添加子资源,就是在已有的公司里面,添加新的员工

方法和 Company 执行post 请求差不多:

1.创建 employeeAddDto 代码如下:

 public class EmployeeAddDto
    {
    
    
        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; }
    }

2.添加映射关系

CreateMap<EmployeeAddDto,Employee>();

3.建立 CreatEmployeeForCompany

  [HttpPost]
        public async Task<ActionResult<EmployeeDto>> 
            CreatEmployeeForCompany(Guid companyId
            ,EmployeeAddDto employeeAddDto)
        {
    
    
            if (!await companRepository.CompanyExistsAsync(companyId))
            {
    
    
                return NotFound();
            }

            var entity = mapper.Map<Employee>(employeeAddDto);
             companRepository.AddEmployee(companyId,entity);
            await companRepository.SavaAsync();
            var resutltDto = mapper.Map<EmployeeDto>(entity);
            return CreatedAtRoute(
                "GetEmoloyeeForCompany",
                new {
    
     
                      companyId=entity.CompanyId,
                    employeeId=entity.Id
                },resutltDto);
        }

4.测试结果如下:
在这里插入图片描述

返回的路由地址

在这里插入图片描述

21.同时创建父子资源

同时创建父子资源的意思:是在创建 Company 的时候,同时带着 employ

1.里面的东西不用改,因为源代码中有遍历。代码如下

   public void AddCompany(Company company)
        {
    
    
            if (company == null)//检查的应该是他传进来的参数
            {
    
    
                throw new ArgumentNullException(nameof(company));
            }
            company.Id = Guid.NewGuid();//Id 是当时生成的;
            if (company.Employees != null)
            {
    
    
                foreach (var employee in company.Employees)
                {
    
    
                    employee.Id = Guid.NewGuid();
                }
            }
            _context.Companies.Add(company);
        }

2.使用 PostMan 进行测试
在这里插入图片描述

22.添加集合资源

:以集合的形式添加 Company (添加多个Company)
1.创建新的 CompanyCollectionController 类:代码如下:


    [Route("api/companyCollections")]
    [ApiController]
    public class CompanyCollectionController : ControllerBase
    {
    
    
        private readonly IMapper mapper;
        private readonly ICompanRepository companRepository;

        //依赖注入的是接口
        public CompanyCollectionController
        (IMapper mapper,ICompanRepository companRepository)
        {
    
    
            this.mapper = mapper;
            this.companRepository = companRepository;
        }
        [HttpPost]
        public async Task<ActionResult<List<Company>>>
            CreatCompanyList(List<CompanyAddDto> companyCollection)
        {
    
    
            var entityCompanyList = mapper.Map<List<Company>>(companyCollection);
            foreach (var company in entityCompanyList)
            {
    
    
                companRepository.AddCompany(company);
            }
           await companRepository.SavaAsync();

            return Ok();
        } }

2.使用postman进行测试:
在这里插入图片描述

23.自定义 Model 绑定器

:使用 , 分开的 id 能够找到分别对应的 Company
代码没看懂,直接上图片吧
在这里插入图片描述
视频连接自定义Model绑定器

24.Http Options 请求

具体代码如下:

 [HttpOptions]
        public IActionResult GetCompanyOptions()
        {
    
    
            Response.Headers.Add("Alllow","get ,post,options");
            return Ok();
        }

用 postman 测试结果如下:
在这里插入图片描述

25.输入验证

Data Annotations(注释)
输入验证,本来在 DbContext 里面有输入验证:

  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);

但是如果使用这个验证:就是在返回的状态码中返回 500 状态码。

所以要在 AddDto 里面进行输入验证:代码如下:
注意 {0} {1} : {0}是表示名字,后边的 1 和 2 表示的最大值和最小值(往后拍)

 public class CompanyAddDto
    {
    
    
        [Display(Name ="公司的名字")]
        [Required(ErrorMessage ="{0}是必须要填的")]
        [MaxLength(100)]
        public string Name {
    
     get; set; }
        
        [Display(Name="公司的简介")]
        [Required(ErrorMessage ="{0}这个是必填的")]
        [StringLength
            (500,MinimumLength =10,ErrorMessage ="{0}这个参数的最大长度是{1},最小范围是{2}")]
        public string Introduction {
    
     get; set; }
        public List<EmployeeAddDto>
         Employees {
    
     get; set; } = new List<EmployeeAddDto>();
    }

验证结果如下:

在这里插入图片描述
Name 和 Introduction 都不一样的时候
在这里插入图片描述


使用 IValidatableObject 来实现输入验证:

IvalidatableObject 功能更强大,通常是要走完属性验证以后,才会进行 IvalidatableObjectio验证:

具体实现代码如下:

比如,在添加员工的时候,姓和名不能一样
注意要继承 IvalidatableObject 这个类

 public class EmployeeAddDto:IValidatableObject
    {
    
    
        [Display(Name ="员工号")]
        [Required(ErrorMessage ="{0}是必须要填的")]
        [StringLength(10,MinimumLength =10,ErrorMessage ="{0}的长度要求是{1}")]
        public string EmployeeNo {
    
     get; set; }

        [Display(Name ="姓")]
        public string FirstName {
    
     get; set; }


        [Display(Name ="名")]
        public string LastName {
    
     get; set; }
        [Display(Name ="性别")]
        public Gender Gender {
    
     get; set; }    //枚举类型要先写

        [Display(Name ="出生日期")]
        public DateTime DateOfBirth {
    
     get; set; }

        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
    
    
            if (FirstName==LastName)
            {
    
    
                yield return
                    new ValidationResult("姓和名字不能一样"
                    ,new[] {
    
     nameof(FirstName),nameof(LastName)});
            }
        }
    }

用 postman 测试结果如下:

在这里插入图片描述

要想作用于类,可以使用自定义的验证特性

没仔细看,具体看视频


26. PUT 整体更新

put 是对数据进行整体的更新,如果没有设置的值,就会被设为默认值。所以这一般用的比较少。

注意:ld 的路由一定要加 {} !!

1.首先建立 UPdateDto,因为要 updateDto 和 其他的 Dto 可能会不一样,这是一样的;

  public class EmployeeAddDto:IValidatableObject
    {
    
    
        [Display(Name ="员工号")]
        [Required(ErrorMessage ="{0}是必须要填的")]
        [StringLength(10,MinimumLength =10,ErrorMessage ="{0}的长度要求是{1}")]
        public string EmployeeNo {
    
     get; set; }

        [Display(Name ="姓")]
        public string FirstName {
    
     get; set; }


        [Display(Name ="名")]
        public string LastName {
    
     get; set; }
        [Display(Name ="性别")]
        public Gender Gender {
    
     get; set; }    //枚举类型要先写

        [Display(Name ="出生日期")]
        public DateTime DateOfBirth {
    
     get; set; }

        public IEnumerable<ValidationResult>
         Validate(ValidationContext validationContext)
        {
    
    
            if (FirstName==LastName)
            {
    
    
                yield return
                    new ValidationResult
                    ("姓和名字不能一样",new[] {
    
     nameof(FirstName),nameof(LastName)});
            }} }

2.添加 PUT 的 Controller

  • 注意路由模板
  • 注意 通过 noContent 获得 204状态码

注意:路由模板

[HttpPut("{employeeId}")]
        public async Task<IActionResult> UpdateEmployeeForCompany
            ([FromRoute]Guid companyId,[FromRoute]Guid employeeId,
            [FromBody]EmployeeUpdateDto employeeUpdateDto)
        {
    
    
            if (!await  companRepository.CompanyExistsAsync(companyId))
            {
    
    
                return NotFound();
            }
            var employeeEntity = await 
            companRepository.GetEmployeeAsync(companyId,employeeId);
            if (employeeEntity==null)
            {
    
    
                return NotFound();
            }

            //把 entity 转化为 UpdateDto
            //把 employee 更新到  updateDto
            //再把 updateDto 映射回 entity
            
            //在 AutoMapper 里面一句就实现了
            mapper.Map(employeeUpdateDto,employeeEntity);
            companRepository.UpdateEmployee(employeeEntity);
            await companRepository.SavaAsync();
            return NoContent();
        }

3.用 Postman 检测效果:

在这里插入图片描述

27. Http Patch 局部更新

1.局更新的操作方法有以下几种
在这里插入图片描述

2.新建 EmployeeController 类

  • 注意 map.map(A) 和map.map(A,B)
  • 注意传入参数时候用的 JsonPatchDocument 类,引用该类型需要引入第三方的一个包:
    在这里插入图片描述
    代码如下:
  [HttpPatch("{employeeId}")]
        public async Task<IActionResult> PatchUpdataEmployeeForCompany
            ( Guid companyId
            ,Guid employeeId
            ,JsonPatchDocument<EmployeeUpdateDto> patchDocument )
        {
    
    

            if (!await companRepository.CompanyExistsAsync(companyId))
            {
    
    
                return NotFound();
            }
            var employeeEntity = await companRepository.GetEmployeeAsync(companyId, employeeId);
            if (employeeEntity == null)
            {
    
    
                return NotFound();
            }
            //只要是涉及到映射改变的,都是先映射回来,再映射回去
            var dtoTopatch = mapper.Map<EmployeeUpdateDto>(employeeEntity);
            //这个是转化类型
            //验证错误;
            patchDocument.ApplyTo(dtoTopatch);
            mapper.Map(dtoTopatch,employeeEntity);
            //这个是从一个实例映射到另一个实例
            companRepository.UpdateEmployee(employeeEntity);
            await companRepository.SavaAsync();
            return NoContent();
        }

3.但是在实际引用的时候,因为 .net core,Json的转化不太健全,所以可以使用 Newtonsoft.Json 转化一下:
引用包:

在这里插入图片描述

代码如下:

  public void ConfigureServices(IServiceCollection services)
        {
    
    
            services.AddControllers(options =>
            options.ReturnHttpNotAcceptable = true 
            //他默认是关闭的
            ).AddNewtonsoftJson(setup=>
            {
    
    
                setup.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            })
            .AddXmlDataContractSerializerFormatters();
            //添加对XML的支持


4.使用postman进行测试:

  1. 注意 Content-Type 要使用 application/json-patch+json 这种类型
    在这里插入图片描述
    其他操作类似:
    在这里插入图片描述

28.HttpDelete 删除 employee 资源

1.这个比较简单,代码如下:

 [HttpDelete("{employeeId}")]
        public async Task<IActionResult>
         DeleteEmployeeForCompany(Guid companyId, Guid employeeId)
        {
    
    
            if (!await companRepository.CompanyExistsAsync(companyId))
            {
    
    
                return NotFound();
            }
            var employeeEntity = 
            await companRepository.GetEmployeeAsync(companyId, employeeId);
            
            if (employeeEntity == null)
            {
    
    
                return NotFound();
            }
            companRepository.DeleteEmployee(employeeEntity);
            await companRepository.SavaAsync();
            return NoContent();
        }

2.用 Postman 的测试结果如下:
在这里插入图片描述

29.HttpDelete 删除 Company

在删除 Company 的同时,顺带着把 Company 下的 Employee 也删除了。

1.需要在 DBcontext 里边把级联属性打开:

 .HasForeignKey(x => x.CompanyId)//指定外键
 // .OnDelete(DeleteBehavior.Restrict);
 //当删除的时候,如果company下边有员工的话,就无法删除;
               .OnDelete(DeleteBehavior.Cascade);

2.在 CompanyController 里面,添加 httpDelete 方法:
注意:要添加
await companRepository.GetEmployeesAsync(companyId, null, null);
进行跟踪

 [HttpDelete("{companyId}")]
        public async Task<IActionResult> 
        DeleteEmployeeForCompany(Guid companyId)
        {
    
    
            var companyEntity = 
            await companRepository.GetCompanyAsync(companyId);
            
            if (companyId==null)
            {
    
    
                return NotFound();
            }
            await companRepository.GetEmployeesAsync(companyId, null, null);
            companRepository.DeleteCompany(companyEntity);
            await companRepository.SavaAsync();
            return NoContent();
        }

3.用 Postman 进行测试:

在这里插入图片描述

30.翻页功能

翻页指在数据库层面的翻页,在查询完成以后,再执行翻页功能没有任何意义

下边以查询 Company 下边的员工为例。

1.因为有查询表达参数这个类,所以直接在类里面添加:
特别注意属性的用法:

  1. 属性没有储存功能
  2. 在类的内部相当于方法
 public class CompanyDtoParamaters
    {
    
    
        private const int maxlength = 5;
        public string CompanyName {
    
     get; set; }
        public string SearchTerm {
    
     get; set; }
        public int pageNumber {
    
     get; set; } = 1;
        private int _pagesize=5;
        public int  PageSize 
        {
    
     get=>_pagesize;
          set=>_pagesize= (value> maxlength )? maxlength:value; 
        }
    }

2.在 Repository 类里面添加一行代码:

  • 注意代码一定要放在过滤和搜素以后
  • 这个代码一定要放在正式查询之前,比如 .ToListAsync();
 public async Task<List<Company>> GetCompaniesAsync
 (CompanyDtoParamaters companyDtoParamaters)
        {
    
    
            if (companyDtoParamaters == null)
            {
    
    
                throw new ArgumentNullException(nameof(companyDtoParamaters));
            }
            //if (string.IsNullOrWhiteSpace(companyDtoParamaters.CompanyName)
            //    && string.IsNullOrWhiteSpace(companyDtoParamaters.SearchTerm))
            //{
    
    
            //    return await _context.Companies.ToListAsync();//遇到TolistAsnyc才会真正的开始查询数据库
            //}
            var item = _context.Companies as IQueryable<Company>;//对数据进行过滤和搜索的时候要用
            //IQueryable 以后再查
            if (!string.IsNullOrEmpty(companyDtoParamaters.CompanyName))
            {
    
    
                companyDtoParamaters.CompanyName =
                 companyDtoParamaters.CompanyName.Trim();
                item = item
                .Where(x => x.Name == companyDtoParamaters.CompanyName);
            }
            if (!string.IsNullOrEmpty(companyDtoParamaters.SearchTerm))
            {
    
    
                companyDtoParamaters.SearchTerm
                 = companyDtoParamaters.SearchTerm.Trim();
                item = item.Where(x => x.Name.Contains(companyDtoParamaters.SearchTerm)
              
                && x.Introduction.Contains(companyDtoParamaters.SearchTerm));
            }

            //翻页在过滤以后再执行;
            item = item
            .Skip
                (companyDtoParamaters.PageSize * (companyDtoParamaters.pageNumber - 1))
                .Take(companyDtoParamaters.PageSize);

            return await item.ToListAsync(); //Async    前边要加 await 
        }

3.使用 Postman 进行测试的结果:
在这里插入图片描述

可以返回一些更加详细的信息,比如前一页和后一页的链接,当前页是第几页等。

这个比较复杂。我没看,详细看视频

31.排序

1.和 employeeDToParamter 一样,建立 EmployeeDtoParamters 类,建立添加OrederBy 的字段

内容如下:

  public class EmployeeDtoparamters
    {
    
    
        private const int MaxPageSize = 20;
        public string Gerder {
    
     get; set; }
        public string Q {
    
     get; set; }
        public int PageNumber {
    
     get; set; } = 1;
        public int _PageSize = 5;
        public int pageSize 
        {
    
     
            get=>_PageSize;
            set=>_PageSize=(value>MaxPageSize)?MaxPageSize:value;
        }
        public string OrderBy {
    
     get; set; } = "Name";
    }

2.在 DBContext 里面添加关于查询的字符串,注意 OrederBy 和 ThenBy

 //根据公司ID寻找所有的员工信息;
        public async Task<List<Employee>> GetEmployeesAsync(Guid companyId, EmployeeDtoparamters dtoparamters)
        {
    
    
            if (companyId == Guid.Empty)
            {
    
    
                throw new ArgumentNullException(nameof(companyId));
            }

            var items = _context.Employees
            .Where(x => x.CompanyId == companyId);

            //IsNullOrWhiteSpace 比 IsNullOrEmpty 大
            if (!string.IsNullOrWhiteSpace(dtoparamters.Gerder))
            //如果非空
            {
    
    
                dtoparamters.Gerder = dtoparamters.Gerder.Trim();
                //名字相同的话,不要写类型;
                var gender = Enum.Parse<Gender>(dtoparamters.Gerder);
                items = items.Where(x => x.Gender == gender);
            }
            if (!string.IsNullOrWhiteSpace(dtoparamters.Q))
            {
    
    
                dtoparamters.Q = dtoparamters.Q.Trim();
                items = items
                .Where(x => x.EmployeeNo.Contains(dtoparamters.Q)
               || x.FirstName.Contains(dtoparamters.Q) 
               || x.LastName.Contains(dtoparamters.Q));
            }

            if (dtoparamters.OrderBy!=null)
            {
    
    
                if (dtoparamters.OrderBy.ToLowerInvariant()=="name")
                {
    
    
                    items = items
                    .OrderBy(x=>x.LastName).ThenBy( x=>x.FirstName);
                }
            }

            return await
                items.ToListAsync();
        }

3.用 postman 进行查询的结果:

在这里插入图片描述
这种简单粗暴的处理方式,不能使用复用,要实现复用的话,相对麻烦一点。我没看,相关部分请看视频。

32.数据塑形

  • 数据塑形:允许 API 消费者选择要返回的资源的字段。
  • /api/companies? fields = id ,name。
  • 针对资源的字段,而不是其他更低层次的对象的字段。

33.HateOAS

翻译过来的意思:超媒体做为应用状态的引擎

HATEOAS 是 REST 架构风格中最复杂的约束,也是构成成熟REST架构风格的核心;他除了返回一些结果以外,还返回一个 links 的数组

有点复杂,我也没看,具体看视频;
在这里插入图片描述

34. 添加 JWT 授权认证功能

题外话: 如果在建立授权类的时候,对里面的某个方法不确定的话,可以先写个get string的函数试一试;

1.要新建一个项目,添加 AuthorizationServer 服务器

新建目录如下
在这里插入图片描述
在这里插入图片描述

2.JWT接口的内容如下

   public  interface IJWTService
    {
    
    
        string GetToken(string UserName);
    }

3…JWTServer 类的内容如下

  public class JWTServer : IJWTService
    {
    
    
        private readonly IConfiguration configuration;

        public JWTServer(IConfiguration configuration)
        {
    
    
            this.configuration = configuration;
        }
        //查一下claim类
        public string GetToken(string UserName)
        {
    
    
            Claim[] claims = new[] 
            //这里面的东西是要添加到payload里面的东西
            {
    
    
            new Claim(ClaimTypes.Name,UserName),
            new Claim("NcikName","zhaodepeng"),
            new Claim("Role","Admin"),
            };

            //用私钥加密
            SymmetricSecurityKey key = 
            new SymmetricSecurityKey
                (Encoding.UTF8.GetBytes(configuration["SecurityKey"]));
                //加密的字符串
                
            SigningCredentials creds =
                new SigningCredentials(key, SecurityAlgorithms.HmacSha256);//指定加密方式;

            //JWT标准规定的一些字段
            var token = new JwtSecurityToken(
                issuer: configuration["issuer"],
                audience: configuration["audience"],
                claims: claims,   //上边的其他声明
                expires: DateTime.Now.AddMinutes(5),//五分钟有效期;
                signingCredentials: creds//加密方法.
                );
            string returnToken = 
            new JwtSecurityTokenHandler().WriteToken(token);//把JWT写到Json里面
            return returnToken;
        }
    }

4.AuthenticationController类的内容如下:


    [Route("authen")]
    [ApiController]
    public class AuthenticationController : ControllerBase
    {
    
    

        #region 注入
        private readonly ILoggerFactory factory;
        private readonly ILogger<AuthenticationController> logger;
        private readonly IConfiguration configuration;
        private readonly IJWTService JWtser;

        public AuthenticationController(
            ILoggerFactory factory,
            ILogger<AuthenticationController> logger,
            IConfiguration configuration, IJWTService service)
        {
    
    
            this.factory = factory;
            this.logger = logger;
            this.configuration = configuration;
            this.JWtser = service;
        }
        #endregion

        [HttpGet("test")]
        public string Gettest()
        {
    
    
            return "能成功的进入到方法里面";
        }

        public string Login(string name, string password)
        {
    
    
            //正常的话是要查询数据库
            if ("zhao".Equals(name) && "123456".Equals(password))
            {
    
    
                string token = JWtser.GetToken(name);
                return JsonConvert.SerializeObject(new
                {
    
    
                    result = true,
                    token
                }); 
            }
            else
            {
    
    
                return JsonConvert.SerializeObject(new
                {
    
    
                    result = false,
                    token = ""
                });
            } } }

5.配置文件的相关内容如下:

{
    
    
  "Logging": {
    
    
    "LogLevel": {
    
    
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "audience": "http://localhost:5000",
  "issuer": "http://localhost:5000",
  "SecurityKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDI2a2EJ7m872v0a
  fyoSDJT2o1+SitIeJSWtLJU8/Wz2m7gStexajkeD+Lka6DSTy8gt9UwfgVQo6uK
  jVLG5Ex7PiGOODVqAEghBuS7JzIYU5RvI543nNDAPfnJsas96mSA7L
  /mD7RTE2drj6hf3oZjJpMPZUQI/B1Qjb5H3K3PNwIDAQAB"
}

6. 在server API中进行配置认证信息
(就是什么样的token可以进来)

 #region Jwt授权认证
            //1.Nuget引入程序包:Microsoft.AspNetCore.Authentication.JwtBearer 
            //services.AddAuthentication();//禁用  
            var ValidAudience = this.Configuration["audience"];
            var ValidIssuer = this.Configuration["issuer"];
            var SecurityKey = this.Configuration["SecurityKey"];
            services.AddAuthentication
            (JwtBearerDefaults.AuthenticationScheme)  //默认授权机制名称;                                      
                     .AddJwtBearer(options =>
                     {
    
    
                         options.TokenValidationParameters = 
                         new TokenValidationParameters
                         {
    
    
                             ValidateIssuer = true,//是否验证Issuer
                             ValidateAudience = true,//是否验证Audience
                             ValidateLifetime = true,//是否验证失效时间
                             ValidateIssuerSigningKey = true,//是否验证SecurityKey
                             ValidAudience = ValidAudience,//Audience
                             ValidIssuer = ValidIssuer,//Issuer,
                             这两项和前面签发jwt的设置一致  表示谁签发的Token
                             IssuerSigningKey = 
                             new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecurityKey))
                             //拿到SecurityKey
                             //AudienceValidator = (m, n, z) =>
                             //{
    
    
                             //    return m != null && m.FirstOrDefault().Equals(this.Configuration["audience"]);
                             //},//自定义校验规则,可以新登录后将之前的无效 
                         };
                     });
            #endregion

7.添加两个中间件管道:


            app.UseAuthentication();//鉴权授权;注意位置

            app.UseAuthorization();

8.在要保护的 Controller 上边添加 [Authorize] 特性, 不授权保护的api上边添加 【AllowAnonymous】特性

9.使用 postman 进行测试的结果如下:

  • 先从认证服务器上获得token
  • 带着token去访问被保护的ApI
    在这里插入图片描述
    在这里插入图片描述

注意!!!!

token 是\ 里面的东西,不包括斜杠,别搞错了!!
在这里插入图片描述

35.配置 Swagger


1.引入 SwashBuckle.AspNetCore 包

在这里插入图片描述

我就是用这个包安装的。引入了以后,可以按 f12 ,查看具体的参数

2.把swagger 放到IOC容器里

 services.AddSwaggerGen(options => //配置swagger...swaggerGen是swagger生成器.
            {
    
    
                options.SwaggerDoc("V1" ,new OpenApiInfo()  //这个v1 是给机器看的。
                {
    
    
                    Title = "test",
                    //下边的信息是给人看的。
                    Version = "Version--0",
                    Description = "公司-员工-信息查询系统"
                });
            });

3在中间件管道里使用

      #region 使用Swagger中间件
            app.UseSwagger();
            app.UseSwaggerUI(s =>//swaggerUI 解释了Swagger JSon
            {
    
    
                s.SwaggerEndpoint("/swagger/V1/swagger.json", "test1");
                //其中的V1和前边(给机器看的)的相对应;
            });
            #endregion

参考文献

源码

猜你喜欢

转载自blog.csdn.net/zhaozhao236/article/details/106897034
今日推荐