本文章所有环境均在Visual Studio 2022 下进行
首先我们介绍一下MVC架构模式
-
首先就是我们MVC架构中的
M
-----模型(Model):用于封装与应用程序业务逻辑相关的数据以及对数据的处理方法。Model有对数据直接访问的权力,例如对数据库的访问。Model不依赖View和Controller,也就是说,Model不关心它会被如何显示或是如何被操作。但是Model中数据的变化一般会通过一种刷新机制被公布。为了实现这种机制,那些用于监视此Model的View必须事先在此Model上注册,由此,View可以了解在数据Model上发生的改变。(如,软件设计模式中的“观察者模式”); -
其实是MVC架构中的
V
-----视图(View):能够实现数据有目的的显示(理论上,这不是必需的)。在View中一般没有程序上的逻辑。为了实现View上的刷新功能,View需要访问它监视的数据模型(即,Model),因此应该事先在被它监视的数据那里注册; -
最后是我们MVC架构中的
C
-----控制器(Controller):起到不同层面间的组织作用,用于控制应用程序的流程。它处理事件并作出响应。“事件”包括用户的行为和数据Model上的改变。
这里我们详细介绍一下控制器(Controller)的职责:
1. 标识控制器需要哪些服务才能处于有些状态,并在它们的类构造函数中正常工作。
这跟我们前面讲到的依赖注入那节有关,当你体验过了依赖注入这点就容易理解了。
2. 从HTTP请求中提取参数。
比如上一节实现的依赖注入中我们的接口参数
pageindex
和pagesize
就是我们需要的参数)
- 将视图中的结果作为HTTP响应返回给客户端,并带有适当的状态码。
- 使用操作(action)名称标识要执行的方法。
- 使用参数获取构建视图模型所需的任何额外数据,并将他们传递给客户端相应的视图。
总的来说控制器的核心作用在于它只执行上面列出的职责,下面的业务逻辑如何实现的它不管,他只提供需要的服务调用。咱们把这个场景抽象为我们在饭店点菜,这个控制器所代表的角色就是
服务员
,服务员根据顾客的需求去指派对应的后厨制作菜品。同样的比喻,我们的M(Model)模型就是我们对应的厨师,具体的菜的制作就是由我们各位厨师去执行烧制的,我们的V(view)视图自然就是我们的菜单了。菜单上展示了餐厅提供了哪些菜品,以供顾客挑选。
介绍完了MVC架构,详细大家对这个架构应该有点概念了吧?接下来我们进入实践阶段来给大家加深一下映像。
M(Model)模型
为了便于讲解和大家理解,这里我们将所有实际执行全部归到M(Model)模型层。
厨师(Model)要做菜了,食材总需要吧?那我们就要给大厨M准备一个Entity层专门用来存储食材(实体对象),厨具也需要吧?那我们要准备一个Service(服务)层,来为我们加工食物提供技术支持,那最后我们摆放厨具的橱柜也需要吧??随便乱放橱柜那岂不是乱套了,那咱就再准备一个IService(接口服务)来存放我们的(Service)厨具.
Entity(实体)
我们在解决方案的位置右击,点击
添加
-->新建项目
-->类库
。最后我们把这个类库命名为langlangshan.SmartFactory.Entity
。创建好
langlangshan.SmartFactory.Entity
之后我们在里面按照下图的结构创建对应的文件夹和类
这里解释一下为什么我们的Enitity(实体)层为什么要分为
EntityMap
和EntityDto
,你想啊,你能直接把食材直接生的端给顾客吗???肯定不行呀,我们得把加工之后的食物端给顾客,那我们就新建了一个EntityDto文件夹来专门用于存储加工好的数据,EntityMap就老老实实存储数据本身。
EntityMap
话不多说,接下来我们直接开始编写我们的代码,这里我们以
SystemLog.cs
为例,SystemLog.cs
存储的是我们网站运行的产生的系统日志,相关内容我们已经存储在数据库中了。接下来给大家看一眼我们的数据库情况。
我们数据表
SystemLog
里面的字段有Id、Data 、Thread 、Level、Logger、Message、Exception。那我们的SystemLog.cs
自然与这些字段匹配即可。
public partial class SystemLog
{
public SystemLog()
{
}
/// <summary>
/// Desc:
/// Default:
/// Nullable:False
/// </summary>
///
[SqlSugar.SugarColumn(IsPrimaryKey = false, IsIdentity = true)]
public int Id { get; set; }
/// <summary>
/// Desc:
/// Default:
/// Nullable:False
/// </summary>
public DateTime Date { get; set; }
/// <summary>
/// Desc:
/// Default:
/// Nullable:False
/// </summary>
public string Thread { get; set; }
/// <summary>
/// Desc:
/// Default:
/// Nullable:False
/// </summary>
public string Level { get; set; }
/// <summary>
/// Desc:
/// Default:
/// Nullable:False
/// </summary>
public string Logger { get; set; }
/// <summary>
/// Desc:
/// Default:
/// Nullable:False
/// </summary>
public string Message { get; set; }
/// <summary>
/// Desc:
/// Default:
/// Nullable:True
/// </summary>
public string Exception { get; set; }
}
[SqlSugar.SugarColumn(IsPrimaryKey = false, IsIdentity = true)],这句是在限定
Id
字段不是主键,但是是自增状态,即每次有新数据会自动+1简单来说
SystemLog.cs
中的代码内容就是与对应数据表结构一致即可。
EntityDto
那思考一下EntityDto中我们要写什么内容呢???上面我们说到EntityDto是我们从EntityMap中加工好的数据,既然是加工,这个过程我们可以不处理任何数据,也可以隐藏掉部分数据,当然也可以新增数据。所有我们这个EntityDto的内容设计完全根据我们的需求来决定:
- 比如我们EntityMap中我们有
Username
、Password
字段,此时我们使用EntityDto去展示数据的时候我们不想要Password
字段出现在用户面前,那自然我们就丢下Password
字段即可。
但是我们本次教学系统是想要EntityDto能够将EntityMap完全显示,所有我们将写上EntityMap所拥有的全部字段
public partial class SystemLogDto
{
public SystemLogDto()
{
}
/// <summary>
/// Desc:
/// Default:
/// Nullable:False
/// </summary>
///
[SqlSugar.SugarColumn(IsPrimaryKey = false, IsIdentity = true)]
public int Id { get; set; }
/// <summary>
/// Desc:
/// Default:
/// Nullable:False
/// </summary>
public DateTime Date { get; set; }
/// <summary>
/// Desc:
/// Default:
/// Nullable:False
/// </summary>
public string Thread { get; set; }
/// <summary>
/// Desc:
/// Default:
/// Nullable:False
/// </summary>
public string Level { get; set; }
/// <summary>
/// Desc:
/// Default:
/// Nullable:False
/// </summary>
public string Logger { get; set; }
/// <summary>
/// Desc:
/// Default:
/// Nullable:False
/// </summary>
public string Message { get; set; }
/// <summary>
/// Desc:
/// Default:
/// Nullable:True
/// </summary>
public string Exception { get; set; }
}
}
至此我们实体(Entity)类就处理完毕了,接下来我们去处理接口类和服务类
IService(接口服务)
在写接口前我们需要弄明白,为什么要设计一个接口的概念?用接口有什么优势呢?? 设想一下这么一个场景:你有两种实现方法去实现一个功能,你随便选了一种方法运行起来了,此时你想用一下另外一个方法,但是又想要保留自己辛辛苦苦写的第一种方法的实现。突然你想到了一种模式:
你想到了如上图的调用模式,你打算在在对
更新数据
这一功能增加一个传呼器,这个传呼器只有规范和定义功能所具有的方法的用途,并不具有实现具体功能的作用。这个传呼器就是我们的接口服务,此时我们想要方法一我们直接去呼叫方法一的实现类,想要方法二我们就去呼叫方法二的实现类就好了。这就是我们接口的重要用途!接下来我们来讲实际操作,一般我们的接口会有一个Base(基础)接口,这个接口定义一些最普遍的一些方法,这样假如我们有多个接口要调用同一类的方法就不用重复写了。除了Base(基础)接口之外就去各种个性化定制的接口,这些接口用来实现个性化的接口方法定义。接下来我们将展示
IBaseService.cs
---基础接口、ISystemlogService.cs
---系统日志查询接口。
IBaseService
//基础接口
using langlangshanWeb.Common.Result;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
namespace langlangshanWeb.BusinessInterface
{
public interface IBaseService
{
#region Query
/// <summary>
/// 主键查询
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="id"></param>
/// <returns></returns>
T Find<T>(int id) where T : class;
/// <summary>
/// 主键查询--异步版本
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="id"></param>
/// <returns></returns>
Task<T> FindAsync<T>(int id) where T : class;
/// <summary>
/// 提供对单表的查询
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
[Obsolete("尽量避免使用,using 带表达式目录树的 代替")]
ISugarQueryable<T> Set<T>() where T : class;
/// <summary>
/// 条件查询
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="funcWhere"></param>
/// <returns></returns>
ISugarQueryable<T> Query<T>(Expression<Func<T,bool>> funcWhere) where T : class;
/// <summary>
/// 分页查询
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="funcWhere"></param>
/// <param name="pageSize"></param>
/// <param name="pageIndex"></param>
/// <param name="funcOrderby"></param>
/// <param name="isAsc"></param>
/// <returns></returns>
PagingData<T> QueryPage<T>(Expression<Func<T,bool>>funcWhere, int pageSize,int pageIndex, Expression<Func<T,object>>funcOrderby,bool isAsc=true )where T: class;
#endregion
ISystemlogService
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace langlangshanWeb.BusinessInterface
{
public interface ISystemlogService:IBaseService
{
public void InsertAndUpdate();//个性化定制方法名
}
}
这个
InsertAndUpdate
就是我们的个性化定制方法名,定义了除了基础方法之外每个服务特有的方法
Service(服务)
听完了上面接口服务的讲解,那这里的Service(服务)层就无需多言了吧?这里我们主要起到一个实现接口类的作用,也就是上面我们讲的实现类。同样的为了避免某些高频率的基本方法的重复编写,实现类也要有一个Base(基础)实现类
BaseService
using langlangshanWeb.BusinessInterface;
using langlangshanWeb.Common.Result;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
namespace langlangshanWeb.BusinessService
{
public class BaseService:IBaseService
{
protected ISqlSugarClient _Client;
/// <summary>
/// 构造函数注入
/// </summary>
/// <param name="client"></param>
public BaseService (ISqlSugarClient client)
{
_Client = client;
}
#region Query
/// <summary>
/// 主键查询
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="id"></param>
/// <returns></returns>
public T Find<T>(int id) where T : class
{
return _Client.Queryable<T>().InSingle(id);
}
/// <summary>
/// 主键查询--异步版本
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="id"></param>
/// <returns></returns>
public async Task<T> FindAsync<T>(int id) where T : class
{
return await _Client.Queryable<T>().InSingleAsync(id);
}
/// <summary>
/// 不应该暴露给上端使用者,尽量少用
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
[Obsolete("尽量避免使用,using带表达式目录树的代替")]
public ISugarQueryable<T> Set<T>() where T : class
{
return _Client.Queryable<T>();
}
/// <summary>
/// 条件查询
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="funcWhere"></param>
/// <returns></returns>
public ISugarQueryable<T> Query<T>(Expression<Func<T, bool>> funcWhere) where T : class
{
return _Client.Queryable<T>().Where(funcWhere);
}
/// <summary>
/// 分页查询
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="funcWhere"></param>
/// <param name="pageSize"></param>
/// <param name="pageIndex"></param>
/// <param name="funcOrderby"></param>
/// <param name="isAsc"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public PagingData<T> QueryPage<T>(Expression<Func<T, bool>> funcWhere, int pageSize, int pageIndex, Expression<Func<T, object>> funcOrderby, bool isAsc = true) where T : class
{
var list = _Client.Queryable<T>();
if(funcWhere !=null)
{
list = list.Where(funcWhere);
}
list = list.OrderByIF(true, funcOrderby, isAsc ? OrderByType.Asc : OrderByType.Desc);
PagingData<T> result = new PagingData<T>()
{
DataList = list.ToPageList(pageIndex, pageSize),
PageIndex = pageIndex,
PageSize = pageSize,
RecordCount = list.Count(),
};
return result;
}
}
}
SystemlogService
using langlangshanWeb.BusinessInterface;
using SqlSugar;
namespace langlangshanWeb.BusinessService
{
public class SystemlogService : BaseService, ISystemlogService
{
public SystemlogService(ISqlSugarClient client) : base(client)
{
}
public void InsertAndUpdate()
{
throw new NotImplementedException();
}
}
}
这部分我们只介绍的是整体架构,代码具体的一些实现细节我们暂时先不讲。
公共(Common)类
这里本质上还是为了解耦,以更好地分离关注点和提高代码的可维护性。我们上面的Entity主要是为了与数据库的字段对接,那我们还有一些数据结构不是直接对应数据表的,并且其是一个通用的数据结构,比如我们系统返回的分页数据。那我们就要引入一个通用类来存储它了。
首先我们要创建一个名叫
langlangshanWeb.Common
的类库,在此类库中我们创建一个名叫Result
的文件夹,在此文件夹中我们创建类PagingData.cs
。类库截图如下图所示:
PagingData就是写我们从数据量返回页面数据,话不多说我们直接上代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace langlangshanWeb.Common.Result
{
public class PagingData<T> where T : class
{
public int RecordCount { get; set; }
public int PageIndex { get; set; } //页码
public int PageSize { get; set; }//每页的页码大小
public List<T>? DataList { get; set; }
public string? SearchString { get; set; }
}
}
是不是很熟悉呀这些字段,这就是我们上节依赖注入的分页数据查询api的参数PageIndex,PageSize。我们具体的数据则是存在了DataList中。
C(Controller)控制器
反正这部分你只要注意依赖注入是最关键的部分就好了,这点能理清楚,其余的部分也没什么好讲的,毕竟它的定位就是餐厅服务员,把执行好的菜(数据)端上来就好了。下面直接上代码,本段代码实现的是分页查询的功能
using AutoMapper;
using langlangshan.SmartFactory.Enitity.EntityDto;
using langlangshan.SmartFactory.Enitity.EntityMap;
using langlangshanWeb.BusinessInterface;
using langlangshanWeb.BusinessService;
using langlangshanWeb.Common.Result;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
namespace langlangshanWebAPI.Controllers
{
/// <summary>
/// 系统日志管理
/// </summary>
[ApiController]
[Route("[controller]")]
public class SystemLogController : ControllerBase
{
private readonly ILogger<SystemLogController> _logger;
private readonly ISqlSugarClient _SqlSugarClient;
private readonly ISystemlogService _SystemlogService;
private readonly IMapper _IMapper;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="logger"></param>
/// <param name="sqlSugarClient"></param>
/// <param name="systemlogService"></param>
public SystemLogController(ILogger<SystemLogController> logger,ISqlSugarClient sqlSugarClient,ISystemlogService
systemlogService,IMapper IMapper)
{
_logger = logger;
_SqlSugarClient = sqlSugarClient;
_SystemlogService = systemlogService;
_IMapper = IMapper;
}
}
/// <summary>
/// 分页查询
/// </summary>
/// <param name="pageindex">当前第几页</param>
/// <param name="pageSize">每一页的数量</param>
/// <returns></returns>
///
[HttpGet("{pageindex:int}/{pageSize:int}")]
public IActionResult SystemLogPage(int pageindex,int pageSize)
{
PagingData <SystemLog> pagerlist = _SystemlogService.QueryPage<SystemLog>(c => true, pageSize, pageindex, s => s.Id, true);
PagingData<SystemLogDto> result = _IMapper.Map<PagingData<SystemLog>, PagingData<SystemLogDto>>(pagerlist);
return new JsonResult(result);
}
}
以上完成了,我们不要忘记在
Program.cs
类完成对control
部分需要的服务进行注册哦~。
builder.Services.AddTransient<IUserService, UserService>();
builder.Services.AddTransient<ISystemlogService, SystemlogService>();
停留在世界边缘,与之惜别