1.背景

工作以后,大部分时间都是在做基于业务的CRUD工作,软件产品或者项目的框架基本都早就搭建好了,程序员只需要在框架内去填格子打代码就行了。于是,我抽了时间来搭建个简单的三层架构模式的web api项目,技术点大概如下:三层架构+EFCore+.Net 8.0 Web Api+AutoMap+IOC容器。本文是我搭建项目的一个过程,比较简单和粗糙,但是完整,适合学习和练手。

2.操作
2.1 项目的架构图

其实图1是我最开始的设计结构,但是设计风格有提到:模块间应该依赖抽象,而不是具体的实现。所以我将结构改造为了图2,针对业务逻辑层和数据访问层开了一个抽象接口层。

2.2 新增项目

按照如下操作,创建项目:SimpleWebApi

2.3 新增类库

按照下图新增类库:SimpleWebApi.Migration、SimpleWebApi.Business.Service、SimpleWebApi.Business.Service.Interface

注意:SimpleWebApi.Migration是数据库访问

SimpleWebApi.Business.Service、SimpleWebApi.Business.Service.Interface是做业务逻辑

2.4 支持EFCore

找到类库:SimpleWebApi.Migration,并给这个项目,添加如下nuget包:Microsoft.EntityFrameworkCore、Microsoft.EntityFrameworkCore.SqlServer、Microsoft.EntityFrameworkCore.Tools、Microsoft.EntityFrameworkCore.Design

按照如下所示:添加Model和DBContext

Commodity的代码如下:

