篇(17)-Asp.Net Core入门实战-文章管理之文章类别管理(Linq子查询) ...

打印 上一主题 下一主题

主题 781|帖子 781|积分 2345

篇(17)-Asp.Net Core入门实战-文章管理之文章类别的管理
如果要做一个CMS系统,那么文章管理算是入门,文章管理附带一个类别管理,用来对文章进行类别区分。所以,本章简单讲一些类别管理,这也是一个数据操作。
(1).文章类别Sql表的建立
  1. CREATE TABLE [dbo].[ArticleCategory](
  2. [Id] [int] IDENTITY(1,1) NOT NULL,
  3. [Title] [varchar](128) NOT NULL,
  4. [ParentId] [int] NOT NULL,
  5. [ClassList] [varchar](128) NULL,
  6. [ClassLayer] [int] NULL,
  7. [Sort] [int] NOT NULL,
  8. [ImageUrl] [varchar](128) NULL,
  9. [SeoTitle] [varchar](128) NULL,
  10. [SeoKeywords] [varchar](256) NULL,
  11. [SeoDescription] [varchar](512) NULL,
  12. [IsDeleted] [bit] NOT NULL,
  13. CONSTRAINT [PK_ARTICLECATEGORY] PRIMARY KEY NONCLUSTERED
  14. (
  15. [Id] ASC
  16. )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
  17. ) ON [PRIMARY]
  18. GO
复制代码
 
(2).文章类别的视图

(2.1)Create视图

  1. @{ ViewData["Title"] = "新建文章分类"; }
  2. @model ArticleCategory
  3. <form action="/ArticleCategory/Create" method="post">
  4. @Html.AntiForgeryToken()
  5. <label asp-for="ParentId">父类别</label>
  6. <label name="Parent_DisplayName" id="Parent_DisplayName">@ViewData["DisplayName"]</label>
  7. <input type="hidden" asp-for="ParentId" id="ParentId" name="ParentId" value="@ViewData["ParentId"]" />
  8. <label asp-for="Title">类别名称</label>
  9. <input type="text" asp-for="Title" name="Title" placeholder="请输入类别名">
  10. <label asp-for="Sort">排序</label>
  11. <input type="text" asp-for="Sort" name="Sort" placeholder="排序">
  12. <button type="submit">确定</button>
  13. <button type="reset">重置</button>
  14. </form>
复制代码
 
(2.2)Edit视图
  1. @{ ViewData["Title"] = "编辑菜单"; }
  2. @model ArticleCategory
  3. <form action="/ArticleCategory/Edit" method="post">
  4. @Html.AntiForgeryToken()
  5. <label asp-for="ParentId">父菜单</label>
  6. <input type="text" asp-for="ParentId" name="ParentId" />
  7. <input type="hidden" asp-for="Id" />
  8. <label asp-for="Title">类别名成</label>
  9. <input type="text" asp-for="Title" name="Title" placeholder="类别名成">
  10. <label asp-for="Sort">排序</label>
  11. <input type="text" asp-for="Sort" name="Sort" placeholder="排序">
  12. <button type="submit">确定</button>
  13. <button type="reset">重置</button>
  14. </form>
复制代码
 
(2.3)Index列表视图

