上一篇我们使用Swagger添加了接口文档,使用Jwt完成了授权,本章我们简答介绍一下RESTful风格的WebAPI开发过程中涉及到的一些知识点,并完善一下尚未完成的功能


.NET下的WebAPI是一种无限接近RESTful风格的框架,RESTful风格它有着自己的一套理论,它的大概意思就是说使用标准的Http方法,将Web系统的服务抽象为资源。稍微具体一点的介绍可以查看阮一峰的这篇文章RESTful API最佳实践。我们这里就分几部分介绍一下构建RESTful API需要了解的基础知识

注:本章介绍部分的内容大多是基于solenovex的使用 ASP.NET Core 3.x 构建 RESTful Web API视频内容的整理,若想进一步了解相关知识,请查看原视频

一、HTTP方法

1、什么是HTTP方法

HTTP方法是对Web服务器的说明,说明如何处理请求的资源。HTTP1.0 定义了三种请求方法: GET, POST 和 HEAD方法;HTTP1.1 新增了六种请求方法:OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT 方法。

2、常用的HTTP方法

  1. GET:通常用来获取资源;GET请求会返回请求路径对应的资源,但它分两种情况:

    ①获取单个资源,通过使用URL的形式带上唯一标识,示例:api/Articles/{ArticleId};

    ②获取集合资源中符合条件的资源,会通过QueryString的形式在URL后面添加?查询条件作为筛选条件,示例:api/Articles?title=WebAPI

  2. POST:通常用来创建资源;POST的参数会放在请求body中,POST请求应该返回新创建的资源以及可以获取该资源的唯一标识URL,示例:api/Articles/{新增的ArticleId}

  3. DELETE:通常用来移除/删除对应路径的资源;通过使用URL的形式带上唯一标识,或者和GET一样使用QueryString,处理完成后通常不会返回资源,只返回状态码204,示例:api/Articles/{ArticleId};

  4. PUT:通常用来完全替换对应路径的资源信息;POST的参数会放在请求body中,且为一个完整对象,示例:api/Articles/{ArticleId};与此同时,它分两类情况:

    ①对应的资源不存在,则新增对应的资源,后续处理和POST一样;

    ②对应的资源存在,则替换对应的资源,处理完成不需要返回信息,只返回状态码204

  5. PATCH:通常用来更新对应路径资源的局部信息;PATCH的参数会放在请求头中,处理完成后通常不会返回资源,只返回状态码204,示例:api/Articles/{ArticleId};

    综上:给出一张图例,来自solenovex,使用 ASP.NET Core 3.x 构建 RESTful Web API

3、安全性和幂等性

安全性是指方法执行后不会改变资源的表述;幂等性是指方法无论执行多少次都会得到相同的结果

二、状态码相关

1、状态码

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:表示服务器发生了错误,客户端无法处理

2、错误与故障

基于HTTP请求状态码,我们需要了解一下错误和故障的区别

错误:API正常工作,但是API用户请求传递的数据不合理,所以请求被拒绝。对应4xx错误;

故障:API工作异常,API用户请求是合理的,但是API无法响应。对应5xx错误

3、故障处理

我们可以在非开发环境进行如下配置,以确保生产环境异常时能查看到相关异常说明,通常这里会写入日志记录异常,我们会在后面的章节添加日志功能,这里先修改如下:

三、WebAPI相关

1、内容协商

  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状态码;

2、内容协商设置

ASP.NET Core目前的设置是仅返回Json格式信息,不支持XML;如果请求的是XML或没有设置,它同样会返回Json;如果希望关闭此项设置,即不存在返回406状态码,可以在Controller服务注册时添加如下设置;

而如果希望支持输出和输入都支持XML格式,可以配置如下:

3、对象绑定

