學習ASP.NET Core(06)-Restful與WebAPI

上一篇咱們使用Swagger添加了接口文檔,使用Jwt完成了受權,本章咱們簡答介紹一下RESTful風格的WebAPI開發過程當中涉及到的一些知識點,並完善一下還沒有完成的功能html


.NET下的WebAPI是一種無限接近RESTful風格的框架,RESTful風格它有着本身的一套理論,它的大概意思就是說使用標準的Http方法,將Web系統的服務抽象爲資源。稍微具體一點的介紹能夠查看阮一峯的這篇文章RESTful API最佳實踐。咱們這裏就分幾部分介紹一下構建RESTful API須要瞭解的基礎知識vue

注:本章介紹部分的內容大可能是基於solenovex的使用 ASP.NET Core 3.x 構建 RESTful Web API視頻內容的整理,若想進一步瞭解相關知識,請查看原視頻git

1、HTTP方法

一、什麼是HTTP方法

HTTP方法是對Web服務器的說明,說明如何處理請求的資源。HTTP1.0 定義了三種請求方法: GET, POST 和 HEAD方法;HTTP1.1 新增了六種請求方法:OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT 方法。github

二、經常使用的HTTP方法

  1. GET:一般用來獲取資源;GET請求會返回請求路徑對應的資源,但它分兩種狀況:web

    ①獲取單個資源,經過使用URL的形式帶上惟一標識,示例:api/Articles/{ArticleId};數據庫

    ②獲取集合資源中符合條件的資源,會經過QueryString的形式在URL後面添加?查詢條件做爲篩選條件,示例:api/Articles?title=WebAPIjson

  2. POST:一般用來建立資源;POST的參數會放在請求body中,POST請求應該返回新建立的資源以及能夠獲取該資源的惟一標識URL,示例:api/Articles/{新增的ArticleId}c#

  3. DELETE:一般用來移除/刪除對應路徑的資源;經過使用URL的形式帶上惟一標識,或者和GET同樣使用QueryString,處理完成後一般不會返回資源,只返回狀態碼204,示例:api/Articles/{ArticleId};後端

  4. PUT:一般用來徹底替換對應路徑的資源信息;POST的參數會放在請求body中,且爲一個完整對象,示例:api/Articles/{ArticleId};與此同時,它分兩類狀況:api

    ①對應的資源不存在,則新增對應的資源,後續處理和POST同樣;

    ②對應的資源存在,則替換對應的資源,處理完成不須要返回信息,只返回狀態碼204

  5. PATCH:一般用來更新對應路徑資源的局部信息;PATCH的參數會放在請求頭中,處理完成後一般不會返回資源,只返回狀態碼204,示例:api/Articles/{ArticleId};

    綜上:給出一張圖例,來自solenovex,使用 ASP.NET Core 3.x 構建 RESTful Web API

三、安全性和冪等性

安全性是指方法執行後不會改變資源的表述;冪等性是指方法不管執行多少次都會獲得相同的結果

2、狀態碼相關

一、狀態碼

HTTP狀態碼是表示Web服務器響應狀態的3位數字代碼。一般會以第一位數字爲區分

1xx:屬於信息性的狀態碼,WebAPI不使用

2xx:表示請求執行成功,經常使用的有200—請求成功,201—建立成功,204—請求成功無返回信息,如刪除

3xx:用於跳轉,如告訴搜索引擎,網址已改變。大多數WebAPI不須要使用這類狀態碼

4xx:表示客戶端錯誤

  • 400:Bad Request,表示API用戶發送到服務器的請求存在錯誤;
  • 401:Unauthorized,表示沒有提供受權信息,或者受權信息不正確;
  • 403:Forbidden,表示身份認證成功,可是無權限訪問請求的資源
  • 404:Not Found,表示請求的資源不存在
  • 405:Method not allowed,表示使用了不被支持的HTTP方法
  • 406:Not Acceptable,表示API用戶請求的格式WebAPI不支持,且WebAPI不提供默認的表述格式
  • 409:Conflict,表示衝突,通常用來表述併發問題,如修改資源期間,資源被已經被更新了
  • 415:Unsupported Media Type,與406相反,表示服務器接受的資源WebAPI不支持
  • 422:Unprocessable Entity,表示服務器已經解析了內容,可是沒法處理,如實體驗證錯誤

5xx:表示服務器錯誤

  • 500:INternal Server Error:表示服務器發生了錯誤,客戶端沒法處理

二、錯誤與故障

基於HTTP請求狀態碼,咱們須要瞭解一下錯誤和故障的區別

錯誤:API正常工做,可是API用戶請求傳遞的數據不合理,因此請求被拒絕。對應4xx錯誤;

故障:API工做異常,API用戶請求是合理的,可是API沒法響應。對應5xx錯誤

三、故障處理

咱們能夠在非開發環境進行以下配置,以確保生產環境異常時能查看到相關異常說明,一般這裏會寫入日誌記錄異常,咱們會在後面的章節添加日誌功能,這裏先修改以下:

3、WebAPI相關

一、內容協商

  1. 什麼是內容協商?即當有多種表述格式(Json/Xml等)可用時,選取最佳的一個進行表述。簡單來講就是請求什麼格式,服務端就返回什麼格式的數據;
  2. 如何設置內容協商?首先咱們要從服務端的角度區分輸出和輸入,輸出表示客戶端發出請求服務端響應數據;輸入表示客戶端提交數據服務端對其進行處理;舉例來講,Get就是輸出,Post就是輸入
  • 先看輸出:在Http請求的Header中有一個Accept Header屬性,如該屬性設置的是application/json,那麼API返回的就應該是Json格式的;在ASP.NET Core中負責響應輸出格式的就是Output Formatters對象
  • 再看輸入:HTTP請求的輸入格式對應的是Content-Type Header屬性,ASP.NET Core中負責響應輸入格式的就是Input Formatters對象

PS:若是沒有設置請求格式,就返回默認格式;而若是請求的格式不存在,則應當返回406狀態碼;

二、內容協商設置

ASP.NET Core目前的設置是僅返回Json格式信息,不支持XML;若是請求的是XML或沒有設置,它一樣會返回Json;若是但願關閉此項設置,即不存在返回406狀態碼,能夠在Controller服務註冊時添加以下設置;

而若是但願支持輸出和輸入都支持XML格式,能夠配置以下:

三、對象綁定

客戶端數據能夠經過多種方式傳遞給API,Binding Source Attribute則是負責處理綁定的對象,它會爲告知Model的綁定引擎,從哪裏能夠找到綁定源,Binding Source Attribute一共有六種綁定數據來源,以下:

  • FromBody:從請求的Body中獲取綁定數據
  • FromForm:從請求的Body中的form獲取綁定數據
  • FromHeader:從請求的Header中獲取綁定數據
  • FromQuery:從QueryString參數中獲取綁定數據
  • FromRoute:從當前請求的路由中獲取綁定數據
  • FromService:從做爲Action參數而注入的服務中獲取綁定數據

四、ApiController特性

