使用ASP.NET Core 3.x 构建 RESTful API P1 准备工作

使用ASP.NET Core 3.x 构建 RESTful API P1 准备工作


博客园文章Id:12554353


Web API

简介:

  1. Web API通常是指"使用HTTP协议并通过网络调用的API",由于它使用了HTTP协议,所以需要通过URL信息来指定端点.
  2. API 是 Application Programming Intrface 的缩写,是软件的外部接口. 也就是说,针对某个软件,人们可以知道它的外部功能,但并不知道(也不需要知道)它内部运作的具体细节,为了从外部调用某些功能,需要指定软件的调用规范等信息,这样的规范就是API,所以WebAPI就是一个Web系统,通过访问URL可以与其进行信息交互.

什么是RESTful API

REST一词是在2000年首次出现的,它是由Roy Fielding博士在《架构风格以及基于网络的软件架构设计》这篇论文中提到的.他为REST风格的API制定了一套约束规范或者叫做架构风格.

所以准确的说,只有符合了Roy Fielding架构风格的Web API才能称作是 RESTful API.但是在实际开发中,有时候也有不完全符合Roy Fileding架构风格的情况出现.

MVC模式

  • MVC (Model-View-Controller) 是一种主要用来构建UI的架构模式.
  • 松耦合.
  • 关注点分离. (separation of concerns)
  • MVC 不是一个完整的应用程序架构.

MVC映射为API

  • Model,它负责处理程序处理的逻辑.
  • View,它是程序里负责展示数据的那部分.在构建API的时候,View就是数据或资源的展示.现在通常使用JSON格式.
  • Controller,它是负责View和Model之间的交互.

三者之间的关系
enter description here

创建 ASP .Net Core 项目步骤

创建新项目
创建新项目

选择ASP.NET Core Web 应用程序 项目模板
选择ASP.NET Core Web 应用程序 项目模板

配置项目位置等信息
配置项目位置等信息

创建一个空的 ASP.NET Core Web应用程序项目
创建一个空的 ASP.NET Core Web应用程序项目

现在可以开始我们创建 ASP.NET Core 应用之旅
现在可以开始我们创建 ASP.NET Core 应用之旅

Startup.cs 类的介绍

在 Asp .Net Core 中 Startup.cs 类主要是用来进行动态配置的工作.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace Routine.Api
{
    public class Startup
    {
        private readonly IConfiguration _configuration;

        /*
         * 通过IServiceCollection 我们可以获取或修改到一些配置文件中的信息,
         * 比如系统默认的appSettings.json文件.
         */
        public Startup(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        /*
         * Asp.Net Core 拥有一个服务的容器,ConfigureServices方法,
         * 就是向服务的容器中,注册一些服务,已经注册的服务就可以通过依赖注入的方式,
         * 在整个应用程序的其它地方进行使用.
         */

        public void ConfigureServices(IServiceCollection services)
        {
            /*
             * AddMvc() 方法不仅包含了构建WebApi方面的服务,它还有其它服务,比如构建视图,
             * 或者是TagHelper相关的服务等.
             */
            //services.AddMvc();

            /*
             * AddController() 方法支持构建WebApi服务,包括
             * Controller的支持,Model的绑定,以及格式化器相关的服务等.
             */
            services.AddControllers();  
        }

        /*
         * Configure 方法是在ConfigureServices方法调用之后被调用的,
         * Asp.Net Core 主要是通过Configure 方法来配置Http请求管道中需要使用到的
         * 中间件.
         */
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())    //判断当前运行的环境是否是开发环境
            {
                /*
                 * 下列中间件是当,程序中有异常没有被捕获,通过此中间件会记录,并显示该异常信息
                 */
                app.UseDeveloperExceptionPage();  
            }

            /*
             * 配置路由中间件
             */
            app.UseRouting();

            /*
             * 为整个WebApi 配置应用授权的能力,当我们需要考虑Api安全性的问题时,我们就需要,
             * 使用并配置UseAuthentication()中间件,通常授权的配置,是在ConfigureServices
             * 方法中完成的.
             */
            app.UseAuthentication();

            /*
             * 端点中间件
             */
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGet("/", async context =>
                {
                    await context.Response.WriteAsync("Hello World!");
                });
            });

            /*
             * 在Asp.Net Core Http请求管道中中间件的配置的顺序实际上是很重要的.
             */
        }
    }
}
ASP .NET Core 请求管道

