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? ProductId { get; set; } public int? CategoryId { get; set; } public string? Title { get; set; } public decimal? Price { get; set; } public string? Url { get; set; } public string? ImageUrl { get; set; } }
CompanyInfo的代码如下:
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<SysUser> SysUsers { get; set; } = new List<SysUser>(); }
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<AdvancedCustomerDbContext> options) : base(options) { } public virtual DbSet<Commodity> Commodities { get; set; } public virtual DbSet<CompanyInfo> CompanyInfos { get; set; } public virtual DbSet<SysUser> 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<Commodity>(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<CompanyInfo>(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<SysUser>(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<T> Query<T>(Expression<Func<T,bool>> 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<Commodity> 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<T> Query<T>(Expression<Func<T, bool>> funcWhere) where T : class { return this.Context.Set<T>().Where<T>(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<Commodity>().Add(commodity); int num= this.Context.SaveChanges(); return num > 0; } public IQueryable<Commodity> GetCommodity(int Id) { var list= this.Context.Set<Commodity>().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<CompanyInfo>().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? ProductId { get; set; } public int? CategoryId { get; set; } public string? Title { get; set; } public decimal? Price { get; set; } public string? Url { get; set; } public string? ImageUrl { get; set; } }
CompanyInfoDTO的代码如下:
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<SysUser> SysUsers { get; set; } = new List<SysUser>(); }
AuotoMapConfig的代码如下:
using AutoMapper; using SimpleWebApi.Migration.Models; namespace SimpleWebApi { public class AuotoMapConfig:Profile { public AuotoMapConfig() { CreateMap<Commodity, CommodityDTO>().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<CompanyInfo, CompanyInfoDTO>(); } } }
为了添加对象映射,DBContext,对象注入等,打开Program文件,按照如下添加
上图的红色代码如下:
//查询数据库真实数据的业务逻辑层服务注册 builder.Services.AddTransient<ICommodityService, CommodityService>(); builder.Services.AddTransient<ICompanyInfoService, CompanyInfoService>(); //添加DbContext //builder.Services.AddDbContext<AdvancedCustomerDbContext>(); builder.Services.AddTransient<DbContext, AdvancedCustomerDbContext>(); //支持AutoMapper builder.Services.AddAutoMapper(options => { options.AddProfile<AuotoMapConfig>(); });
选中“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<ApiController> _logger; private ICommodityService _comService; private ICompanyInfoService _companyService; private IMapper _mapper; public ApiController(ILogger<ApiController> logger, ICommodityService comService, ICompanyInfoService companyService, IMapper mapper) { _logger = logger; _comService = comService; _companyService = companyService; _mapper = mapper; } [HttpGet] public IEnumerable<CommodityDTO> GetCommodity(int Id) { Expression<Func<Commodity, bool>> funcWhere = null; funcWhere = a => a.Id == Id; var commodityList = _comService.Query(funcWhere); List<CommodityDTO> list = new List<CommodityDTO>(); _mapper.Map<IQueryable<Commodity>, List<CommodityDTO>>(commodityList, list); return list; } [HttpGet] public CompanyInfoDTO GetCompanyInfo(int companyId) { var company = _companyService.GetCompany(companyId); CompanyInfoDTO dto = new CompanyInfoDTO(); _mapper.Map<CompanyInfo, CompanyInfoDTO>(company, dto); return dto; } } }
这里有个小坑,打开SimpleWebApi.csproj,按照下图设置,可以解决问题
<InvariantGlobalization>false</InvariantGlobalization>
ps:默认该数据值是true,运行程序后会异常,异常提示如下:
System.Globalization.CultureNotFoundException HResult=0x80070057 Message=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.CoreLib StackTrace: 在 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.<MoveNext>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_ProjectSimpleProjectDemoSimpleProjectDemoControllersWeatherForecastController.cs 中: 第 50 行 在 Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncObjectResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) 在 Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<<InvokeActionMethodAsync>g__Logged|12_1>d.MoveNext()
2.7 运行项目
运行项目,如下图所示显示Swagger页面:
用接口api/Api/GetCommodity 来测试下
3.结论
至此,操作完成。成功的搭建了一个简单的.net 8.0的web api项目。