ASP.NET Core WebAPI中咱們一般會使用[ApiController]特性來修飾咱們的Controller對象,該特性爲了更好的適應API方法,對上述分類規則進行了修改,修改以下:

  • FormBody:一般用來推斷複雜類型的參數
  • FromForm:一般用來推斷IFormFilr和IFormFileColllection類型的Action參數,即文件上傳相對應的參數
  • FromRoute:一般用來推斷Action的參數名和路由模板中的參數名一致的狀況
  • FromQuery:用來推斷其餘的Action參數

一些特殊狀況,須要手動指明對象的來源,如在HttpGet方法中,查詢參數是一個複雜的類類型,則ApiController對象會默認綁定源爲請求body, 這時候就須要手動指明綁定源爲FromQuery;

五、輸入驗證

一般咱們會使用一些驗證規則對客戶端的輸入內容進行限制,像用戶名不能包含特殊字符,用戶名長度等

一、驗證規則

WebAPI中內置了一組名爲Data Annotations的驗證規則,像以前咱們添加的[Required],[StringLength...]都屬於這個類型。或者咱們能夠自定義一個類,實現IValidatableObject接口,對多個字段進行限制;固然咱們也能夠針對類或者是屬性自定義一些驗證規則,須要繼承ValidationAttribute類重寫IsValid方法

二、驗證檢查

檢查時會使用ModelState對象,它是一個字典,包含model的狀態和model的綁定驗證信息;同時它還包含針對每一個提交的屬性值的錯誤信息的集合,每當有請求進來的時候,定義好的驗證規則就會被檢查。若是驗證不經過,ModelState.IsValid()就會返回false;

三、報告驗證錯誤

如發生驗證錯誤,應當返回Unprocessable Entity 422錯誤,並在響應的body中包含驗證錯誤信息;ASP.NET Core已經定義好了這部份內容,當Controller使用[ApiController]屬性進行註解時,若是遇到錯誤,那麼將會自返回400錯誤狀態碼

4、完成Controller基礎功能

controller功能的實現是大多基於對BLL層的引用,雖然咱們在第3小結中已經實現了數據層和邏輯層的基礎功能,但在Controller實現時仍是發現了不少不合理的地方,因此調整了不少內容,下面咱們依次來看一下

一、UserController

一、首先對Model的層進行了調整,調整了出生日期和性別的默認值

using System;
using System.ComponentModel.DataAnnotations;

namespace BlogSystem.Model
{
    /// <summary>
    /// 用戶
    /// </summary>
    public class User : BaseEntity
    {
        /// <summary>
        /// 帳戶
        /// </summary>
        [Required, StringLength(40)]
        public string Account { get; set; }
        /// <summary>
        /// 密碼
        /// </summary>
        [Required, StringLength(200)]
        public string Password { get; set; }
        /// <summary>
        /// 頭像
        /// </summary>
        public string ProfilePhoto { get; set; }
        /// <summary>
        /// 出生日期
        /// </summary>
        public DateTime BirthOfDate { get; set; } = DateTime.Today;

        /// <summary>
        /// 性別
        /// </summary>
        public Gender Gender { get; set; } = Gender.保密;
        /// <summary>
        /// 用戶等級
        /// </summary>
        public Level Level { get; set; } = Level.普通用戶;
        /// <summary>
        /// 粉絲數
        /// </summary>
        public int FansNum { get; set; }
        /// <summary>
        /// 關注數
        /// </summary>
        public int FocusNum { get; set; }
    }
}

對ViewModel進行了調整,以下:

using System.ComponentModel.DataAnnotations;

namespace BlogSystem.Model.ViewModels
{
    /// <summary>
    /// 用戶註冊
    /// </summary>
    public class RegisterViewModel
    {
        /// <summary>
        /// 帳號
        /// </summary>
        [Required, StringLength(40, MinimumLength = 4)]
        [RegularExpression(@"/^([\u4e00-\u9fa5]{2,4})|([A-Za-z0-9_]{4,16})|([a-zA-Z0-9_\u4e00-\u9fa5]{3,16})$/")]
        public string Account { get; set; }

        /// <summary>
        /// 密碼
        /// </summary>
        [Required, StringLength(20, MinimumLength = 6)]
        public string Password { get; set; }

        /// <summary>
        /// 確認密碼
        /// </summary>
        [Required, Compare(nameof(Password))]
        public string RequirePassword { get; set; }
    }
}
using System.ComponentModel.DataAnnotations;

namespace BlogSystem.Model.ViewModels
{
    /// <summary>
    /// 用戶登陸
    /// </summary>
    public class LoginViewModel
    {
        /// <summary>
        /// 用戶名稱
        /// </summary>
        [Required, StringLength(40, MinimumLength = 4)]
        [RegularExpression(@"/^([\u4e00-\u9fa5]{2,4})|([A-Za-z0-9_]{4,16})|([a-zA-Z0-9_\u4e00-\u9fa5]{3,16})$/")]
        public string Account { get; set; }

        /// <summary>
        /// 用戶密碼
        /// </summary>
        [Required, StringLength(20, MinimumLength = 6), DataType(DataType.Password)]
        public string Password { get; set; }
    }
}
using System.ComponentModel.DataAnnotations;

namespace BlogSystem.Model.ViewModels
{
    /// <summary>
    /// 修改用戶密碼
    /// </summary>
    public class ChangePwdViewModel
    {
        /// <summary>
        /// 舊密碼
        /// </summary>
        [Required]
        public string OldPassword { get; set; }

        /// <summary>
        /// 新密碼
        /// </summary>
        [Required]
        public string NewPassword { get; set; }

        /// <summary>
        /// 確認新密碼
        /// </summary>
        [Required, Compare(nameof(NewPassword))]
        public string RequirePassword { get; set; }
    }
}
using System;
using System.ComponentModel.DataAnnotations;

namespace BlogSystem.Model.ViewModels
{
    /// <summary>
    /// 修改用戶資料
    /// </summary>
    public class ChangeUserInfoViewModel
    {
        /// <summary>
        /// 帳號
        /// </summary>
        public string Account { get; set; }

        /// <summary>
        /// 出生日期
        /// </summary>
        [DataType(DataType.Date)]
        public DateTime BirthOfDate { get; set; }

        /// <summary>
        /// 性別
        /// </summary>
        public Gender Gender { get; set; }
    }
}
namespace BlogSystem.Model.ViewModels
{
    /// <summary>
    /// 用戶詳細信息
    /// </summary>
    public class UserDetailsViewModel
    {
        /// <summary>
        /// 帳號
        /// </summary>
        public string Account { get; set; }
        /// <summary>
        /// 頭像
        /// </summary>
        public string ProfilePhoto { get; set; }
        /// <summary>
        /// 年齡
        /// </summary>
        public int Age { get; set; }
        /// <summary>
        /// 性別
        /// </summary>
        public string Gender { get; set; }
        /// <summary>
        /// 用戶等級
        /// </summary>
        public string Level { get; set; }
        /// <summary>
        /// 粉絲數
        /// </summary>
        public int FansNum { get; set; }
        /// <summary>
        /// 關注數
        /// </summary>
        public int FocusNum { get; set; }
    }
}

二、IBLL和BLL層調整以下:

using System;
using BlogSystem.Model;
using BlogSystem.Model.ViewModels;
using System.Threading.Tasks;

namespace BlogSystem.IBLL
{
    /// <summary>
    /// 用戶服務接口
    /// </summary>
    public interface IUserService : IBaseService<User>
    {
        /// <summary>
        /// 註冊
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        Task<bool> Register(RegisterViewModel model);

        /// <summary>
        /// 登陸成功返回userId
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        Task<Guid> Login(LoginViewModel model);

        /// <summary>
        /// 修改用戶密碼
        /// </summary>
        /// <param name="model"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        Task<bool> ChangePassword(ChangePwdViewModel model, Guid userId);

        /// <summary>
        /// 修改用戶頭像
        /// </summary>
        /// <param name="profilePhoto"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        Task<bool> ChangeUserPhoto(string profilePhoto, Guid userId);

        /// <summary>
        /// 修改用戶信息
        /// </summary>
        /// <param name="model"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        Task<bool> ChangeUserInfo(ChangeUserInfoViewModel model, Guid userId);

        /// <summary>
        /// 使用account獲取用戶信息
        /// </summary>
        /// <param name="account"></param>
        /// <returns></returns>
        Task<UserDetailsViewModel> GetUserInfoByAccount(string account);
    }
}
using BlogSystem.Common.Helpers;
using BlogSystem.IBLL;
using BlogSystem.IDAL;
using BlogSystem.Model;
using BlogSystem.Model.ViewModels;
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace BlogSystem.BLL
{
    public class UserService : BaseService<User>, IUserService
    {
        private readonly IUserRepository _userRepository;

        public UserService(IUserRepository userRepository)
        {
            _userRepository = userRepository;
            BaseRepository = userRepository;
        }

        /// <summary>
        /// 用戶註冊
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        public async Task<bool> Register(RegisterViewModel model)
        {
            //判斷帳戶是否存在
            if (await _userRepository.GetAll().AnyAsync(m => m.Account == model.Account))
            {
                return false;
            }
            var pwd = Md5Helper.Md5Encrypt(model.Password);
            await _userRepository.CreateAsync(new User
            {
                Account = model.Account,
                Password = pwd
            });
            return true;
        }

        /// <summary>
        /// 用戶登陸
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        public async Task<Guid> Login(LoginViewModel model)
        {
            var pwd = Md5Helper.Md5Encrypt(model.Password);
            var user = await _userRepository.GetAll().FirstOrDefaultAsync(m => m.Account == model.Account && m.Password == pwd);
            return user == null ? new Guid() : user.Id;
        }

        /// <summary>
        /// 修改用戶密碼
        /// </summary>
        /// <param name="model"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        public async Task<bool> ChangePassword(ChangePwdViewModel model, Guid userId)
        {
            var oldPwd = Md5Helper.Md5Encrypt(model.OldPassword);
            var user = await _userRepository.GetAll().FirstOrDefaultAsync(m => m.Id == userId && m.Password == oldPwd);
            if (user == null)
            {
                return false;
            }
            var newPwd = Md5Helper.Md5Encrypt(model.NewPassword);
            user.Password = newPwd;
            await _userRepository.EditAsync(user);
            return true;
        }

        /// <summary>
        /// 修改用戶照片
        /// </summary>
        /// <param name="profilePhoto"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        public async Task<bool> ChangeUserPhoto(string profilePhoto, Guid userId)
        {
            var user = await _userRepository.GetAll().FirstOrDefaultAsync(m => m.Id == userId);
            if (user == null) return false;
            user.ProfilePhoto = profilePhoto;
            await _userRepository.EditAsync(user);
            return true;
        }

        /// <summary>
        ///  修改用戶信息
        /// </summary>
        /// <param name="model"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        public async Task<bool> ChangeUserInfo(ChangeUserInfoViewModel model, Guid userId)
        {
            //確保用戶名惟一
            if (await _userRepository.GetAll().AnyAsync(m => m.Account == model.Account))
            {
                return false;
            }
            var user = await _userRepository.GetOneByIdAsync(userId);
            user.Account = model.Account;
            user.Gender = model.Gender;
            user.BirthOfDate = model.BirthOfDate;
            await _userRepository.EditAsync(user);
            return true;
        }

        /// <summary>
        /// 經過帳號名稱獲取用戶信息
        /// </summary>
        /// <param name="account"></param>
        /// <returns></returns>
        public async Task<UserDetailsViewModel> GetUserInfoByAccount(string account)
        {
            if (await _userRepository.GetAll().AnyAsync(m => m.Account == account))
            {
                return await _userRepository.GetAll().Where(m => m.Account == account).Select(m =>
                    new UserDetailsViewModel()
                    {
                        Account = m.Account,
                        ProfilePhoto = m.ProfilePhoto,
                        Age = DateTime.Now.Year - m.BirthOfDate.Year,
                        Gender = m.Gender.ToString(),
                        Level = m.Level.ToString(),
                        FansNum = m.FansNum,
                        FocusNum = m.FocusNum
                    }).FirstAsync();
            }
            return new UserDetailsViewModel();
        }
    }
}

三、Controller層功能的實現大多數須要基於UserId,咱們怎麼獲取UserId呢?還記得Jwt嗎?客戶端發送請求時會在Header中帶上Jwt字符串,咱們能夠解析該字符串獲得用戶名。在自定義的JwtHelper中咱們實現了兩個方法,一個是加密Jwt,一個是解密Jwt,咱們對解密方法進行調整,以下:

/// <summary>
        /// Jwt解密
        /// </summary>
        /// <param name="jwtStr"></param>
        /// <returns></returns>
        public static TokenModelJwt JwtDecrypt(string jwtStr)
        {
            if (string.IsNullOrEmpty(jwtStr) || string.IsNullOrWhiteSpace(jwtStr))
            {
                return new TokenModelJwt();
            }
            jwtStr = jwtStr.Substring(7);//截取前面的Bearer和空格
            var jwtHandler = new JwtSecurityTokenHandler();
            JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(jwtStr);

            jwtToken.Payload.TryGetValue(ClaimTypes.Role, out object level);

            var model = new TokenModelJwt
            {
                UserId = Guid.Parse(jwtToken.Id),
                Level = level == null ? "" : level.ToString()
            };
            return model;
        }

在對應的Contoneller中咱們可使用HttpContext對象獲取Http請求的信息,可是HttpContext的使用是須要註冊的,在StartUp的ConfigureServices中進行註冊,services.AddHttpContextAccessor();以後在對應的控制器構造函數中進行注入IHttpContextAccessor對象便可,以下:

using BlogSystem.Core.Helpers;
using BlogSystem.IBLL;
using BlogSystem.Model.ViewModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Threading.Tasks;

namespace BlogSystem.Core.Controllers
{
    [ApiController]
    [Route("api/user")]
    public class UserController : ControllerBase
    {
        private readonly IUserService _userService;
        private readonly Guid _userId;