请求管道在请求中的作用
请求管道在请求中的作用

ASP .Net Core 启动项配置

Asp .Net Core 启动项的配置有两种方式.

  1. 修改Properties中的launchSettings.json文件, 如:

launchSettings.json
launchSettings.json

  1. 或者我们可以通过vs的配置页面来修改launchSettings.json文件,如:

通过右击项目文件→属性→调试
通过右击项目文件→属性→调试

此处我们建议使用 .Net Core 自带的控制台程序来运行项目,而不是使用IIS来运行控制台项目来进行调试.

运行 .Net Core 项目时的效果如下:
启动运行

下面我们将使用 Sqlite 以及 Entity Farmework Core 来构建一个小型的WebApi接口项目,针对Entity Farmework Core 如何在.Net Core 3.x中运行,我们可以查阅官方文档. Entity Framework Core

开始项目
  1. 我们首先需要在项目中通过Nuget来添加Entity Framework Core,以及Sqlite的支持.

Micosoft.EntityFrameworkCore.tools 主要是用来做迁移工作的.
Micosoft.EntityFrameworkCore.sqlite 主要是用来使用EF Core 操作 Sqlite数据库的.

Micosoft.EntityFrameworkCore.tools
Micosoft.EntityFrameworkCore.tools

Micosoft.EntityFrameworkCore.sqlite

  1. 搭建Demo
    在项目下创建 Controllers 文件夹,用于创建 WebApi 所必须的Controller控制器类
    在项目下创建 Entitle 文件夹,用于创建 项目所需的实体类.
    在项目下创建 Data 文件夹,用于创建 EF上下文类.
    在项目下创建 Services文件夹,用于创建服务.

RoutineDbContext 类

using System;
using Microsoft.EntityFrameworkCore;
using Routine.Api.Entitle;

namespace Routine.Api.Data
{
    public class RoutineDbContext:DbContext
    {

        /*
         * Ef Core 需要 DbContextOptions 类型的配置才能进行工作,所以此处的配置,我们需要再Startup类中进行注入,
         * 才能使用此DbContext.
         */
        public RoutineDbContext(DbContextOptions<RoutineDbContext> options):base(options)
        {
            
        }

        /// <summary>
        /// 下面两个实例,将被映射到数据库的两个表中
        /// </summary>
        public DbSet<Company> Companies { get; set; }
        public DbSet<Employee> Employees { get; set; }


        /*
         * 有的时候我们需要对实体,进行一些限制,比如是必填的,最大长度等,
         * 这个使用我们就需要重写父类中的 OnModelCreating 方法
         */

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            //要求 Company类中 Name属性 是必填的.并且其长度不能超过100个字符
            modelBuilder.Entity<Company>()
                .Property(x => x.Name)
                .IsRequired()
                .HasMaxLength(100);

            //要求 Company类中 Introduction属性 的长度不能超过500个字符
            modelBuilder.Entity<Company>()
                .Property(x => x.Introduction)
                .HasMaxLength(500);

            //要求 Employee类中 EmployeeNo 属性 是必填的.并且其长度不能超过10个字符
            modelBuilder.Entity<Employee>()
                .Property(x => x.EmployeeNo)
                .IsRequired()
                .HasMaxLength(10);

            //要求 Employee类中 FirstName 属性 是必填的.并且其长度不能超过50个字符
            modelBuilder.Entity<Employee>()
                .Property(x => x.FirstName)
                .IsRequired()
                .HasMaxLength(50);

