上一篇咱们使用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
HTTP方法是对Web服务器的说明,说明如何处理请求的资源。HTTP1.0 定义了三种请求方法: GET, POST 和 HEAD方法;HTTP1.1 新增了六种请求方法:OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT 方法。github
GET:一般用来获取资源;GET请求会返回请求路径对应的资源,但它分两种状况:web
①获取单个资源,经过使用URL的形式带上惟一标识,示例:api/Articles/{ArticleId};数据库
②获取集合资源中符合条件的资源,会经过QueryString的形式在URL后面添加?查询条件做为筛选条件,示例:api/Articles?title=WebAPIjson
POST:一般用来建立资源;POST的参数会放在请求body中,POST请求应该返回新建立的资源以及能够获取该资源的惟一标识URL,示例:api/Articles/{新增的ArticleId}c#
DELETE:一般用来移除/删除对应路径的资源;经过使用URL的形式带上惟一标识,或者和GET同样使用QueryString,处理完成后一般不会返回资源,只返回状态码204,示例:api/Articles/{ArticleId};后端
PUT:一般用来彻底替换对应路径的资源信息;POST的参数会放在请求body中,且为一个完整对象,示例:api/Articles/{ArticleId};与此同时,它分两类状况:api
①对应的资源不存在,则新增对应的资源,后续处理和POST同样;
②对应的资源存在,则替换对应的资源,处理完成不须要返回信息,只返回状态码204
PATCH:一般用来更新对应路径资源的局部信息;PATCH的参数会放在请求头中,处理完成后一般不会返回资源,只返回状态码204,示例:api/Articles/{ArticleId};
综上:给出一张图例,来自solenovex,使用 ASP.NET Core 3.x 构建 RESTful Web API
安全性是指方法执行后不会改变资源的表述;幂等性是指方法不管执行多少次都会获得相同的结果
HTTP状态码是表示Web服务器响应状态的3位数字代码。一般会以第一位数字为区分
1xx:属于信息性的状态码,WebAPI不使用
2xx:表示请求执行成功,经常使用的有200—请求成功,201—建立成功,204—请求成功无返回信息,如删除
3xx:用于跳转,如告诉搜索引擎,网址已改变。大多数WebAPI不须要使用这类状态码
4xx:表示客户端错误
5xx:表示服务器错误
基于HTTP请求状态码,咱们须要了解一下错误和故障的区别
错误:API正常工做,可是API用户请求传递的数据不合理,因此请求被拒绝。对应4xx错误;
故障:API工做异常,API用户请求是合理的,可是API没法响应。对应5xx错误
咱们能够在非开发环境进行以下配置,以确保生产环境异常时能查看到相关异常说明,一般这里会写入日志记录异常,咱们会在后面的章节添加日志功能,这里先修改以下:
PS:若是没有设置请求格式,就返回默认格式;而若是请求的格式不存在,则应当返回406状态码;
ASP.NET Core目前的设置是仅返回Json格式信息,不支持XML;若是请求的是XML或没有设置,它一样会返回Json;若是但愿关闭此项设置,即不存在返回406状态码,能够在Controller服务注册时添加以下设置;
而若是但愿支持输出和输入都支持XML格式,能够配置以下:
客户端数据能够经过多种方式传递给API,Binding Source Attribute则是负责处理绑定的对象,它会为告知Model的绑定引擎,从哪里能够找到绑定源,Binding Source Attribute一共有六种绑定数据来源,以下:
ASP.NET Core WebAPI中咱们一般会使用[ApiController]特性来修饰咱们的Controller对象,该特性为了更好的适应API方法,对上述分类规则进行了修改,修改以下:
一些特殊状况,须要手动指明对象的来源,如在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错误状态码
controller功能的实现是大多基于对BLL层的引用,虽然咱们在第3小结中已经实现了数据层和逻辑层的基础功能,但在Controller实现时仍是发现了不少不合理的地方,因此调整了不少内容,下面咱们依次来看一下
一、首先对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(); } } }
一、调整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(); } } }
一、这里我在操做时遇到了文章内容乱码的问题,多是由于数据库的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); } } }
一、这里发现评论回复表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 先后端分离