大家好!我是未来村村长,就是那个“请你跟我这样做,我就跟你这样做!”的村长!
“人生苦短,你用Python”,“Java内卷,我用C#”。
从Java到C#,不仅仅是语言使用的改变,更是我从理想到现实,从象牙塔到大熔炉的第一步。.NET是微软的一盘棋,而C#是我的棋子,只希望微软能下好这盘棋,而我能好好利用这个棋子。
Web API项目基于两个框架:Microsoft.AspNetCore.App和Microsoft.NETCore.App
在Program.cs中,包含两个方法CreateHostBuilder和Main。在CreateHostBuilder主要完成两件事,一是加载Startup配置类,二是返回HostBuilder对象。在Main方法中,通过调用CreateHostBuilder方法得到的HostBuilder来Build一个Host对象作为Web API运行的容器,通过Run来启动。
namespace EFLearn.API
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup();
});
}
}
Startup也有两个关键方法:ConfigureServices和Configure,ConfigureServices用于向容器添加服务,Configure用于配置HTTP请求管道,例如我们使用swagger时,需要通过services.AddSwaggerGen来进行服务的配置, 通过app.UseSwagger()进行服务的使用。
namespace EFLearn.API
{
public class Startup
{
//构造器
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
//此方法由运行时调用。使用此方法向容器添加服务。
public void ConfigureServices(IServiceCollection services){}
//运行时调用此方法。使用此方法配置HTTP请求管道。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env){ }
}
}
launchSettings是启动服务设置,对应了两种启动方式IIS Express和EFLearn.API,其中applicationUrl是相应启动方式对应的请求地址,launchUrl是默认启动api。
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:22649",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"EFLearn.API": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
appsettings是项目属性配置文件,用于属性的配置。例如我们在原文件基础上新增连接字符串"ConnectionStrings"。
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
"ConnectionStrings": {
"MySQL": "Server=localhost;Port=3306;Database=projectmanagement; User=root;Password=123456"
}
}
Web API分层架构可以分为:Models【实体层】,Controller【控制层】,Repositories【仓储层/数据层】,Services【服务层】。
Swashbuckle.AspNetCore
我们需要在Startup中的ConfigureServices方法中添加相应的服务,这里通过AddSwaggerGen来进行注册配置。SwaggerDoc用于设置文档显示。
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "EFLearn.API", Description="项目说明文档",Version = "v1" });
});
我们需要在Startup中的Configure方法中配置相应的服务,这里配置了UseSwagger和UseSwaggerUI。
if (env.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "EFLearn.API v1"));
}
如果我们希望将如下Controller的接口注释添加到SwaggerUI显示中,我们需要先生成相应的文档,然后将生成的xml文档读取到Swagger中。
namespace EFLearn.API.Controllers
{
///
/// 项目管理接口管理
///
[Route("api/[controller]")]
[ApiController]
public class ItemControController : ControllerBase
{
///
/// 获取项目名称
///
///
[HttpGet]
public string Get()
{
return "项目名称";
}
}
}
在属性-输出中,设置XML文档文件路径,此处使用…/相对路径。
AppContext.BaseDirectory为当前项目的相对路径,通过Path.Combine将相对路径名与文件名连接成完整的文件路径,随后通过IncludeXmlComments来加载注释文档,路径后的true参数是指该文档为controller文档。除了Controller以外,我们一般还需要为实体层建立swagger注释,方法类似。
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "EFLearn.API", Description="项目说明文档",Version = "v1" });
var xmlPath = Path.Combine(AppContext.BaseDirectory, "EFLearn.API.xml");
c.IncludeXmlComments(xmlPath,true);
});
当我们配置了注释文档后,编译器会认为我们处处需要注释,我们可以通过增加1591字段来取消相应的显示警告。
此处为基于.NET Core Web API 5进行MySQL的连接,需要在包含实体类和数据库上下文的包中导入以下两个Nuget包:
use TestDb;
create table if not exists `item`(
`id` int(4) not null auto_increment,
`name` varchar(10) not null default "无名项目",
primary key(`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Entity.Models
{
[Table("item")]
public class Item
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
}
using Microsoft.EntityFrameworkCore;
using Entity.Models;
namespace Entity.DBContexts
{
public class ItemDbContext : DbContext
{
public DbSet- Items { get; set; }
public ItemDbContext(DbContextOptions
options) : base(options){}
}
}
using System.Collections.Generic;
using System.Linq;
using Entity.Models;
using Entity.DBContexts;
using Repositories.IRepositories;
namespace Repositories
{
public class ItemRepositories : IItemRepositories
{
private readonly ItemDbContext _dbContext;
public ItemRepositories(ItemDbContext dbContext)
{
_dbContext = dbContext;
}
public IEnumerable- GetAll()
{
using (_dbContext)
{
return _dbContext.Items.ToList();
}
}
}
}
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"MySQL": "Server=localhost;Port=3306;Database=TestDb; User=root;Password=123456"
}
}
services.AddDbContext(options => options.UseMySql(Configuration.GetConnectionString("MySQL"), MySqlServerVersion.LatestSupportedServerVersion));
在类Startup中的ConfigureServices进行依赖的注册。
services.AddTransient();
使用EFCore需要以下步骤:建立数据库表、构建EF模型[对应table]、定义数据库上下文[对应Database]。首先我们需要选择相应数据库建立相应的库表,然后在此基础上建立对应的EF模型。即实体类。最后我们需要定义数据库上下文(DbContext)进行数据库的操作。
IoC控制反转容器来控制对象的依赖注入,依赖注入就是指在程序运行过程中,一个对象需要使用到另一个对象的属性或方法时,不需要该对象去new另一个对象的实例,而是交给IoC来完成外部注入。.NET Core自带的依赖注入,只支持构造注入:构造注入能使得依赖变得更清晰,我既然依赖你,那么我实例化的时候你就必须得出现。
但是.NET Core自带的注入方式,必须每次使用都要到ConfigureService方法中进行Add注册,若模块中涉及的类较多,则会导致代码冗杂,则推荐使用Autofac三方DI框架进行依赖注入实现。
先看一则案例:ItemService注入到ItemController。
ItemService:通过构造注入的方式注入了ItemRepositories
namespace Services
{
public class ItemService : IItemService
{
private readonly IItemRepositories _repositories;
public ItemService(IItemRepositories repositories)
{
_repositories = repositories;
}
public IEnumerable- GetAllItem()
{
return _repositories.GetAll();
}
}
}
ItemController:通过构造注入的方式注入了ItemService
namespace EFCoreToMysql.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ItemController : ControllerBase
{
private readonly IItemService _itemService;
public ItemController(IItemService itemService)
{
_itemService = itemService;
}
[HttpGet]
public IActionResult GetAll()
{
return Ok(_itemService.GetAllItem());
}
}
}
ConfigureServices(IServiceCollection services):我们通过接口声明进行注入时,需要提供相应接口,将接口和对应的类注册到容器中,若只提供了类,则只注册相应的类。
services.AddTransient();
services.AddTransient();
//只提供类
//services.AddTransient();
使用.NET Core集成的IoC容器,只需要定义好相应的接口,在Startup.cs的ConfigureService方法里使用对应的生命周期的绑定方法即可,常见的方法如下:
services.AddTransient();
service.AddScoped();
service.AddSingleton();
//但遇到泛型基类注册时
service.AddScoped(typeof(Ixxx<>),typeof(xxx<>));
使用不同的方法对应了注入的不同生命周期:
对应了Microsoft.Extensions.DependencyInjection.ServiceLifetime的三个枚举值:
public enum ServiceLifetime
{
Singleton,
Scoped,
Transient
}
我们都是在ConfigureServices(IServiceCollection services)进行服务的注册,该接口的定义如下,继承了IList,泛型定义为ServiceDescriptor,所以IServiceCollection本质就是IList。
using System.Collections.Generic;
namespace Microsoft.Extensions.DependencyInjection
{
public interface IServiceCollection : IList{}
}
而AddScoped、AddTransient、AddSingleton的方法位于ServiceCollectionServiceExtensions扩展类,用作IServiceCollection的扩展方法。
namespace Microsoft.Extensions.DependencyInjection
{
//Extension methods for adding services to an .
public static class ServiceCollectionServiceExtensions
知识点插入:当我们使用sealed关键字修饰类时就不能进行继承,这是为了防止该类的行为被破坏。但是我们可以通过两个方式来进行功能的重用:静态方法和扩展方法。静态方式是将该类型作为参数传入,从而实现重用。使用扩展方法,能够减少输入的代码量,扩展方法是静态方法的拓展。
比如我们想要扩展string类型的操作,增加检查字符串是否为电子邮箱格式,相应的静态方法和扩展方法如下。
//静态方法扩展
public class StringExtensions{
public static bool IsValidEmail(string input){
return Regex.IsMatch(input,@"[a-zA-Z0-9\.-_]+@[a-zA-Z0-9\.-_]+");
}
}
//扩展方法
public class StringExtensions{
public static bool IsValidEmail(this string input){
return Regex.IsMatch(input,@"[a-zA-Z0-9\.-_]+@[a-zA-Z0-9\.-_]+");
}
}
扩展方法即是在静态方法的基础上,在传入参数前增加this关键字,将其变为String类型的扩展方法,我们可以通过string.IsValidEmail直接进行调用。
在.Net Core里提供了默认的依赖注入容器IServiceCollection,它是一个轻量级容器。核心组件为两个IServiceCollection和IServiceProvider,IServiceCollection负责注册,IServiceProvider负责提供实例。
我们看下方ServiceConllection源码,因为是对IList
namespace Microsoft.Extensions.DependencyInjection
{
/// Default implementation of .
public class ServiceCollection : IServiceCollection
{
private readonly List _descriptors = new List();
public int Count => _descriptors.Count;
public bool IsReadOnly => false;
}
}
使用官方DI时注册我们都是将服务注册到一个ServiceCollection
对象中,IServiceCollection
集合就是一个继承IList
集合接口的一个类型,而ServiceDescriptor
类型则是一个注册的服务描述类型,我们传入注册最后都会封装为一个ServiceDescriptor
类型然后缓存到ServiceCollection
集合之中。
在使用之前我们先添加一个类库Extensions,然后加入以下NuGet依赖包:Autofac.Extensions.Dependencylnjection和Autofac.Extras.DynamicProxy。然后我们在Extensions类库中引入Services类库,在Controller类库取消Services类库的引用,改为引用Extensions类库。
定义一个类AutofacMoudleRegister实现Autofac.Module,然后重写Load方法,通过builder.RegisterType<实现类名>().As<接口名>();进行注册。
using Autofac;
using Services.IService;
using Services;
namespace Extensions.ServiceExtensions
{
public class AutofacMoudleRegister : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType().As();
//泛型注册
//builder.RegisterType(typeof(xxx<>)).As(typeof(Ixxx<>)).InstancePerDependency();
}
}
}
首先需要在CreateHostBuilder方法中,通过Host.UseServiceProviderFactory,传入AutofacServiceProviderFactory实例进行相应的容器添加(Use)。
namespace EFCoreToMysql.API
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
//配置Autofac工厂容器
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup();
});
}
}
然后在Startup类中增加相应的容器配置方法:
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterModule();
}
我们发现不仅要到Program和StartUp进行相应的注册配置,还得在Autofac.Module的实现类中通过builder.RegisterType.As这样的方式将类进行注册,才能使用依赖注入。这并没有减轻我们的工作量,但是除此方式以外,Autofac还增加了程序集批量注入的方式。
我们通过反射的方式通过Assembly.Load【Load(AssemblyName) 在给定程序集的 AssemblyName的情况下,加载程序集】将程序集中的类引入,然后通过builder.RegisterAssemblyTypes(程序集).AsImplementedInterfaces()来进行依赖注册。
using Autofac;
using Services.IService;
using Services;
using System.Reflection;
namespace Extensions.ServiceExtensions
{
public class AutofacModuleRegister : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
//Services注入
var assemblysServices = Assembly.Load("Services");
builder.RegisterAssemblyTypes(assemblysServices).AsImplementedInterfaces();
//Repositories注入
var assemblysRepositories = Assembly.Load("Repositories");
builder.RegisterAssemblyTypes(assemblysRepositories).AsImplementedInterfaces();
}
}
}
新建项目,这里可根据数据库设计直接建立PDM,
通过Palette进行实体的创建,和外键联系的建立。
在[数据库-生成数据库]或Ctrl+G来生成相应的SQL文件。
Project:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Entity.Models
{
[Table("project")]
public class Project
{
[Key]
public int PId { get; set; }
public string PName { get; set; }
public int Synopsis { get; set; }
public string PType { get; set; }
public int Star { get; set; }
public int Is_Filed { get; set; }
public int Is_Del { get; set; }
public int Del_Time { get; set; }
}
}
ProjectDbContext:
using Microsoft.EntityFrameworkCore;
using Entity.Models;
namespace Repository.DbContexts
{
public class ProjectDbContext : DbContext
{
public DbSet Projects { get; set; }
public ProjectDbContext(DbContextOptions options) : base(options) { }
}
}
ProjectRepository:
using System.Collections.Generic;
using System.Linq;
using Repository.DbContexts;
using Entity.Models;
using Repository.IRepository;
namespace Repository
{
public class ProjectRepository : IProjectRepository
{
private readonly ProjectDbContext _dbContext;
public ProjectRepository(ProjectDbContext dbContext)
{
_dbContext = dbContext;
}
public void Add(Project project)
{
_dbContext.Projects.Add(project);
_dbContext.SaveChanges();
}
public void Delete(Project project)
{
var projects = from p in _dbContext.Projects
where p.PId == project.PId
select p;
if (projects.FirstOrDefault() is Project)
{
_dbContext.Projects.Remove(projects.FirstOrDefault());
_dbContext.SaveChanges();
}
}
public IEnumerable GetAll()
{
return _dbContext.Projects.ToList();
}
public Project GetById(int id)
{
var projects = from project in _dbContext.Projects
where project.PId == id
select project;
if(projects.FirstOrDefault() is Project)
{
return projects.FirstOrDefault();
}
return null;
}
public void Update(Project project)
{
var projects = from p in _dbContext.Projects
where p.PId == project.PId
select p;
if(projects.FirstOrDefault() is Project)
{
Project newproject = projects.FirstOrDefault();
newproject.PType = project.PType;
newproject.PName = project.PName;
newproject.Synopsis = project.Synopsis;
_dbContext.SaveChanges();
}
}
}
}
ProjectService:
using Entity.Models;
using Service.IService;
using System.Collections.Generic;
using Repository.IRepository;
namespace Service
{
public class ProjectService : IProjectService
{
private readonly IProjectRepository _repository;
public ProjectService(IProjectRepository repository)
{
_repository = repository;
}
public IEnumerable GetAll()
{
return _repository.GetAll();
}
void IProjectService.Add(Project project)
{
_repository.Add(project);
}
void IProjectService.Delete(Project project)
{
_repository.Delete(project);
}
Project IProjectService.GetById(int id)
{
return _repository.GetById(id);
}
void IProjectService.Update(Project project)
{
_repository.Update(project);
}
}
}
ProjectController:
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Entity.Models;
using Service.IService;
using System.Collections.Generic;
namespace ProjectManagement.API.Controllers
{
///
/// 项目接口
///
[Route("api/[controller]")]
[ApiController]
public class ProjectController : ControllerBase
{
private readonly IProjectService _projectService;
public ProjectController(IProjectService projectService)
{
_projectService = projectService;
}
[HttpGet]
[Route("getAllProject")]
public IEnumerable GetAllProjects()
{
return _projectService.GetAll();
}
[HttpGet]
[Route("getProjectByID")]
public Project GetProjectByID(int id)
{
return _projectService.GetById(id);
}
[HttpPost]
[Route("addProject")]
public void AddProject(Project project)
{
_projectService.Add(project);
}
[HttpDelete]
[Route("delProject")]
public void DelProject(Project project)
{
_projectService.Delete(project);
}
[HttpPost]
[Route("updateProject")]
public void UpdateProject(Project project)
{
_projectService.Update(project);
}
}
}
在Git\usr\bin目录下打开命令提示符,输入ssh-keygen -t rsa -C “GitLab的邮箱地址”,生成相应的SSH文件[C:\Users\用户名\.ssh\id_ras.pub]。
然后到GitLab的用户设置中的SSH密钥进行添加,将id_ras.pub输入到相应位置,点击添加即可。