本文章所有环境均在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上的改变。
    浪浪山管理系统开发---MVC架构设计

这里我们详细介绍一下控制器(Controller)的职责:

1.  标识控制器需要哪些服务才能处于有些状态,并在它们的类构造函数中正常工作。

这跟我们前面讲到的依赖注入那节有关,当你体验过了依赖注入这点就容易理解了。

2. 从HTTP请求中提取参数。

比如上一节实现的依赖注入中我们的接口参数pageindexpagesize就是我们需要的参数)

浪浪山管理系统开发---MVC架构设计

  1. 将视图中的结果作为HTTP响应返回给客户端,并带有适当的状态码。
  2. 使用操作(action)名称标识要执行的方法。
  3. 使用参数获取构建视图模型所需的任何额外数据,并将他们传递给客户端相应的视图。

总的来说控制器的核心作用在于它只执行上面列出的职责,下面的业务逻辑如何实现的它不管,他只提供需要的服务调用。咱们把这个场景抽象为我们在饭店点菜,这个控制器所代表的角色就是服务员,服务员根据顾客的需求去指派对应的后厨制作菜品。

同样的比喻,我们的M(Model)模型就是我们对应的厨师,具体的菜的制作就是由我们各位厨师去执行烧制的,我们的V(view)视图自然就是我们的菜单了。菜单上展示了餐厅提供了哪些菜品,以供顾客挑选。

介绍完了MVC架构,详细大家对这个架构应该有点概念了吧?接下来我们进入实践阶段来给大家加深一下映像。

M(Model)模型

为了便于讲解和大家理解,这里我们将所有实际执行全部归到M(Model)模型层。

厨师(Model)要做菜了,食材总需要吧?那我们就要给大厨M准备一个Entity层专门用来存储食材(实体对象),厨具也需要吧?那我们要准备一个Service(服务)层,来为我们加工食物提供技术支持,那最后我们摆放厨具的橱柜也需要吧??随便乱放橱柜那岂不是乱套了,那咱就再准备一个IService(接口服务)来存放我们的(Service)厨具.

Entity(实体)

我们在解决方案的位置右击,点击添加 --> 新建项目 --> 类库。最后我们把这个类库命名为langlangshan.SmartFactory.Entity

创建好langlangshan.SmartFactory.Entity之后我们在里面按照下图的结构创建对应的文件夹和类

浪浪山管理系统开发---MVC架构设计

这里解释一下为什么我们的Enitity(实体)层为什么要分为EntityMapEntityDto,你想啊,你能直接把食材直接生的端给顾客吗???肯定不行呀,我们得把加工之后的食物端给顾客,那我们就新建了一个EntityDto文件夹来专门用于存储加工好的数据,EntityMap就老老实实存储数据本身。

EntityMap

话不多说,接下来我们直接开始编写我们的代码,这里我们以SystemLog.cs为例,SystemLog.cs存储的是我们网站运行的产生的系统日志,相关内容我们已经存储在数据库中了。接下来给大家看一眼我们的数据库情况。

浪浪山管理系统开发---MVC架构设计

我们数据表SystemLog里面的字段有IdDataThreadLevelLoggerMessageException。那我们的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的内容设计完全根据我们的需求来决定:

  1. 比如我们EntityMap中我们有UsernamePassword字段,此时我们使用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(接口服务)

在写接口前我们需要弄明白,为什么要设计一个接口的概念?用接口有什么优势呢?? 设想一下这么一个场景:你有两种实现方法去实现一个功能,你随便选了一种方法运行起来了,此时你想用一下另外一个方法,但是又想要保留自己辛辛苦苦写的第一种方法的实现。突然你想到了一种模式:

浪浪山管理系统开发---MVC架构设计

你想到了如上图的调用模式,你打算在在对更新数据这一功能增加一个传呼器,这个传呼器只有规范和定义功能所具有的方法的用途,并不具有实现具体功能的作用。这个传呼器就是我们的接口服务,此时我们想要方法一我们直接去呼叫方法一的实现类,想要方法二我们就去呼叫方法二的实现类就好了。这就是我们接口的重要用途!

接下来我们来讲实际操作,一般我们的接口会有一个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。类库截图如下图所示:

浪浪山管理系统开发---MVC架构设计

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>();