        public UserController(IUserService userService, IHttpContextAccessor httpContext)
        {
            _userService = userService ?? throw new ArgumentNullException(nameof(userService));
            var accessor = httpContext ?? throw new ArgumentNullException(nameof(httpContext));
            _userId = JwtHelper.JwtDecrypt(accessor.HttpContext.Request.Headers["Authorization"]).UserId;
        }

        /// <summary>
        /// 用戶註冊
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        [HttpPost(nameof(Register))]
        public async Task<IActionResult> Register(RegisterViewModel model)
        {
            if (!await _userService.Register(model))
            {
                return Ok("用戶已存在");
            }
            //建立成功返回到登陸方法,並返回註冊成功的account
            return CreatedAtRoute(nameof(Login), model.Account);
        }

        /// <summary>
        /// 用戶登陸
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        [HttpPost("Login", Name = nameof(Login))]
        public async Task<IActionResult> Login(LoginViewModel model)
        {
            //判斷帳號密碼是否正確
            var userId = await _userService.Login(model);
            if (userId == Guid.Empty) return Ok("帳號或密碼錯誤!");

            //登陸成功進行jwt加密
            var user = await _userService.GetOneByIdAsync(userId);
            TokenModelJwt tokenModel = new TokenModelJwt { UserId = user.Id, Level = user.Level.ToString() };
            var jwtStr = JwtHelper.JwtEncrypt(tokenModel);
            return Ok(jwtStr);
        }

        /// <summary>
        /// 獲取用戶信息
        /// </summary>
        /// <param name="account"></param>
        /// <returns></returns>
        [HttpGet("{account}")]
        public async Task<IActionResult> UserInfo(string account)
        {
            var list = await _userService.GetUserInfoByAccount(account);
            if (string.IsNullOrEmpty(list.Account))
            {
                return NotFound();
            }
            return Ok(list);
        }

        /// <summary>
        /// 修改用戶密碼
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        [Authorize]
        [HttpPatch("password")]
        public async Task<IActionResult> ChangePassword(ChangePwdViewModel model)
        {
            if (!await _userService.ChangePassword(model, _userId))
            {
                return NotFound("用戶密碼錯誤!");
            }
            return NoContent();
        }

        /// <summary>
        /// 修改用戶照片
        /// </summary>
        /// <param name="profilePhoto"></param>
        /// <returns></returns>
        [Authorize]
        [HttpPatch("photo")]
        public async Task<IActionResult> ChangeUserPhoto([FromBody]string profilePhoto)
        {
            if (!await _userService.ChangeUserPhoto(profilePhoto, _userId))
            {
                return NotFound();
            }
            return NoContent();
        }

        /// <summary>
        ///  修改用戶信息
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        [Authorize]
        [HttpPatch("info")]
        public async Task<IActionResult> ChangeUserInfo(ChangeUserInfoViewModel model)
        {
            if (!await _userService.ChangeUserInfo(model, _userId))
            {
                return Ok("用戶名已存在");
            }
            return NoContent();
        }
    }
}

二、分類Controller

一、調整ViewModel層以下:

using System;
using System.ComponentModel.DataAnnotations;

namespace BlogSystem.Model.ViewModels
{
    /// <summary>
    /// 編輯分類
    /// </summary>
    public class EditCategoryViewModel
    {
        /// <summary>
        /// 分類Id
        /// </summary>
        public Guid CategoryId { get; set; }

        /// <summary>
        /// 分類名稱
        /// </summary>
        [Required, StringLength(30, MinimumLength = 2)]
        public string CategoryName { get; set; }
    }
}
using System;
using System.ComponentModel.DataAnnotations;

namespace BlogSystem.Model.ViewModels
{
    /// <summary>
    /// 分類列表
    /// </summary>
    public class CategoryListViewModel
    {
        /// <summary>
        /// 分類Id
        /// </summary>
        public Guid CategoryId { get; set; }

        /// <summary>
        /// 分類名稱
        /// </summary>
        [Required, StringLength(30, MinimumLength = 2)]
        public string CategoryName { get; set; }
    }
}
using System;
using System.ComponentModel.DataAnnotations;

namespace BlogSystem.Model.ViewModels
{
    /// <summary>
    /// 建立文章分類
    /// </summary>
    public class CreateCategoryViewModel
    {
        /// <summary>
        /// 分類Id
        /// </summary>
        public Guid CategoryId { get; set; }

        /// <summary>
        /// 分類名稱
        /// </summary>
        [Required, StringLength(30, MinimumLength = 2)]
        public string CategoryName { get; set; }
    }
}

二、調整IBLL和BLL層以下:

using BlogSystem.Model;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using BlogSystem.Model.ViewModels;

namespace BlogSystem.IBLL
{
    /// <summary>
    /// 分類服務接口
    /// </summary>
    public interface ICategoryService : IBaseService<Category>
    {
        /// <summary>
        /// 建立分類
        /// </summary>
        /// <param name="categoryName"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        Task<Guid> CreateCategory(string categoryName, Guid userId);

        /// <summary>
        /// 編輯分類
        /// </summary>
        /// <param name="model"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        Task<bool> EditCategory(EditCategoryViewModel model, Guid userId);

        /// <summary>
        /// 經過用戶Id獲取全部分類
        /// </summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        Task<List<CategoryListViewModel>> GetCategoryByUserIdAsync(Guid userId);
    }
}
using BlogSystem.IBLL;
using BlogSystem.IDAL;
using BlogSystem.Model;
using BlogSystem.Model.ViewModels;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace BlogSystem.BLL
{
    public class CategoryService : BaseService<Category>, ICategoryService
    {
        private readonly ICategoryRepository _categoryRepository;

        public CategoryService(ICategoryRepository categoryRepository)
        {
            _categoryRepository = categoryRepository;
            BaseRepository = categoryRepository;
        }

        /// <summary>
        /// 建立分類
        /// </summary>
        /// <param name="categoryName"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        public async Task<Guid> CreateCategory(string categoryName, Guid userId)
        {
            //當前用戶存在該分類名稱則返回
            if (string.IsNullOrEmpty(categoryName) || await _categoryRepository.GetAll()
                .AnyAsync(m => m.UserId == userId && m.CategoryName == categoryName))
            {
                return Guid.Empty;
            }
            //建立成功返回分類Id
            var categoryId = Guid.NewGuid();
            await _categoryRepository.CreateAsync(new Category
            {
                Id = categoryId,
                UserId = userId,
                CategoryName = categoryName
            });
            return categoryId;
        }

        /// <summary>
        ///  編輯分類
        /// </summary>
        /// <param name="model"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        public async Task<bool> EditCategory(EditCategoryViewModel model, Guid userId)
        {
            //用戶不存在該分類則返回
            if (!await _categoryRepository.GetAll().AnyAsync(m => m.UserId == userId && m.Id == model.CategoryId))
            {
                return false;
            }

            await _categoryRepository.EditAsync(new Category
            {
                UserId = userId,
                Id = model.CategoryId,
                CategoryName = model.CategoryName
            });
            return true;
        }

        /// <summary>
        ///  經過用戶Id獲取全部分類
        /// </summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        public Task<List<CategoryListViewModel>> GetCategoryByUserIdAsync(Guid userId)
        {
            return _categoryRepository.GetAll().Where(m => m.UserId == userId).Select(m => new CategoryListViewModel
            {
                CategoryId = m.Id,
                CategoryName = m.CategoryName
            }).ToListAsync();
        }
    }
}