using System;using System.Collections.Generic;namespace SimpleWebApi.Migration.Models;public partial class Commodity{public int Id { get; set; }public long" />using System;using System.Collections.Generic;namespace SimpleWebApi.Migration.Models;public partial class CompanyInfo{public int CompanyId { get; set; }public string? Name { get; set; }public DateTime? CreateTime { get; set; }public int CreatorId { get; set; }public int? LastModifierId { get; set; }public DateTime? LastModifyTime { get; set; }public virtual ICollection SysUsers { get; set; } = new List();}

SysUser的代码如下:

using System;using System.Collections.Generic;namespace SimpleWebApi.Migration.Models;public partial class SysUser{public int Id { get; set; }public string? Name { get; set; }public string? Password { get; set; }public int Status { get; set; }public string? Phone { get; set; }public string? Mobile { get; set; }public string? Address { get; set; }public string? Email { get; set; }public long? Qq { get; set; }public string? WeChat { get; set; }public int? Sex { get; set; }public DateTime? LastLoginTime { get; set; }public DateTime? CreateTime { get; set; }public int? CreateId { get; set; }public DateTime? LastModifyTime { get; set; }public int? LastModifyId { get; set; }public int? CompanyId { get; set; }public virtual CompanyInfo? Company { get; set; }}

AdvancedCustomerDbContext的代码如下:

using System;using System.Collections.Generic;using Microsoft.EntityFrameworkCore;using SimpleWebApi.Migration.Models;namespace SimpleWebApi.Migration{public class AdvancedCustomerDbContext : DbContext{public AdvancedCustomerDbContext(){}public AdvancedCustomerDbContext(DbContextOptions options): base(options){}public virtual DbSet Commodities { get; set; }public virtual DbSet CompanyInfos { get; set; }public virtual DbSet SysUsers { get; set; }protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263.=> optionsBuilder.UseSqlServer("Data Source=127.0.0.1;Initial Catalog=AdvancedCustomerDB_Init;Persist Security Info=True;User ID=sa;Password=****;Encrypt=False;TrustServerCertificate=true");protected override void OnModelCreating(ModelBuilder modelBuilder){modelBuilder.Entity(entity =>{entity.ToTable("Commodity");entity.Property(e => e.ImageUrl).HasMaxLength(1000).IsUnicode(false);entity.Property(e => e.Price).HasColumnType("decimal(18, 2)");entity.Property(e => e.Title).HasMaxLength(500).IsUnicode(false);entity.Property(e => e.Url).HasMaxLength(1000).IsUnicode(false);});modelBuilder.Entity(entity =>{entity.HasKey(e => e.CompanyId).HasName("PK_Company");entity.ToTable("CompanyInfo");entity.Property(e => e.CreateTime).HasColumnType("datetime");entity.Property(e => e.LastModifyTime).HasColumnType("datetime");entity.Property(e => e.Name).HasMaxLength(50).IsUnicode(false);});modelBuilder.Entity(entity =>{entity.ToTable("SysUser");entity.Property(e => e.Address).HasMaxLength(500).IsUnicode(false);entity.Property(e => e.CreateTime).HasColumnType("datetime");entity.Property(e => e.Email).HasMaxLength(50).IsUnicode(false);entity.Property(e => e.LastLoginTime).HasColumnType("datetime");entity.Property(e => e.LastModifyTime).HasColumnType("datetime");entity.Property(e => e.Mobile).HasMaxLength(12).IsUnicode(false);entity.Property(e => e.Name).HasMaxLength(50).IsUnicode(false);entity.Property(e => e.Password).HasMaxLength(50).IsUnicode(false);entity.Property(e => e.Phone).HasMaxLength(12).IsUnicode(false);entity.Property(e => e.Qq).HasColumnName("QQ");entity.Property(e => e.WeChat).HasMaxLength(50).IsUnicode(false);entity.HasOne(d => d.Company).WithMany(p => p.SysUsers).HasForeignKey(d => d.CompanyId).OnDelete(DeleteBehavior.Cascade).HasConstraintName("FK_SysUser_CompanyInfo");});OnModelCreatingPartial(modelBuilder);}public void OnModelCreatingPartial(ModelBuilder modelBuilder){}}}
2.5 业务逻辑抽象层

找到项目“SimpleWebApi.Business.Service.Interface”,新增项目引用-SimpleWebApi.Migration,如下图:

按照下图添加以下接口:

IBaseService的代码如下:

using System;using System.Collections.Generic;using System.Linq;using System.Linq.Expressions;using System.Text;using System.Threading.Tasks;namespace SimpleWebApi.Business.Service.Interface{public interface IBaseService{public IQueryable Query(Expression<Func> funcWhere) where T : class;}}

ICommodityService的代码如下:

using SimpleWebApi.Migration.Models;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace SimpleWebApi.Business.Service.Interface{public interface ICommodityService:IBaseService{public bool AddCommodity(Commodity commodity);public IQueryable GetCommodity(int Id); }}

ICompanyInfoService的代码如下:

using SimpleWebApi.Migration.Models;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace SimpleWebApi.Business.Service.Interface{public interface ICompanyInfoService:IBaseService{CompanyInfo GetCompany(int companyID);}}

ISysUserService的代码如下:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace SimpleWebApi.Business.Service.Interface{public interface ISysUserService:IBaseService{}}

2.6 业务逻辑层

找到项目“SimpleWebApi.Business.Service”,新增项目引用如下:SimpleWebApi.Business.Service.Interface和SimpleWebApi.Migration

按照下图添加以下类:

BaseService的代码如下:

using Microsoft.EntityFrameworkCore;using SimpleWebApi.Business.Service.Interface;using System;using System.Collections.Generic;using System.Linq;using System.Linq.Expressions;using System.Text;using System.Threading.Tasks;namespace SimpleWebApi.Business.Service{public class BaseService:IBaseService{protected DbContext Context;public BaseService(DbContext context){Console.WriteLine($"{this.GetType().Name}被构造了......");this.Context= context; }public IQueryable Query(Expression<Func> funcWhere) where T : class{return this.Context.Set().Where(funcWhere); }}}

CommodityService的代码如下:

using Microsoft.EntityFrameworkCore;using SimpleWebApi.Business.Service.Interface;using SimpleWebApi.Migration.Models;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace SimpleWebApi.Business.Service{public class CommodityService : BaseService, ICommodityService{public CommodityService(DbContext context):base(context) {}public bool AddCommodity(Commodity commodity){this.Context.Set().Add(commodity);int num= this.Context.SaveChanges();return num > 0;}public IQueryable GetCommodity(int Id){var list= this.Context.Set().Where(a => a.Id == Id);return list;}}}

CompanyInfoService的代码如下:

using Microsoft.EntityFrameworkCore;using SimpleWebApi.Business.Service.Interface;using SimpleWebApi.Migration.Models;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace SimpleWebApi.Business.Service{public class CompanyInfoService:BaseService, ICompanyInfoService{public CompanyInfoService(DbContext context):base(context){}public CompanyInfo GetCompany(int companyID){var company = this.Context.Set().Where(a=>a.CompanyId==companyID).FirstOrDefault();return company; }}}

SysUserService的代码如下:

using Microsoft.EntityFrameworkCore;using SimpleWebApi.Business.Service.Interface;using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace SimpleWebApi.Business.Service{public class SysUserService:BaseService, ISysUserService{public SysUserService(DbContext context) : base(context){}}}
2.6 UI

找到项目“SimpleWebApi”,并新增项目引用:

SimpleWebApi.Business.Service、SimpleWebApi.Business.Service.Interface、SimpleWebApi.Migration

按照如下,新增nuget包引用:AutoMapper、AutoMapper.Extensions.Microsoft.DependencyInjection

新增文件夹DTO和Map,并新增类文件和AutoMapper的规则文件

CommodityDTO的代码如下:

using System;using System.Collections.Generic;namespace SimpleWebApi;public class CommodityDTO{public int CommodityId { get; set; }public long" />using SimpleWebApi.Migration.Models;using System;using System.Collections.Generic;namespace SimpleWebApi;public class CompanyInfoDTO{public int CompanyId { get; set; }public string? Name { get; set; }public DateTime? CreateTime { get; set; }public int CreatorId { get; set; }public int? LastModifierId { get; set; }public DateTime? LastModifyTime { get; set; }public virtual ICollection SysUsers { get; set; } = new List();}

AuotoMapConfig的代码如下:

using AutoMapper;using SimpleWebApi.Migration.Models;namespace SimpleWebApi{public class AuotoMapConfig:Profile{public AuotoMapConfig(){CreateMap().ForMember(c=>c.CommodityId, s=>s.MapFrom(c=>c.Id)).ForMember(c=>c.ProductId,s=>s.MapFrom(c=>c.ProductId)).ForMember(c=>c.CategoryId,s=>s.MapFrom(c=>c.CategoryId)).ForMember(c=>c.Title,s=>s.MapFrom(c=>c.Title)).ForMember(c=>c.Price,s=>s.MapFrom(c=>c.Price)).ForMember(c=>c.Url,s=>s.MapFrom(c=>c.Url)).ForMember(c=>c.ImageUrl,s=>s.MapFrom(c=>c.ImageUrl));CreateMap();}}}

为了添加对象映射,DBContext,对象注入等,打开Program文件,按照如下添加

上图的红色代码如下:

//查询数据库真实数据的业务逻辑层服务注册builder.Services.AddTransient();builder.Services.AddTransient();//添加DbContext//builder.Services.AddDbContext();builder.Services.AddTransient();//支持AutoMapperbuilder.Services.AddAutoMapper(options =>{options.AddProfile();});

选中“Controllers”文件夹新增控制器 ApiController

ApiController代码如下:

using AutoMapper;using Microsoft.AspNetCore.Mvc;using SimpleWebApi.Business.Service.Interface;using SimpleWebApi.Migration.Models;using System.Linq.Expressions;namespace SimpleWebApi.Controllers{[ApiController][Route("api/[controller]/[action]")]public class ApiController : ControllerBase{private readonly ILogger _logger;private ICommodityService _comService;private ICompanyInfoService _companyService;private IMapper _mapper;public ApiController(ILogger logger, ICommodityService comService, ICompanyInfoService companyService, IMapper mapper){_logger = logger;_comService = comService;_companyService = companyService;_mapper = mapper;}[HttpGet]public IEnumerable GetCommodity(int Id){Expression<Func> funcWhere = null;funcWhere = a => a.Id == Id;var commodityList = _comService.Query(funcWhere);List list = new List();_mapper.Map<IQueryable, List>(commodityList, list);return list;}[HttpGet]public CompanyInfoDTO GetCompanyInfo(int companyId){var company = _companyService.GetCompany(companyId);CompanyInfoDTO dto = new CompanyInfoDTO();_mapper.Map(company, dto);return dto;}}}

这里有个小坑,打开SimpleWebApi.csproj,按照下图设置,可以解决问题

false

ps:默认该数据值是true,运行程序后会异常,异常提示如下:

System.Globalization.CultureNotFoundExceptionHResult=0x80070057Message=Only the invariant culture is supported in globalization-invariant mode. See https://aka.ms/GlobalizationInvariantMode for more information. (Parameter 'name')en-us is an invalid culture identifier.Source=System.Private.CoreLibStackTrace: 在 System.Globalization.CultureInfo.GetCultureInfo(String name) 在 Microsoft.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry, SqlConnectionOverrides overrides) 在 Microsoft.Data.SqlClient.SqlConnection.Open(SqlConnectionOverrides overrides) 在 Microsoft.Data.SqlClient.SqlConnection.Open() 在 Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerConnection.OpenDbConnection(Boolean errorsExpected) 在 Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenInternal(Boolean errorsExpected) 在 Microsoft.EntityFrameworkCore.Storage.RelationalConnection.Open(Boolean errorsExpected) 在 Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReader(RelationalCommandParameterObject parameterObject) 在 Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.InitializeReader(Enumerator enumerator) 在 Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.c.b__21_0(DbContext _, Enumerator enumerator) 在 Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded) 在 Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.MoveNext() 在 System.Collections.Generic.List`1..ctor(IEnumerable`1 collection) 在 System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source) 在 SimpleProjectDemo.Controllers.WeatherForecastController.Get2() 在 E:\Vs_Project\SimpleProjectDemo\SimpleProjectDemo\Controllers\WeatherForecastController.cs 中: 第 50 行 在 Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncObjectResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) 在 Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<g__Logged|12_1>d.MoveNext()
2.7 运行项目

运行项目,如下图所示显示Swagger页面:

用接口api/Api/GetCommodity 来测试下

3.结论

至此,操作完成。成功的搭建了一个简单的.net 8.0的web api项目。

ps:本项目的代码都很简单,真诚建议跟着指引来敲代码梳理思路,不过考虑极端情况,我还是打包代码上传到了csdn。有需要的童鞋可以按需下载。谢谢。