            //要求 Employee类中 LastName 属性 是必填的.并且其长度不能超过50个字符
            modelBuilder.Entity<Employee>()
                .Property(x => x.LastName)
                .IsRequired()
                .HasMaxLength(50);

            //手动指明两个表的对应关系(可以不手动指明)
            modelBuilder.Entity<Employee>()
                .HasOne(x => x.Company)       //指明 Employee 类的导航属性是Company
                .WithMany(x => x.Employees)   // Company 类中的导航属性是 Employees
                .HasForeignKey(x => x.CompanyId)                //外键是CompanyId
                .OnDelete(DeleteBehavior.Restrict);             //在做删除的时候Company下面存在Employee时,是不能删除的.


            /*
             * 我们还可以在 OnModelCreating 方法中,配置一些种子数据,在做数据迁移的时候,我们就可以使用到这些.
             * 种子数据.
             */

            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-afae-620d40542853"),
                    Name = "Alipapa",
                    Introduction = "Fubao Company"
                }
            );
        }
    }
}

实体模型 Company

using System;
using System.Collections.Generic;

namespace Routine.Api.Entitle
{
    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 Microsoft.AspNetCore.Identity;

namespace Routine.Api.Entitle
{
    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; }
    }
}
namespace Routine.Api.Entitle
{
    public enum Gender
    {
        男 = 1,
        女 = 2
    }
}

声明服务

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Routine.Api.Entitle;

namespace Routine.Api.Services
{
    public interface ICompanyRepository
    {
        Task<IEnumerable<Company>> GetCompaniesAsync();
        Task<IEnumerable<Company>> GetCompaniesAsync(IEnumerable<Guid> companyIds);
        Task<Company> GetCompanyAsync(Guid companyId);
        void AddCompany(Company company);
        void UpdateCompany(Company company);
        void DeleteCompany(Company company);
        Task<bool> CompanyExistsAsync(Guid companyId);


        Task<IEnumerable<Employee>> GetEmployeeAsync(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> SaveAsync();
    }
}

声明实现类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Routine.Api.Data;
using Routine.Api.Entitle;

namespace Routine.Api.Services
{
    public class CompanyImpl:ICompanyRepository
    {
        private readonly RoutineDbContext _content;

        public CompanyImpl(RoutineDbContext content)
        {
            _content = content ?? throw new ArgumentNullException(nameof(content));
        }

        public async Task<IEnumerable<Company>> GetCompaniesAsync()
        {
            return await this._content.Companies.ToListAsync();
        }

        public async Task<IEnumerable<Company>> GetCompaniesAsync(IEnumerable<Guid> companyIds)
        {
            if (companyIds == null)
            {
                throw new ArgumentNullException(nameof(companyIds));
            }

            return await this._content.Companies
                .Where(x => companyIds.Contains(x.Id))
                .OrderBy(x => x.Name)
                .ToListAsync();
        }

        public async Task<Company> GetCompanyAsync(Guid companyId)
        {
            if (companyId == null)
            {
                throw new ArgumentNullException(nameof(companyId));
            }

            return await this._content.Companies.FirstOrDefaultAsync(x => x.Id == companyId);
        }

        public void AddCompany(Company company)
        {
            this._content.Companies.AddAsync(company);
        }

        public void UpdateCompany(Company company)
        {
            //下面这句话不写也行,因为 Ef Core 对 实体是进行跟踪的,所以实体如果有更改的话,Ef core是知道的.
            //this._content.Entry(company).State = EntityState.Modified;
        }

        public void DeleteCompany(Company company)
        {
            this._content.Entry(company).State = EntityState.Deleted;
        }

        public async Task<bool> CompanyExistsAsync(Guid companyId)
        {
            if (companyId == null)
            {
                throw new ArgumentNullException(nameof(companyId));
            }

            return await this._content.Companies.AnyAsync(x => x.Id == companyId);
        }