单纯的列表在现实父类名称时,显示的还是Id值,看第一步的表结构可以发现,这个字段是ParentId,我们得显示成名称才行。
  1. @using Humanizer;
  2. @using RjWebCms.Db;
  3. @using RjWebCms.Models.Articles;
  4. @using RjWebCms.Common;
  5. @model PaginatedList<ArticleCategoryView>
  6. @{
  7. ViewData["Title"] = "文章类别列表";
  8. }
  9. @section Scripts{
  10. }
  11. @ViewData["Title"]
  12. @Html.AntiForgeryToken()
  13. <form asp-action="Index" method="get">
  14. <table>
  15. <tr><td><a asp-controller="ArticleCategory" asp-action="Create">添加</a></td></tr>
  16. <tr>
  17. <td>查询关键词:<input type="text" name="SearchString" value="@ViewData["CurrentFilter"]" /></td>
  18. <td><input type="submit" value="查询" /></td>
  19. <td><a asp-action="Index">Back</a></td>
  20. <td><a asp-action="DeleteAll">批量删除</a></td>
  21. </tr>
  22. </table>
  23. </form>
  24. <table class="table table-hover">
  25. <thead>
  26. <tr>
  27. <td>✔</td>
  28. <td><a asp-action="Index" asp-route-sortOrder="@ViewData["NameSortParm"]" asp-route-currentFilter="@ViewData["CurrentFilter"]">类别名称</a></td>
  29. <td>父类名称</td>
  30. <td><a asp-action="Index" asp-route-sortOrder="@ViewData["DateSortParm"]" asp-route-currentFilter="@ViewData["CurrentFilter"]">序号</a></td>
  31. <td>操作</td>
  32. </tr>
  33. @foreach (var item in Model)
  34. {
  35. if (item.ParentId == 0)
  36. {
  37. <tr>
  38. <td><input type="checkbox" class="done-checkbox" name="chk_ids" value="@item.Id"></td>
  39. <td>@item.Title</td>
  40. <td>\</td>
  41. <td>@item.Sort</td>
  42. <td>
  43. <a asp-action="Create" asp-route-id="@item.Id">Add</a>
  44. <a asp-action="Edit" asp-route-id="@item.Id">Edit</a>
  45. <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
  46. </td>
  47. </tr>
  48. }
  49. else
  50. {
  51. <tr>
  52. <td><input type="checkbox" class="done-checkbox" name="chk_ids" value="@item.Id"></td>
  53. <td>@item.Title</td>
  54. @*<td>@Html.Action("GetParentName",new { id=item.ParentId})</td>*@
  55. <td>@item.ParentName</td>
  56. <td>@item.Sort</td>
  57. <td>
  58. <a asp-action="Create" asp-route-id="@item.Id">Add</a>
  59. <a asp-action="Edit" asp-route-id="@item.Id">Edit</a>
  60. <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
  61. </td>
  62. </tr>
  63. }
  64. }
  65. </thead>
  66. </table>
  67. @{
  68. var prevDisabled = !Model.HasPreviousPage ? "disabled" : "";
  69. var nextDisabled = !Model.HasNextPage ? "disabled" : ""; ;
  70. }
  71. <a asp-action="Index"
  72. asp-route-sortOrder="@ViewData["CurrentSort"]"
  73. asp-route-pageNumber="@(Model.PageIndex - 1)"
  74. asp-route-currentFilter="@ViewData["CurrentFilter"]"
  75. class="btn btn-default @prevDisabled">
  76. 上一页
  77. </a>
  78. <a asp-action="Index"
  79. asp-route-sortOrder="@ViewData["CurrentSort"]"
  80. asp-route-pageNumber="@(Model.PageIndex + 1)"
  81. asp-route-currentFilter="@ViewData["CurrentFilter"]"
  82. class="btn btn-default @nextDisabled">
  83. 下一页
  84. </a>
复制代码
 
所以,需要编写ViewModel是实现,这在前面章节中详细讲过。

(3).Model对象的编码实现。