三、調整Controller功能以下:

using BlogSystem.Core.Helpers;
using BlogSystem.IBLL;
using BlogSystem.Model.ViewModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Threading.Tasks;

namespace BlogSystem.Core.Controllers
{
    [ApiController]
    [Route("api/category")]
    public class CategoryController : ControllerBase
    {
        private readonly ICategoryService _categoryService;
        private readonly IArticleService _aeArticleService;
        private readonly Guid _userId;

        public CategoryController(ICategoryService categoryService, IArticleService articleService,
            IHttpContextAccessor httpContext)
        {
            _categoryService = categoryService ?? throw new ArgumentNullException(nameof(categoryService));
            _aeArticleService = articleService ?? throw new ArgumentNullException(nameof(articleService));
            var accessor = httpContext ?? throw new ArgumentNullException(nameof(httpContext));
            _userId = JwtHelper.JwtDecrypt(accessor.HttpContext.Request.Headers["Authorization"]).UserId;
        }

        /// <summary>
        /// 查詢用戶的文章分類
        /// </summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        [HttpGet("{userId}", Name = nameof(GetCategoryByUserId))]
        public async Task<IActionResult> GetCategoryByUserId(Guid userId)
        {
            if (userId == Guid.Empty)
            {
                return NotFound();
            }
            var list = await _categoryService.GetCategoryByUserIdAsync(userId);
            return Ok(list);
        }

        /// <summary>
        /// 新增文章分類
        /// </summary>
        /// <param name="categoryName"></param>
        /// <returns></returns>
        [Authorize]
        [HttpPost]
        public async Task<IActionResult> CreateCategory([FromBody]string categoryName)
        {
            var categoryId = await _categoryService.CreateCategory(categoryName, _userId);
            if (categoryId == Guid.Empty)
            {
                return BadRequest("重複分類!");
            }
            //建立成功返回查詢頁面連接
            var category = new CreateCategoryViewModel { CategoryId = categoryId, CategoryName = categoryName };
            return CreatedAtRoute(nameof(GetCategoryByUserId), new { userId = _userId }, category);
        }

        /// <summary>
        /// 刪除分類
        /// </summary>
        /// <param name="categoryId"></param>
        /// <returns></returns>
        [Authorize]
        [HttpDelete("{categoryId}")]
        public async Task<IActionResult> RemoveCategory(Guid categoryId)
        {
            //確認是否存在,操做人與歸屬人是否一致
            var category = await _categoryService.GetOneByIdAsync(categoryId);
            if (category == null || category.UserId != _userId)
            {
                return NotFound();
            }
            //有文章使用了該分類,沒法刪除
            var data = await _aeArticleService.GetArticlesByCategoryIdAsync(_userId, categoryId);
            if (data.Count > 0)
            {
                return BadRequest("存在使用該分類的文章!");
            }

            await _categoryService.RemoveAsync(categoryId);
            return NoContent();
        }

        /// <summary>
        /// 編輯分類
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        [Authorize]
        [HttpPatch]
        public async Task<IActionResult> EditCategory(EditCategoryViewModel model)
        {
            if (!await _categoryService.EditCategory(model, _userId))
            {
                return NotFound();
            }

            return NoContent();
        }
    }
}

三、文章Controller

一、這裏我在操做時遇到了文章內容亂碼的問題,多是由於數據庫的text格式和輸入格式有衝突,因此這裏我暫時將其改爲了nvarchar(max)的類型

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace BlogSystem.Model
{
    /// <summary>
    /// 文章
    /// </summary>
    public class Article : BaseEntity
    {
        /// <summary>
        /// 文章標題
        /// </summary>
        [Required]
        public string Title { get; set; }
        /// <summary>
        /// 文章內容
        /// </summary>
        [Required]
        public string Content { get; set; }
        /// <summary>
        /// 發表人的Id,用戶表的外鍵
        /// </summary>
        [ForeignKey(nameof(User))]
        public Guid UserId { get; set; }
        public User User { get; set; }
        /// <summary>
        /// 看好人數
        /// </summary>
        public int GoodCount { get; set; }
        /// <summary>
        /// 不看好人數
        /// </summary>
        public int BadCount { get; set; }
        /// <summary>
        /// 文章查看所需等級
        /// </summary>
        public Level Level { get; set; } = Level.普通用戶;
    }
}

ViewModel調整以下:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace BlogSystem.Model.ViewModels
{
    /// <summary>
    /// 建立文章
    /// </summary>
    public class CreateArticleViewModel
    {
        /// <summary>
        /// 文章標題
        /// </summary>
        [Required]
        public string Title { get; set; }

        /// <summary>
        /// 文章內容
        /// </summary>
        [Required]
        public string Content { get; set; }

        /// <summary>
        /// 文章分類
        /// </summary>
        [Required]
        public List<Guid> CategoryIds { get; set; }
    }
}
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace BlogSystem.Model.ViewModels
{
    /// <summary>
    /// 編輯文章
    /// </summary>
    public class EditArticleViewModel
    {
        /// <summary>
        /// 文章Id
        /// </summary>
        public Guid Id { get; set; }

        /// <summary>
        /// 文章標題
        /// </summary>
        [Required]
        public string Title { get; set; }

        /// <summary>
        /// 文章內容
        /// </summary>
        [Required]
        public string Content { get; set; }

        /// <summary>
        /// 文章分類
        /// </summary>
        public List<Guid> CategoryIds { get; set; }
    }
}
using System;

namespace BlogSystem.Model.ViewModels
{
    /// <summary>
    /// 文章列表
    /// </summary>
    public class ArticleListViewModel
    {
        /// <summary>
        /// 文章Id
        /// </summary>
        public Guid ArticleId { get; set; }

        /// <summary>
        /// 文章標題
        /// </summary>
        public string Title { get; set; }

        /// <summary>
        /// 文章內容
        /// </summary>
        public string Content { get; set; }

        /// <summary>
        /// 建立時間
        /// </summary>
        public DateTime CreateTime { get; set; }

        /// <summary>
        /// 帳號
        /// </summary>
        public string Account { get; set; }

        /// <summary>
        /// 頭像
        /// </summary>
        public string ProfilePhoto { get; set; }

    }
}
using System;
using System.Collections.Generic;

namespace BlogSystem.Model.ViewModels
{
    /// <summary>
    /// 文章詳情
    /// </summary>
    public class ArticleDetailsViewModel
    {
        /// <summary>
        /// 文章Id
        /// </summary>
        public Guid Id { get; set; }

        /// <summary>
        /// 文章標題
        /// </summary>
        public string Title { get; set; }

        /// <summary>
        /// 文章內容
        /// </summary>
        public string Content { get; set; }

        /// <summary>
        /// 建立時間
        /// </summary>
        public DateTime CreateTime { get; set; }

        /// <summary>
        /// 做者
        /// </summary>
        public string Account { get; set; }

        /// <summary>
        /// 頭像
        /// </summary>
        public string ProfilePhoto { get; set; }