客户端数据可以通过多种方式传递给API,Binding Source Attribute则是负责处理绑定的对象,它会为告知Model的绑定引擎,从哪里可以找到绑定源,Binding Source Attribute一共有六种绑定数据来源,如下:

  • FromBody:从请求的Body中获取绑定数据
  • FromForm:从请求的Body中的form获取绑定数据
  • FromHeader:从请求的Header中获取绑定数据
  • FromQuery:从QueryString参数中获取绑定数据
  • FromRoute:从当前请求的路由中获取绑定数据
  • FromService:从作为Action参数而注入的服务中获取绑定数据

4、ApiController特性

ASP.NET Core WebAPI中我们通常会使用[ApiController]特性来修饰我们的Controller对象,该特性为了更好的适应API方法,对上述分类规则进行了修改,修改如下:

  • FormBody:通常用来推断复杂类型的参数
  • FromForm:通常用来推断IFormFilr和IFormFileColllection类型的Action参数,即文件上传相对应的参数
  • FromRoute:通常用来推断Action的参数名和路由模板中的参数名一致的情况
  • FromQuery:用来推断其他的Action参数

一些特殊情况,需要手动指明对象的来源,如在HttpGet方法中,查询参数是一个复杂的类类型,则ApiController对象会默认绑定源为请求body, 这时候就需要手动指明绑定源为FromQuery;

5、输入验证

通常我们会使用一些验证规则对客户端的输入内容进行限制,像用户名不能包含特殊字符,用户名长度等

1、验证规则

WebAPI中内置了一组名为Data Annotations的验证规则,像之前我们添加的[Required],[StringLength...]都属于这个类型。或者我们可以自定义一个类,实现IValidatableObject接口,对多个字段进行限制;当然我们也可以针对类或者是属性自定义一些验证规则,需要继承ValidationAttribute类重写IsValid方法

2、验证检查

检查时会使用ModelState对象,它是一个字典,包含model的状态和model的绑定验证信息;同时它还包含针对每个提交的属性值的错误信息的集合,每当有请求进来的时候,定义好的验证规则就会被检查。如果验证不通过,ModelState.IsValid()就会返回false;

3、报告验证错误

如发生验证错误,应当返回Unprocessable Entity 422错误,并在响应的body中包含验证错误信息;ASP.NET Core已经定义好了这部分内容,当Controller使用[ApiController]属性进行注解时,如果遇到错误,那么将会自返回400错误状态码

四、完成Controller基础功能

controller功能的实现是大多基于对BLL层的引用,虽然我们在第3小结中已经实现了数据层和逻辑层的基础功能,但在Controller实现时还是发现了很多不合理的地方,所以调整了很多内容,下面我们依次来看一下

1、UserController

1、首先对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; }
}
}

2、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();
}
}
}

3、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();
}
}
}

2、分类Controller

1、调整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; }
}
}

2、调整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();
}
}
}

3、调整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();
}
}
}

3、文章Controller

1、这里我在操作时遇到了文章内容乱码的问题,可能是因为数据库的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; } }
}

2、调整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);
}
}
}

3、调整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);
}
}
}

4、评论Controller

1、这里发现评论回复表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; } }
}

2、调整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);
}
}
}

3、调整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 前后端分离

声明