(3.1)表的直接映射对象ArticleCategory.cs
  1. /// <summary>
  2. /// 文章类别
  3. /// </summary>
  4. public class ArticleCategory
  5. {
  6. /// <summary>
  7. /// 主键
  8. /// </summary>
  9. [Key]
  10. public Int32 Id { get; set; }
  11. /// <summary>
  12. /// 分类标题
  13. /// </summary>
  14. [Required]
  15. public String Title { get; set; }
  16. /// <summary>
  17. /// 父分类ID
  18. /// </summary>
  19. [Required]
  20. public Int32 ParentId { get; set; }
  21. /// <summary>
  22. /// 类别ID列表(逗号分隔开)
  23. /// </summary>
  24. public String ClassList { get; set; }
  25. /// <summary>
  26. /// 类别深度
  27. /// </summary>
  28. public Int32? ClassLayer { get; set; }
  29. /// <summary>
  30. /// 排序
  31. /// </summary>
  32. [Required]
  33. public Int32 Sort { get; set; }
  34. /// <summary>
  35. /// 分类图标
  36. /// </summary>
  37. public String ImageUrl { get; set; }
  38. /// <summary>
  39. /// 分类SEO标题
  40. /// </summary>
  41. public String SeoTitle { get; set; }
  42. /// <summary>
  43. /// 分类SEO关键字
  44. /// </summary>
  45. public String SeoKeywords { get; set; }
  46. /// <summary>
  47. /// 分类SEO描述
  48. /// </summary>
  49. public String SeoDescription { get; set; }
  50. /// <summary>
  51. /// 是否删除
  52. /// </summary>
  53. [Required]
  54. public Boolean IsDeleted { get; set; }
  55. }
  56. (3.2). ArticleCategoryView.cs 也就是文章类别列表页的ViewModel代码
  57. public class ArticleCategoryView
  58. {
  59. public int Id { get; set; }
  60. public string Title { get; set; }
  61. public int ParentId { get; set; }
  62. public string ParentName { get; set; }
  63. public int Sort { get; set; }
  64. }
  65. (4).Controller的编码实现
  66. public class ArticleCategoryController : Controller
  67. {
  68. private readonly IArticleCategoryService _articleCategoryService;
  69. private readonly AppDbContext _appDbContext;
  70. public ArticleCategoryController(IArticleCategoryService articleCategoryService, AppDbContext appDbContext)
  71. {
  72. _appDbContext = appDbContext;
  73. _articleCategoryService = articleCategoryService;
  74. }
  75. public async Task<IActionResult> Index(string sortOrder, string currentFilter, string searchString, int? pageNumber)
  76. {
  77. ViewData["CurrentSort"] = sortOrder;
  78. ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
  79. ViewData["DateSortParm"] = sortOrder == "sort" ? "sort_desc" : "sort";
  80. if (searchString != null)
  81. {
  82. pageNumber = 1;
  83. }
  84. else
  85. {
  86. searchString = currentFilter;
  87. }
  88. ViewData["CurrentFilter"] = searchString;
  89. var articleCategories = from s in _appDbContext.ArticleCategory.OrderBy(s=>s.Id)
  90. join p in _appDbContext.ArticleCategory on s.ParentId equals p.Id into T1
  91. from m in T1.DefaultIfEmpty()
  92. select new ArticleCategoryView
  93. {
  94. Id = s.Id,
  95. Title = s.Title,
  96. ParentId = s.ParentId,
  97. ParentName = m.Title,
  98. Sort = s.Sort
  99. };
  100. if (!string.IsNullOrEmpty(searchString))
  101. {
  102. articleCategories = articleCategories.Where(s => s.Title.Contains(searchString));
  103. }
  104. switch (sortOrder)
  105. {
  106. case "name_desc":
  107. articleCategories = articleCategories.OrderByDescending(s => s.Title);
  108. break;
  109. case "sort":
  110. articleCategories = articleCategories.OrderBy(s => s.Sort);
  111. break;
  112. case "sort_desc":
  113. articleCategories = articleCategories.OrderByDescending(s => s.Sort);
  114. break;
  115. default:
  116. articleCategories = articleCategories.OrderBy(s => s.Title);
  117. break;
  118. }
  119. int pageSize = 4;
  120. return View(await PaginatedList<ArticleCategoryView>.CreateAsync(articleCategories.AsNoTracking(), pageNumber ?? 1, pageSize));
  121. }
  122. [HttpGet]
  123. public async Task<IActionResult> Create(int id)
  124. {
  125. if (id != 0)
  126. {
  127. var articleCategory = await _articleCategoryService.FindArticleCategoryAsync(id);
  128. if (articleCategory != null)
  129. {
  130. ViewData["DisplayName"] = articleCategory.Title;
  131. ViewData["ParentId"] = articleCategory.Id; //增加子目录时,以id作为子目录的parentid
  132. }
  133. }
  134. else
  135. {
  136. ViewData["DisplayName"] = "顶级根目录";
  137. ViewData["ParentId"] = 0;
  138. }
  139. return View();
  140. }
  141. [HttpPost]
  142. [ValidateAntiForgeryToken]
  143. public async Task<IActionResult> Create(int id, ArticleCategory articleCategory)
  144. {
  145. //去掉对字段IsSystem的验证,IsSystem在数据库是bool类型,而前端是0和1,ModelState的验证总是报false,所以去掉对其验证
  146. //ModelState.Remove("IsSystem");//在View端已经解决了了bool类型,那么此行代码可以不用
  147. if (id != 0)
  148. articleCategory.ParentId = id;
  149. if (ModelState.IsValid)
  150. {
  151. var successful = await _articleCategoryService.AddArticleCategoryAysnc(articleCategory);
  152. if (successful)
  153. return RedirectToAction("Index");
  154. else
  155. return BadRequest("失败");
  156. }
  157. return View(articleCategory);
  158. }<br>
  159. [HttpGet]
  160. public async Task<IActionResult> Edit(int id)
  161. {
  162. if (string.IsNullOrEmpty(id.ToString()))
  163. return NotFound();
  164. var articleCategory = await _articleCategoryService.FindArticleCategoryAsync(id);
  165. if (articleCategory == null)
  166. return NotFound();
  167. return View(articleCategory);
  168. }
  169. [HttpPost]
  170. [ValidateAntiForgeryToken]
  171. public async Task<IActionResult> Edit(int id, ArticleCategory articleCategory)
  172. {
  173. if (id != articleCategory.Id)
  174. {
  175. return NotFound();
  176. }
  177. if (ModelState.IsValid)
  178. {
  179. try
  180. {
  181. var result = await _articleCategoryService.UpdateArticleCategoryAsync(id, articleCategory);
  182. }
  183. catch (Exception ex)
  184. {
  185. return BadRequest("编辑失败");
  186. }
  187. return RedirectToAction("Index");
  188. }
  189. return View(articleCategory);
  190. }
  191. [HttpGet]
  192. public async Task<IActionResult> Delete(int id)
  193. {
  194. var result = await _articleCategoryService.DeleteArticleCategoryAsync(id);
  195. if (result)
  196. return RedirectToAction("Index");
  197. else
  198. return BadRequest("删除失败");
  199. }
  200. [HttpPost]
  201. [ValidateAntiForgeryToken]
  202. public async Task<IActionResult> DeleteAll(string[] ids)
  203. {
  204. int countSuccessDel = 0;//记录删除成功的数据条数
  205. int countFailedDel = 0;//记录删除成功的数据条数
  206. foreach (string id in ids)
  207. {
  208. if (await _articleCategoryService.DeleteArticleCategoryAsync(int.Parse(id)))
  209. countSuccessDel++;
  210. else
  211. countFailedDel++;
  212. }
  213. if (countSuccessDel > 0)
  214. return RedirectToAction("Index");
  215. else
  216. return BadRequest("删除失败");
  217. }
  218. public async Task<IActionResult> Details(int id)
  219. {
  220. if (string.IsNullOrEmpty(id.ToString()))
  221. return NotFound();
  222. var articleCategory = await _articleCategoryService.FindArticleCategoryAsync(id);
  223. if (articleCategory == null)
  224. return NotFound();
  225. return View(articleCategory);
  226. }
  227. [HttpGet]
  228. public async Task<IActionResult> GetCategory()
  229. {
  230. var items = await _articleCategoryService.GetArticleCategory();
  231. return View(items);
  232. }
  233. public IActionResult GetParentName(int id)
  234. {
  235. var category = _appDbContext.ArticleCategory.Where(x => x.Id == id).FirstOrDefault();
  236. if (category != null)
  237. return Content(category.Title);
  238. else
  239. return Content("");
  240. }
  241. }