        public async Task<IEnumerable<Employee>> GetEmployeeAsync(Guid companyId)
        {
            if (companyId == null)
            {
                throw new ArgumentNullException(nameof(companyId));
            }

            return await this._content.Employees
                .Where(x => x.CompanyId == companyId)
                .OrderBy(x => x.EmployeeNo)
                .ToListAsync();
        }

        public async Task<Employee> GetEmployeeAsync(Guid companyId, Guid employeeId)
        {
            if (companyId == null)
            {
                throw new ArgumentNullException(nameof(companyId));
            }
            if (employeeId == null)
            {
                throw new ArgumentNullException(nameof(employeeId));
            }

            return await this._content.Employees
                .Where(x => x.CompanyId == companyId && x.Id == employeeId)
                .FirstOrDefaultAsync();
        }

        public void AddEmployee(Guid companyId, Employee employee)
        {
            if (companyId == null)
            {
                throw new ArgumentNullException(nameof(companyId));
            }
            if (employee == null)
            {
                throw new ArgumentNullException(nameof(employee));
            }

            employee.CompanyId = companyId;
            this._content.Employees.Add(employee);
        }

        public void UpdateEmployee(Employee employee)
        {
            //下面这句话不写也行,因为 Ef Core 对 实体是进行跟踪的,所以实体如果有更改的话,Ef core是知道的.
            //this._content.Entry(employee).State = EntityState.Modified;
        }

        public void DeleteEmployee(Employee employee)
        {
            this._content.Employees.Remove(employee);
        }

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

接下来我们使用 .Net Core 自带的容器对 ICompanyRepository 服务进行注册,
以及Sqlite数据库的连接配置工作.
Startup.cs类中的ConfigureServices方法中添加如下代码:

//将组件 CompanyImpl 注册给 ICompanyRepository 服务
services.AddScoped<ICompanyRepository, CompanyImpl>();

//注册Sqlite数据的链接配置
services.AddDbContext<RoutineDbContext>(option =>
{
    option.UseSqlite("Date Source = routine.db ");
});

为了演示程序方便进行,我们需要修改Programe.cs类中的 Main方法中的代码,具体如下:

//我们需要再Build()方法之后Run()方法之前,添加一些数据库迁移的工作,具体Main函数方法代码如下:

public static void Main(string[] args)
 {
     var host = CreateHostBuilder(args).Build();

     /*
      * 代码执行到此处时,暂时还不可以进行传统的构造函数进行注入,所以我们需要通过下列方式,将容器
      * 中的服务取出来.
      */
     //服务的作用范围
     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, "Database Migration Error");
         }
     }

     host.Run();
 }

现在我们手动执行一下迁移,以确保数据库中,现在存在种子数据.

运行vs中的程序包管理器控制台(视图→其它窗口→程序包管理控制台) 执行如下代码:

Add-Migration InitialMigration

上面代码的意思表示添加一次迁移,并未此次的迁移添加一个名字(InititalMiaration)

执行结果:
执行结果

并且自动会在工程中创建 Migrations 文件夹,以及两个类文件.
自动生成

20200325154437_InitialMigration.cs 类文件表示此次迁移的变化.
RoutineDbContextModelSnapshot.cs 类文件表示当前数据库的快照.

以上两个迁移文件生成之后,我们就不在需要进行手动迁移了(因为我们在Main方法中已经写了迁移的代码了),当程序执行到Main方法中时,数据库连接对象会先删除数据库中就的数据信息,并将Migrations文件夹中的迁移数据信息,迁移至数据库中.

到此,我们运行程序,就会在工程下产生一个Routine.dbsqlite数据库文件.
sqlite数据库文件

我们可以通过数据库可视化工具查看该数据库文件中的数据,再此我们使用DataGrip来查看.
选择数据库:
选择数据库
指定数据库文件并测试连接:
指定数据库文件并测试连接
此时我们可以看到数据库的Companies表中已经存在三条种子数据:
种子数据

到此准备工作结束.

posted @ 2020-03-23 19:34  HelloZyjS  阅读(339)  评论(0编辑  收藏  举报