1.背景
工作以后,大部分时间都是在做基于业务的CRUD工作,软件产品或者项目的框架基本都早就搭建好了,程序员只需要在框架内去填格子打代码就行了。于是,我抽了时间来搭建个简单的三层架构模式的web api项目,技术点大概如下:三层架构+EFCore+.*** 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
***modity的代码如下:
using System;
using System.Collections.Generic;
namespace SimpleWebApi.Migration.Models;
public partial class ***modity
{
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; }
}
***panyInfo的代码如下:
using System;
using System.Collections.Generic;
namespace SimpleWebApi.Migration.Models;
public partial class ***panyInfo
{
public int ***panyId { 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? ***panyId { get; set; }
public virtual ***panyInfo? ***pany { 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<***modity> ***modities { get; set; }
public virtual DbSet<***panyInfo> ***panyInfos { 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.***/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.***/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<***modity>(entity =>
{
entity.ToTable("***modity");
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<***panyInfo>(entity =>
{
entity.HasKey(e => e.***panyId).HasName("PK_***pany");
entity.ToTable("***panyInfo");
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.***pany).WithMany(p => p.SysUsers)
.HasForeignKey(d => d.***panyId)
.OnDelete(DeleteBehavior.Cascade)
.HasConstraintName("FK_SysUser_***panyInfo");
});
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;
}
}
I***modityService的代码如下:
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 I***modityService:IBaseService
{
public bool Add***modity(***modity ***modity);
public IQueryable<***modity> Get***modity(int Id);
}
}
I***panyInfoService的代码如下:
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 I***panyInfoService:IBaseService
{
***panyInfo Get***pany(int ***panyID);
}
}
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);
}
}
}
***modityService的代码如下:
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 ***modityService : BaseService, I***modityService
{
public ***modityService(DbContext context):base(context)
{
}
public bool Add***modity(***modity ***modity)
{
this.Context.Set<***modity>().Add(***modity);
int num= this.Context.SaveChanges();
return num > 0;
}
public IQueryable<***modity> Get***modity(int Id)
{
var list= this.Context.Set<***modity>().Where(a => a.Id == Id);
return list;
}
}
}
***panyInfoService的代码如下:
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 ***panyInfoService:BaseService, I***panyInfoService
{
public ***panyInfoService(DbContext context):base(context)
{
}
public ***panyInfo Get***pany(int ***panyID)
{
var ***pany = this.Context.Set<***panyInfo>().Where(a=>a.***panyId==***panyID).FirstOrDefault();
return ***pany;
}
}
}
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的规则文件
***modityDTO的代码如下:
using System;
using System.Collections.Generic;
namespace SimpleWebApi;
public class ***modityDTO
{
public int ***modityId { 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; }
}
***panyInfoDTO的代码如下:
using SimpleWebApi.Migration.Models;
using System;
using System.Collections.Generic;
namespace SimpleWebApi;
public class ***panyInfoDTO
{
public int ***panyId { 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<***modity, ***modityDTO>().ForMember(c=>c.***modityId, 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<***panyInfo, ***panyInfoDTO>();
}
}
}
为了添加对象映射,DBContext,对象注入等,打开Program文件,按照如下添加
上图的红色代码如下:
//查询数据库真实数据的业务逻辑层服务注册
builder.Services.AddTransient<I***modityService, ***modityService>();
builder.Services.AddTransient<I***panyInfoService, ***panyInfoService>();
//添加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.Asp***Core.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 I***modityService _***Service;
private I***panyInfoService _***panyService;
private IMapper _mapper;
public ApiController(ILogger<ApiController> logger, I***modityService ***Service, I***panyInfoService ***panyService, IMapper mapper)
{
_logger = logger;
_***Service = ***Service;
_***panyService = ***panyService;
_mapper = mapper;
}
[HttpGet]
public IEnumerable<***modityDTO> Get***modity(int Id)
{
Expression<Func<***modity, bool>> funcWhere = null;
funcWhere = a => a.Id == Id;
var ***modityList = _***Service.Query(funcWhere);
List<***modityDTO> list = new List<***modityDTO>();
_mapper.Map<IQueryable<***modity>, List<***modityDTO>>(***modityList, list);
return list;
}
[HttpGet]
public ***panyInfoDTO Get***panyInfo(int ***panyId)
{
var ***pany = _***panyService.Get***pany(***panyId);
***panyInfoDTO dto = new ***panyInfoDTO();
_mapper.Map<***panyInfo, ***panyInfoDTO>(***pany, 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(Task***pletionSource`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.Relational***mand.ExecuteReader(Relational***mandParameterObject 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 verifySu***eeded)
在 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.Asp***Core.Mvc.Infrastructure.ActionMethodExecutor.SyncObjectResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
在 Microsoft.Asp***Core.Mvc.Infrastructure.ControllerActionInvoker.<<InvokeActionMethodAsync>g__Logged|12_1>d.MoveNext()
2.7 运行项目
运行项目,如下图所示显示Swagger页面:
用接口api/Api/Get***modity 来测试下
3.结论
至此,操作完成。成功的搭建了一个简单的.*** 8.0的web api项目。
ps:本项目的代码都很简单,真诚建议跟着指引来敲代码梳理思路,不过考虑极端情况,我还是打包代码上传到了csdn。有需要的童鞋可以按需下载。谢谢。