        /// <summary>
        /// 分類Id
        /// </summary>
        public List<Guid> CategoryIds { get; set; }

        /// <summary>
        /// 分類名稱
        /// </summary>
        public List<string> CategoryNames { get; set; }

        /// <summary>
        /// 看好人數
        /// </summary>
        public int GoodCount { get; set; }
        /// <summary>
        /// 不看好人數
        /// </summary>
        public int BadCount { get; set; }

    }
}

二、調整IBLL和BLL內容,以下

using BlogSystem.Model;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using BlogSystem.Model.ViewModels;

namespace BlogSystem.IBLL
{
    /// <summary>
    /// 評論服務接口
    /// </summary>
    public interface ICommentService : IBaseService<ArticleComment>
    {
        /// <summary>
        /// 添加評論
        /// </summary>
        /// <param name="model"></param>
        /// <param name="articleId"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        Task CreateComment(CreateCommentViewModel model, Guid articleId, Guid userId);

        /// <summary>
        /// 添加普通評論的回覆
        /// </summary>
        /// <param name="model"></param>
        /// <param name="articleId"></param>
        /// <param name="commentId"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        Task CreateReplyComment(CreateApplyCommentViewModel model, Guid articleId, Guid commentId, Guid userId);

        /// <summary>
        /// 添加回複評論的回覆
        /// </summary>
        /// <param name="model"></param>
        /// <param name="articleId"></param>
        /// <param name="commentId"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        Task CreateToReplyComment(CreateApplyCommentViewModel model, Guid articleId, Guid commentId, Guid userId);

        /// <summary>
        /// 經過文章Id獲取全部評論
        /// </summary>
        /// <param name="articleId"></param>
        /// <returns></returns>
        Task<List<CommentListViewModel>> GetCommentsByArticleIdAsync(Guid articleId);

        /// <summary>
        /// 確認回覆型評論是否存在
        /// </summary>
        /// <param name="commentId"></param>
        /// <returns></returns>
        Task<bool> ReplyExistAsync(Guid commentId);
    }
}
using BlogSystem.IBLL;
using BlogSystem.IDAL;
using BlogSystem.Model;
using BlogSystem.Model.ViewModels;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace BlogSystem.BLL
{
    public class CommentService : BaseService<ArticleComment>, ICommentService
    {
        private readonly IArticleCommentRepository _commentRepository;
        private readonly ICommentReplyRepository _commentReplyRepository;

        public CommentService(IArticleCommentRepository commentRepository, ICommentReplyRepository commentReplyRepository)
        {
            _commentRepository = commentRepository;
            BaseRepository = commentRepository;
            _commentReplyRepository = commentReplyRepository;
        }

        /// <summary>
        /// 添加評論
        /// </summary>
        /// <param name="model"></param>
        /// <param name="articleId"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        public async Task CreateComment(CreateCommentViewModel model, Guid articleId, Guid userId)
        {
            await _commentRepository.CreateAsync(new ArticleComment()
            {
                ArticleId = articleId,
                Content = model.Content,
                UserId = userId
            });
        }

        /// <summary>
        ///  添加普通評論的回覆
        /// </summary>
        /// <param name="model"></param>
        /// <param name="articleId"></param>
        /// <param name="commentId"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        public async Task CreateReplyComment(CreateApplyCommentViewModel model, Guid articleId, Guid commentId, Guid userId)
        {
            var comment = await _commentRepository.GetOneByIdAsync(commentId);
            var toUserId = comment.UserId;

            await _commentReplyRepository.CreateAsync(new CommentReply()
            {
                CommentId = commentId,
                ToUserId = toUserId,
                ArticleId = articleId,
                UserId = userId,
                Content = model.Content
            });
        }

        /// <summary>
        /// 添加回復型評論的回覆
        /// </summary>
        /// <param name="model"></param>
        /// <param name="articleId"></param>
        /// <param name="commentId"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        public async Task CreateToReplyComment(CreateApplyCommentViewModel model, Guid articleId, Guid commentId, Guid userId)
        {
            var comment = await _commentReplyRepository.GetOneByIdAsync(commentId);
            var toUserId = comment.UserId;

            await _commentReplyRepository.CreateAsync(new CommentReply()
            {
                CommentId = commentId,
                ToUserId = toUserId,
                ArticleId = articleId,
                UserId = userId,
                Content = model.Content
            });
        }

        /// <summary>
        /// 根據文章Id獲取評論信息
        /// </summary>
        /// <param name="articleId"></param>
        /// <returns></returns>
        public async Task<List<CommentListViewModel>> GetCommentsByArticleIdAsync(Guid articleId)
        {
            //正常評論
            var comment = await _commentRepository.GetAll().Where(m => m.ArticleId == articleId)
                .Include(m => m.User).Select(m => new CommentListViewModel
                {
                    ArticleId = m.ArticleId,
                    UserId = m.UserId,
                    Account = m.User.Account,
                    ProfilePhoto = m.User.ProfilePhoto,
                    CommentId = m.Id,
                    CommentContent = m.Content,
                    CreateTime = m.CreateTime
                }).ToListAsync();

            //回覆型的評論
            var replyComment = await _commentReplyRepository.GetAll().Where(m => m.ArticleId == articleId)
                .Include(m => m.User).Select(m => new CommentListViewModel
                {
                    ArticleId = m.ArticleId,
                    UserId = m.UserId,
                    Account = m.User.Account,
                    ProfilePhoto = m.User.ProfilePhoto,
                    CommentId = m.Id,
                    CommentContent = $"@{m.ToUser.Account}" + Environment.NewLine + m.Content,
                    CreateTime = m.CreateTime
                }).ToListAsync();

            var list = comment.Union(replyComment).OrderByDescending(m => m.CreateTime).ToList();
            return list;
        }

        /// <summary>
        /// 確認回覆型評論是否存在
        /// </summary>
        /// <param name="commentId"></param>
        /// <returns></returns>
        public async Task<bool> ReplyExistAsync(Guid commentId)
        {
            return await _commentReplyRepository.GetAll().AnyAsync(m => m.Id == commentId);
        }
    }
}

三、調整Controller以下:

using BlogSystem.Core.Helpers;
using BlogSystem.IBLL;
using BlogSystem.Model.ViewModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Threading.Tasks;

namespace BlogSystem.Core.Controllers
{
    [ApiController]
    [Route("api/Article/{articleId}/Comment")]
    public class CommentController : ControllerBase
    {
        private readonly ICommentService _commentService;
        private readonly IArticleService _articleService;
        private readonly Guid _userId;

        public CommentController(ICommentService commentService, IArticleService articleService, IHttpContextAccessor httpContext)
        {
            _commentService = commentService ?? throw new ArgumentNullException(nameof(commentService));
            _articleService = articleService ?? throw new ArgumentNullException(nameof(articleService));
            var accessor = httpContext ?? throw new ArgumentNullException(nameof(httpContext));
            _userId = JwtHelper.JwtDecrypt(accessor.HttpContext.Request.Headers["Authorization"]).UserId;
        }