复制代码
 
注意,在Index的Action中的我用了个简单的Linq关联查询

因为,父Id与Id都是存在一张表中,关联子查询在使用时要用到:from m in T1.DefaultIfEmpty() 这一句,可以将空也计算在内,否则,会排除掉最前面2行(下图),我的本意sql语句是:
Select a.Id,a.Title,a.parentId,b.Title as ParentName,a.sort from ArticleCategory a
Left join ArticleCategory b on a.ParentId = b.Id ,这样可以将父Id的名称作为一个新列放在查询表中。

要注意在关键地方的改变,其他内容与将菜单功能时类似。

(5).服务层代码实现
(5.1)接口代码
  1. public interface IArticleCategoryService
  2. {
  3. Task<ArticleCategory[]> GetArticleCategory();
  4. Task<ArticleCategory[]> GetArticleCategoryByParentId(int pId);
  5. Task<ArticleCategory> FindArticleCategoryAsync(int Id);
  6. Task<bool> AddArticleCategoryAysnc(ArticleCategory menu);
  7. Task<bool> UpdateArticleCategoryAsync(int id, ArticleCategory menu);
  8. Task<bool> DeleteArticleCategoryAsync(int Id);
  9. }
复制代码
 
(5.2)实现代码
  1. public class ArticleCategoryService : IArticleCategoryService
  2. {
  3. private readonly AppDbContext _appDbContext;
  4. public ArticleCategoryService(AppDbContext appDbContext)
  5. {
  6. _appDbContext = appDbContext;
  7. }
  8. public async Task<bool> AddArticleCategoryAysnc(ArticleCategory articleCategory)
  9. {
  10. articleCategory.IsDeleted = false;
  11. if (articleCategory.ParentId == 0)
  12. articleCategory.ClassLayer = 1;
  13. else
  14. articleCategory.ClassLayer = 2;
  15. await _appDbContext.ArticleCategory.AddAsync(articleCategory);
  16. var result = await _appDbContext.SaveChangesAsync();
  17. return result == 1;
  18. }
  19. public async Task<bool> DeleteArticleCategoryAsync(int Id)
  20. {
  21. var articleCategory = await _appDbContext.ArticleCategory.FirstOrDefaultAsync(x => x.Id == Id);
  22. if (articleCategory != null)
  23. {
  24. _appDbContext.ArticleCategory.Remove(articleCategory);
  25. }
  26. var result = await _appDbContext.SaveChangesAsync();
  27. return result == 1; //注意(result==1 如果等式成立,则返回true,说明删除成功)
  28. }
  29. public async Task<ArticleCategory> FindArticleCategoryAsync(int Id)
  30. {
  31. var item = await _appDbContext.ArticleCategory.Where(x => x.Id == Id).FirstOrDefaultAsync();
  32. return item;
  33. }
  34. public async Task<ArticleCategory[]> GetArticleCategory()
  35. {
  36. var items = await _appDbContext.ArticleCategory.Where(x => x.IsDeleted==false).OrderByDescending(x => x.Sort).ToArrayAsync();
  37. return items;
  38. }
  39. public async Task<ArticleCategory[]> GetArticleCategoryByParentId(int pId)
  40. {
  41. var items = await _appDbContext.ArticleCategory.Where(x => x.ParentId == pId).OrderByDescending(x => x.Sort).ToArrayAsync();
  42. return items;
  43. }
  44. public async Task<bool> UpdateArticleCategoryAsync(int id, ArticleCategory articleCategory)
  45. {
  46. var oldArticleCategory = await FindArticleCategoryAsync(id); //找出旧对象
  47. //将新值赋到旧对象上
  48. oldArticleCategory.Title = articleCategory.Title;
  49. oldArticleCategory.ParentId = articleCategory.ParentId;
  50. oldArticleCategory.Sort = articleCategory.Sort;
  51. //对旧对象执行更新
  52. _appDbContext.Entry(oldArticleCategory).State = EntityState.Modified;
  53. var result = await _appDbContext.SaveChangesAsync();
  54. return result == 1;
  55. }
  56. }
复制代码
 

至此,文章管理功能编写完成。通篇只有2个主要知识点:linq子查询和ViewModel显示

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

拉不拉稀肚拉稀

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表