学习ASP.NET Core(06)-Restful与WebAPI的更多相关文章

  1. C#编译器优化那点事 c# 如果一个对象的值为null,那么它调用扩展方法时为甚么不报错 webAPI 控制器(Controller)太多怎么办? .NET MVC项目设置包含Areas中的页面为默认启动页 (五)Net Core使用静态文件 学习ASP.NET Core Razor 编程系列八——并发处理

    C#编译器优化那点事   使用C#编写程序,给最终用户的程序,是需要使用release配置的,而release配置和debug配置,有一个关键区别,就是release的编译器优化默认是启用的.优化代码 ...

  2. 使用ASP.NET Core构建RESTful API的技术指南

    译者荐语:利用周末的时间,本人拜读了长沙.NET技术社区翻译的技术标准<微软RESTFul API指南>,打算按照步骤写一个完整的教程,后来无意中看到了这篇文章,与我要写的主题有不少相似之 ...

  3. 初次学习asp.net core的心得

    初次学习Asp.Net Core方面的东西,虽然研究的还不是很深,今天主要是学习了一下Asp.Net Core WebAPI项目的使用,发现与Asp.Net WebAPI项目还是有很多不同.不同点包含 ...

  4. 学习ASP.NET Core,你必须了解无处不在的“依赖注入”

    ASP.NET Core的核心是通过一个Server和若干注册的Middleware构成的管道,不论是管道自身的构建,还是Server和Middleware自身的实现,以及构建在这个管道的应用,都需要 ...

  5. 学习ASP.NET Core Razor 编程系列二——添加一个实体

    在Razor页面应用程序中添加一个实体 在本篇文章中,学习添加用于管理数据库中的书籍的实体类.通过实体框架(EF Core)使用这些类来处理数据库.EF Core是一个对象关系映射(ORM)框架,它简 ...

  6. 学习ASP.NET Core Razor 编程系列四——Asp.Net Core Razor列表模板页面

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  7. 学习ASP.NET Core Razor 编程系列五——Asp.Net Core Razor新建模板页面

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  8. 学习ASP.NET Core Razor 编程系列六——数据库初始化

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  9. 学习ASP.NET Core Razor 编程系列七——修改列表页面

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

随机推荐

  1. python爬虫实战之爬取智联职位信息和博客文章信息

    1.python爬取招聘信息 简单爬取智联招聘职位信息 # !/usr/bin/env python # -*-coding:utf-8-*- """ @Author  ...

  2. python小白入门之导入指定的模块

    在python中导入模块是通过关键字import进行导入的,下面演示一下,模块的导入,指定模块别名,指定函数别名,调用模块中所有的函数运行结果:  1.模块的导入Study.py文件里面的内容是:形式 ...

  3. CG-CTF(4)

    CG-CTF https://cgctf.nuptsast.com/challenges#Web 续上~ 第十六题:bypass again 代码分析: 当a不等于b,且a和b的md5值相同时,才会返 ...

  4. [C语言] 获得 pwd 的几种函数

    _getcwd() GetCurrentDirectory GetModuleFileName main函数参数 argv[0] // crt_getcwd.c // This program pla ...

  5. 一篇文章带你编写10种语言HelloWorld

    0,编程语言排行榜 计算机编程语言众多,世界上大概有600 多种编程语言,但是流行的也就几十种.我们来看下编程语言排行榜,下面介绍两种语言排行榜. Ⅰ TIOBE 指数 该指数每月更新一次,它监控了近 ...

  6. Spring Boot JPA中java 8 的应用

    文章目录 Optional Stream API CompletableFuture Spring Boot JPA中java 8 的应用 上篇文章中我们讲到了如何在Spring Boot中使用JPA ...

  7. Intellij-IDEA-maven+springMVC+mybatis整合

    2019独角兽企业重金招聘Python工程师标准>>> GitHub地址 https://github.com/Ethel731/WebProjectDemo 前言 之前都是在已经建 ...

  8. 一维滑动窗口(SlidingWindow)

    滑动窗口(Sliding Window)问题经常使用快慢指针(slow, fast pointer)[0, slow) 的区域为滑动窗口已经探索过的区域[slow, fast]的区域为滑动窗口正在探索 ...

  9. Ubuntu开机黑屏解决办法

    联想笔记本通过虚拟机安装Ubuntu12.04后,开机黑屏.这个问题和NVIDIA显卡有关.网上有人提到更改/etc/default/grub文件,可是我通过root身份也无法很好地修改该文件,遂放弃 ...

  10. PLAI那些事_06 FAE

    没有了with表达,with,exp,body的id换成exp的lambda函数,从而可以没有with来进行实现.即,{with {id exp} body}换成了{{fun {id} body} e ...