        /// <summary>
        /// 添加評論
        /// </summary>
        /// <param name="articleId"></param>
        /// <param name="model"></param>
        /// <returns></returns>
        [Authorize]
        [HttpPost]
        public async Task<IActionResult> CreateComment(Guid articleId, CreateCommentViewModel model)
        {
            if (!await _articleService.ExistsAsync(articleId))
            {
                return NotFound();
            }

            await _commentService.CreateComment(model, articleId, _userId);
            return CreatedAtRoute(nameof(GetComments), new { articleId }, model);
        }

        /// <summary>
        /// 添加回復型評論
        /// </summary>
        /// <param name="articleId"></param>
        /// <param name="commentId"></param>
        /// <param name="model"></param>
        /// <returns></returns>
        [Authorize]
        [HttpPost("reply")]
        public async Task<IActionResult> CreateReplyComment(Guid articleId, Guid commentId, CreateApplyCommentViewModel model)
        {
            if (!await _articleService.ExistsAsync(articleId))
            {
                return NotFound();
            }
            //回覆的是正常評論
            if (await _commentService.ExistsAsync(commentId))
            {
                await _commentService.CreateReplyComment(model, articleId, commentId, _userId);
                return CreatedAtRoute(nameof(GetComments), new { articleId }, model);
            }
            //須要考慮回覆的是正常評論仍是回覆型評論
            if (await _commentService.ReplyExistAsync(commentId))
            {
                await _commentService.CreateToReplyComment(model, articleId, commentId, _userId);
                return CreatedAtRoute(nameof(GetComments), new { articleId }, model);
            }
            return NotFound();
        }

        /// <summary>
        /// 獲取評論
        /// </summary>
        /// <param name="articleId"></param>
        /// <returns></returns>
        [HttpGet(Name = nameof(GetComments))]
        public async Task<IActionResult> GetComments(Guid articleId)
        {
            if (!await _articleService.ExistsAsync(articleId))
            {
                return NotFound();
            }

            var list = await _commentService.GetCommentsByArticleIdAsync(articleId);
            return Ok(list);
        }
    }
}

四、評論Controller

一、這裏發現評論回覆表CommentReply設計存在問題,由於回覆也有多是針對回覆型評論的,因此調整以後須要使用EF的遷移命令更行數據庫,以下:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace BlogSystem.Model
{
    /// <summary>
    /// 評論回覆表
    /// </summary>
    public class CommentReply : BaseEntity
    {
        /// <summary>
        /// 回覆指向的評論Id
        /// </summary>
        public Guid CommentId { get; set; }
        /// <summary>
        /// 回覆指向的用戶Id
        /// </summary>
        [ForeignKey(nameof(ToUser))]
        public Guid ToUserId { get; set; }
        public User ToUser { get; set; }
        /// <summary>
        /// 文章ID
        /// </summary>
        [ForeignKey(nameof(Article))]
        public Guid ArticleId { get; set; }
        public Article Article { get; set; }
        /// <summary>
        /// 用戶Id
        /// </summary>
        [ForeignKey(nameof(User))]
        public Guid UserId { get; set; }
        public User User { get; set; }
        /// <summary>
        /// 回覆的內容
        /// </summary>
        [Required, StringLength(800)]
        public string Content { get; set; }
    }
}

調整ViewModel以下,有人發現評論和回覆的ViewModel相同,爲何不使用一個?是爲了應對後續兩張表欄位不一樣時,須要調整的狀況

using System;
using System.ComponentModel.DataAnnotations;

namespace BlogSystem.Model.ViewModels
{
    /// <summary>
    /// 文章評論
    /// </summary>
    public class CreateCommentViewModel
    {
        /// <summary>
        /// 評論內容
        /// </summary>
        [Required, StringLength(800)]
        public string Content { get; set; }
    }
}
using System.ComponentModel.DataAnnotations;

namespace BlogSystem.Model.ViewModels
{
    /// <summary>
    /// 添加回復型評論
    /// </summary>
    public class CreateApplyCommentViewModel
    {
        /// <summary>
        /// 回覆的內容
        /// </summary>
        [Required, StringLength(800)]
        public string Content { get; set; }
    }
}
using System;

namespace BlogSystem.Model.ViewModels
{
    /// <summary>
    /// 文章評論列表
    /// </summary>
    public class CommentListViewModel
    {
        /// <summary>
        /// 文章Id
        /// </summary>
        public Guid ArticleId { get; set; }

        /// <summary>
        /// 用戶Id
        /// </summary>
        public Guid UserId { get; set; }

        /// <summary>
        /// 帳號
        /// </summary>
        public string Account { get; set; }

        /// <summary>
        /// 頭像
        /// </summary>
        public string ProfilePhoto { get; set; }

        /// <summary>
        /// 評論Id
        /// </summary>
        public Guid CommentId { get; set; }

        /// <summary>
        /// 評論內容
        /// </summary>
        public string CommentContent { get; set; }

        /// <summary>
        /// 建立時間
        /// </summary>
        public DateTime CreateTime { get; set; }

    }
}

二、調整IBLL和BLL以下:

using BlogSystem.Model;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using BlogSystem.Model.ViewModels;

namespace BlogSystem.IBLL
{
    /// <summary>
    /// 評論服務接口
    /// </summary>
    public interface ICommentService : IBaseService<ArticleComment>
    {
        /// <summary>
        /// 添加評論
        /// </summary>
        /// <param name="model"></param>
        /// <param name="articleId"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        Task CreateComment(CreateCommentViewModel model, Guid articleId, Guid userId);

        /// <summary>
        /// 添加普通評論的回覆
        /// </summary>
        /// <param name="model"></param>
        /// <param name="articleId"></param>
        /// <param name="commentId"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        Task CreateReplyComment(CreateApplyCommentViewModel model, Guid articleId, Guid commentId, Guid userId);

        /// <summary>
        /// 添加回複評論的回覆
        /// </summary>
        /// <param name="model"></param>
        /// <param name="articleId"></param>
        /// <param name="commentId"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        Task CreateToReplyComment(CreateApplyCommentViewModel model, Guid articleId, Guid commentId, Guid userId);

        /// <summary>
        /// 經過文章Id獲取全部評論
        /// </summary>
        /// <param name="articleId"></param>
        /// <returns></returns>
        Task<List<CommentListViewModel>> GetCommentsByArticleIdAsync(Guid articleId);

        /// <summary>
        /// 確認回覆型評論是否存在
        /// </summary>
        /// <param name="commentId"></param>
        /// <returns></returns>
        Task<bool> ReplyExistAsync(Guid commentId);
    }
}
using BlogSystem.IBLL;
using BlogSystem.IDAL;
using BlogSystem.Model;
using BlogSystem.Model.ViewModels;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace BlogSystem.BLL
{
    public class CommentService : BaseService<ArticleComment>, ICommentService
    {
        private readonly IArticleCommentRepository _commentRepository;
        private readonly ICommentReplyRepository _commentReplyRepository;

        public CommentService(IArticleCommentRepository commentRepository, ICommentReplyRepository commentReplyRepository)
        {
            _commentRepository = commentRepository;
            BaseRepository = commentRepository;
            _commentReplyRepository = commentReplyRepository;
        }

        /// <summary>
        /// 添加評論
        /// </summary>
        /// <param name="model"></param>
        /// <param name="articleId"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        public async Task CreateComment(CreateCommentViewModel model, Guid articleId, Guid userId)
        {
            await _commentRepository.CreateAsync(new ArticleComment()
            {
                ArticleId = articleId,
                Content = model.Content,
                UserId = userId
            });
        }

        /// <summary>
        ///  添加普通評論的回覆
        /// </summary>
        /// <param name="model"></param>
        /// <param name="articleId"></param>
        /// <param name="commentId"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        public async Task CreateReplyComment(CreateApplyCommentViewModel model, Guid articleId, Guid commentId, Guid userId)
        {
            var comment = await _commentRepository.GetOneByIdAsync(commentId);
            var toUserId = comment.UserId;

            await _commentReplyRepository.CreateAsync(new CommentReply()
            {
                CommentId = commentId,
                ToUserId = toUserId,
                ArticleId = articleId,
                UserId = userId,
                Content = model.Content
            });
        }

        /// <summary>
        /// 添加回復型評論的回覆
        /// </summary>
        /// <param name="model"></param>
        /// <param name="articleId"></param>
        /// <param name="commentId"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        public async Task CreateToReplyComment(CreateApplyCommentViewModel model, Guid articleId, Guid commentId, Guid userId)
        {
            var comment = await _commentReplyRepository.GetOneByIdAsync(commentId);
            var toUserId = comment.UserId;

            await _commentReplyRepository.CreateAsync(new CommentReply()
            {
                CommentId = commentId,
                ToUserId = toUserId,
                ArticleId = articleId,
                UserId = userId,
                Content = model.Content
            });
        }

        /// <summary>
        /// 根據文章Id獲取評論信息
        /// </summary>
        /// <param name="articleId"></param>
        /// <returns></returns>
        public async Task<List<CommentListViewModel>> GetCommentsByArticleIdAsync(Guid articleId)
        {
            //正常評論
            var comment = await _commentRepository.GetAll().Where(m => m.ArticleId == articleId)
                .Include(m => m.User).Select(m => new CommentListViewModel
                {
                    ArticleId = m.ArticleId,
                    UserId = m.UserId,
                    Account = m.User.Account,
                    ProfilePhoto = m.User.ProfilePhoto,
                    CommentId = m.Id,
                    CommentContent = m.Content,
                    CreateTime = m.CreateTime
                }).ToListAsync();

            //回覆型的評論
            var replyComment = await _commentReplyRepository.GetAll().Where(m => m.ArticleId == articleId)
                .Include(m => m.User).Select(m => new CommentListViewModel
                {
                    ArticleId = m.ArticleId,
                    UserId = m.UserId,
                    Account = m.User.Account,
                    ProfilePhoto = m.User.ProfilePhoto,
                    CommentId = m.Id,
                    CommentContent = $"@{m.ToUser.Account}" + Environment.NewLine + m.Content,
                    CreateTime = m.CreateTime
                }).ToListAsync();

            var list = comment.Union(replyComment).OrderByDescending(m => m.CreateTime).ToList();
            return list;
        }

        /// <summary>
        /// 確認回覆型評論是否存在
        /// </summary>
        /// <param name="commentId"></param>
        /// <returns></returns>
        public async Task<bool> ReplyExistAsync(Guid commentId)
        {
            return await _commentReplyRepository.GetAll().AnyAsync(m => m.Id == commentId);
        }
    }
}

三、調整Controller以下:

using BlogSystem.Core.Helpers;
using BlogSystem.IBLL;
using BlogSystem.Model.ViewModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Threading.Tasks;

namespace BlogSystem.Core.Controllers
{
    [ApiController]
    [Route("api/Article/{articleId}/Comment")]
    public class CommentController : ControllerBase
    {
        private readonly ICommentService _commentService;
        private readonly IArticleService _articleService;
        private readonly Guid _userId;

        public CommentController(ICommentService commentService, IArticleService articleService, IHttpContextAccessor httpContext)
        {
            _commentService = commentService ?? throw new ArgumentNullException(nameof(commentService));
            _articleService = articleService ?? throw new ArgumentNullException(nameof(articleService));
            var accessor = httpContext ?? throw new ArgumentNullException(nameof(httpContext));
            _userId = JwtHelper.JwtDecrypt(accessor.HttpContext.Request.Headers["Authorization"]).UserId;
        }

        /// <summary>
        /// 添加評論
        /// </summary>
        /// <param name="articleId"></param>
        /// <param name="model"></param>
        /// <returns></returns>
        [Authorize]
        [HttpPost]
        public async Task<IActionResult> CreateComment(Guid articleId, CreateCommentViewModel model)
        {
            if (!await _articleService.ExistsAsync(articleId))
            {
                return NotFound();
            }

            await _commentService.CreateComment(model, articleId, _userId);
            return CreatedAtRoute(nameof(GetComments), new { articleId }, model);
        }

        /// <summary>
        /// 添加回復型評論
        /// </summary>
        /// <param name="articleId"></param>
        /// <param name="commentId"></param>
        /// <param name="model"></param>
        /// <returns></returns>
        [Authorize]
        [HttpPost("reply")]
        public async Task<IActionResult> CreateReplyComment(Guid articleId, Guid commentId, CreateApplyCommentViewModel model)
        {
            if (!await _articleService.ExistsAsync(articleId))
            {
                return NotFound();
            }
            //回覆的是正常評論
            if (await _commentService.ExistsAsync(commentId))
            {
                await _commentService.CreateReplyComment(model, articleId, commentId, _userId);
                return CreatedAtRoute(nameof(GetComments), new { articleId }, model);
            }
            //須要考慮回覆的是正常評論仍是回覆型評論
            if (await _commentService.ReplyExistAsync(commentId))
            {
                await _commentService.CreateToReplyComment(model, articleId, commentId, _userId);
                return CreatedAtRoute(nameof(GetComments), new { articleId }, model);
            }
            return NotFound();
        }

        /// <summary>
        /// 獲取評論
        /// </summary>
        /// <param name="articleId"></param>
        /// <returns></returns>
        [HttpGet(Name = nameof(GetComments))]
        public async Task<IActionResult> GetComments(Guid articleId)
        {
            if (!await _articleService.ExistsAsync(articleId))
            {
                return NotFound();
            }

            var list = await _commentService.GetCommentsByArticleIdAsync(articleId);
            return Ok(list);
        }
    }
}

該項目源碼已上傳至GitHub,有須要的朋友能夠下載使用:https://github.com/Jscroop/BlogSystem

本章完~


本人知識點有限,若文中有錯誤的地方請及時指正,方便你們更好的學習和交流。

本文部份內容參考了網絡上的視頻內容和文章,僅爲學習和交流,視頻地址以下:

solenovex,ASP.NET Core 3.x 入門視頻

solenovex,使用 ASP.NET Core 3.x 構建 RESTful Web API

老張的哲學,系列教程一目錄:.netcore+vue 先後端分離

聲明
相關文章
相關標籤/搜索