篇(17)-Asp.Net Core入门实战-文章管理之文章类别的管理
如果要做一个CMS系统,那么文章管理算是入门,文章管理附带一个类别管理,用来对文章进行类别区分。所以,本章简单讲一些类别管理,这也是一个数据操作。
(1).文章类别Sql表的建立
- CREATE TABLE [dbo].[ArticleCategory](
- [Id] [int] IDENTITY(1,1) NOT NULL,
- [Title] [varchar](128) NOT NULL,
- [ParentId] [int] NOT NULL,
- [ClassList] [varchar](128) NULL,
- [ClassLayer] [int] NULL,
- [Sort] [int] NOT NULL,
- [ImageUrl] [varchar](128) NULL,
- [SeoTitle] [varchar](128) NULL,
- [SeoKeywords] [varchar](256) NULL,
- [SeoDescription] [varchar](512) NULL,
- [IsDeleted] [bit] NOT NULL,
- CONSTRAINT [PK_ARTICLECATEGORY] PRIMARY KEY NONCLUSTERED
- (
- [Id] ASC
- )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
- ) ON [PRIMARY]
- GO
复制代码
(2).文章类别的视图
(2.1)Create视图
- @{ ViewData["Title"] = "新建文章分类"; }
- @model ArticleCategory
- <form action="/ArticleCategory/Create" method="post">
- @Html.AntiForgeryToken()
- <label asp-for="ParentId">父类别</label>
- <label name="Parent_DisplayName" id="Parent_DisplayName">@ViewData["DisplayName"]</label>
- <input type="hidden" asp-for="ParentId" id="ParentId" name="ParentId" value="@ViewData["ParentId"]" />
- <label asp-for="Title">类别名称</label>
- <input type="text" asp-for="Title" name="Title" placeholder="请输入类别名">
- <label asp-for="Sort">排序</label>
- <input type="text" asp-for="Sort" name="Sort" placeholder="排序">
- <button type="submit">确定</button>
- <button type="reset">重置</button>
- </form>
复制代码
(2.2)Edit视图
- @{ ViewData["Title"] = "编辑菜单"; }
- @model ArticleCategory
- <form action="/ArticleCategory/Edit" method="post">
- @Html.AntiForgeryToken()
- <label asp-for="ParentId">父菜单</label>
- <input type="text" asp-for="ParentId" name="ParentId" />
- <input type="hidden" asp-for="Id" />
- <label asp-for="Title">类别名成</label>
- <input type="text" asp-for="Title" name="Title" placeholder="类别名成">
- <label asp-for="Sort">排序</label>
- <input type="text" asp-for="Sort" name="Sort" placeholder="排序">
- <button type="submit">确定</button>
- <button type="reset">重置</button>
- </form>
复制代码
(2.3)Index列表视图
单纯的列表在现实父类名称时,显示的还是Id值,看第一步的表结构可以发现,这个字段是ParentId,我们得显示成名称才行。- @using Humanizer;
- @using RjWebCms.Db;
- @using RjWebCms.Models.Articles;
- @using RjWebCms.Common;
- @model PaginatedList<ArticleCategoryView>
- @{
- ViewData["Title"] = "文章类别列表";
- }
- @section Scripts{
- }
- @ViewData["Title"]
- @Html.AntiForgeryToken()
- <form asp-action="Index" method="get">
- <table>
- <tr><td><a asp-controller="ArticleCategory" asp-action="Create">添加</a></td></tr>
- <tr>
- <td>查询关键词:<input type="text" name="SearchString" value="@ViewData["CurrentFilter"]" /></td>
- <td><input type="submit" value="查询" /></td>
- <td><a asp-action="Index">Back</a></td>
- <td><a asp-action="DeleteAll">批量删除</a></td>
- </tr>
- </table>
- </form>
- <table class="table table-hover">
- <thead>
- <tr>
- <td>✔</td>
- <td><a asp-action="Index" asp-route-sortOrder="@ViewData["NameSortParm"]" asp-route-currentFilter="@ViewData["CurrentFilter"]">类别名称</a></td>
- <td>父类名称</td>
- <td><a asp-action="Index" asp-route-sortOrder="@ViewData["DateSortParm"]" asp-route-currentFilter="@ViewData["CurrentFilter"]">序号</a></td>
- <td>操作</td>
- </tr>
- @foreach (var item in Model)
- {
- if (item.ParentId == 0)
- {
- <tr>
- <td><input type="checkbox" class="done-checkbox" name="chk_ids" value="@item.Id"></td>
- <td>@item.Title</td>
- <td>\</td>
- <td>@item.Sort</td>
- <td>
- <a asp-action="Create" asp-route-id="@item.Id">Add</a>
- <a asp-action="Edit" asp-route-id="@item.Id">Edit</a>
- <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
- </td>
- </tr>
- }
- else
- {
- <tr>
- <td><input type="checkbox" class="done-checkbox" name="chk_ids" value="@item.Id"></td>
- <td>@item.Title</td>
- @*<td>@Html.Action("GetParentName",new { id=item.ParentId})</td>*@
- <td>@item.ParentName</td>
- <td>@item.Sort</td>
- <td>
- <a asp-action="Create" asp-route-id="@item.Id">Add</a>
- <a asp-action="Edit" asp-route-id="@item.Id">Edit</a>
- <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
- </td>
- </tr>
- }
- }
- </thead>
- </table>
- @{
- var prevDisabled = !Model.HasPreviousPage ? "disabled" : "";
- var nextDisabled = !Model.HasNextPage ? "disabled" : ""; ;
- }
- <a asp-action="Index"
- asp-route-sortOrder="@ViewData["CurrentSort"]"
- asp-route-pageNumber="@(Model.PageIndex - 1)"
- asp-route-currentFilter="@ViewData["CurrentFilter"]"
- class="btn btn-default @prevDisabled">
- 上一页
- </a>
- <a asp-action="Index"
- asp-route-sortOrder="@ViewData["CurrentSort"]"
- asp-route-pageNumber="@(Model.PageIndex + 1)"
- asp-route-currentFilter="@ViewData["CurrentFilter"]"
- class="btn btn-default @nextDisabled">
- 下一页
- </a>
复制代码
所以,需要编写ViewModel是实现,这在前面章节中详细讲过。
(3).Model对象的编码实现。
(3.1)表的直接映射对象ArticleCategory.cs- /// <summary>
- /// 文章类别
- /// </summary>
- public class ArticleCategory
- {
- /// <summary>
- /// 主键
- /// </summary>
- [Key]
- public Int32 Id { get; set; }
- /// <summary>
- /// 分类标题
- /// </summary>
- [Required]
- public String Title { get; set; }
- /// <summary>
- /// 父分类ID
- /// </summary>
- [Required]
- public Int32 ParentId { get; set; }
- /// <summary>
- /// 类别ID列表(逗号分隔开)
- /// </summary>
- public String ClassList { get; set; }
- /// <summary>
- /// 类别深度
- /// </summary>
- public Int32? ClassLayer { get; set; }
- /// <summary>
- /// 排序
- /// </summary>
- [Required]
- public Int32 Sort { get; set; }
- /// <summary>
- /// 分类图标
- /// </summary>
- public String ImageUrl { get; set; }
- /// <summary>
- /// 分类SEO标题
- /// </summary>
- public String SeoTitle { get; set; }
- /// <summary>
- /// 分类SEO关键字
- /// </summary>
- public String SeoKeywords { get; set; }
- /// <summary>
- /// 分类SEO描述
- /// </summary>
- public String SeoDescription { get; set; }
- /// <summary>
- /// 是否删除
- /// </summary>
- [Required]
- public Boolean IsDeleted { get; set; }
- }
- (3.2). ArticleCategoryView.cs 也就是文章类别列表页的ViewModel代码
- public class ArticleCategoryView
- {
- public int Id { get; set; }
- public string Title { get; set; }
- public int ParentId { get; set; }
- public string ParentName { get; set; }
- public int Sort { get; set; }
- }
- (4).Controller的编码实现
- public class ArticleCategoryController : Controller
- {
- private readonly IArticleCategoryService _articleCategoryService;
- private readonly AppDbContext _appDbContext;
- public ArticleCategoryController(IArticleCategoryService articleCategoryService, AppDbContext appDbContext)
- {
- _appDbContext = appDbContext;
- _articleCategoryService = articleCategoryService;
- }
- public async Task<IActionResult> Index(string sortOrder, string currentFilter, string searchString, int? pageNumber)
- {
- ViewData["CurrentSort"] = sortOrder;
- ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
- ViewData["DateSortParm"] = sortOrder == "sort" ? "sort_desc" : "sort";
- if (searchString != null)
- {
- pageNumber = 1;
- }
- else
- {
- searchString = currentFilter;
- }
- ViewData["CurrentFilter"] = searchString;
- var articleCategories = from s in _appDbContext.ArticleCategory.OrderBy(s=>s.Id)
- join p in _appDbContext.ArticleCategory on s.ParentId equals p.Id into T1
- from m in T1.DefaultIfEmpty()
- select new ArticleCategoryView
- {
- Id = s.Id,
- Title = s.Title,
- ParentId = s.ParentId,
- ParentName = m.Title,
- Sort = s.Sort
- };
- if (!string.IsNullOrEmpty(searchString))
- {
- articleCategories = articleCategories.Where(s => s.Title.Contains(searchString));
- }
- switch (sortOrder)
- {
- case "name_desc":
- articleCategories = articleCategories.OrderByDescending(s => s.Title);
- break;
- case "sort":
- articleCategories = articleCategories.OrderBy(s => s.Sort);
- break;
- case "sort_desc":
- articleCategories = articleCategories.OrderByDescending(s => s.Sort);
- break;
- default:
- articleCategories = articleCategories.OrderBy(s => s.Title);
- break;
- }
- int pageSize = 4;
- return View(await PaginatedList<ArticleCategoryView>.CreateAsync(articleCategories.AsNoTracking(), pageNumber ?? 1, pageSize));
- }
- [HttpGet]
- public async Task<IActionResult> Create(int id)
- {
- if (id != 0)
- {
- var articleCategory = await _articleCategoryService.FindArticleCategoryAsync(id);
- if (articleCategory != null)
- {
- ViewData["DisplayName"] = articleCategory.Title;
- ViewData["ParentId"] = articleCategory.Id; //增加子目录时,以id作为子目录的parentid
- }
- }
- else
- {
- ViewData["DisplayName"] = "顶级根目录";
- ViewData["ParentId"] = 0;
- }
- return View();
- }
- [HttpPost]
- [ValidateAntiForgeryToken]
- public async Task<IActionResult> Create(int id, ArticleCategory articleCategory)
- {
- //去掉对字段IsSystem的验证,IsSystem在数据库是bool类型,而前端是0和1,ModelState的验证总是报false,所以去掉对其验证
- //ModelState.Remove("IsSystem");//在View端已经解决了了bool类型,那么此行代码可以不用
- if (id != 0)
- articleCategory.ParentId = id;
- if (ModelState.IsValid)
- {
- var successful = await _articleCategoryService.AddArticleCategoryAysnc(articleCategory);
- if (successful)
- return RedirectToAction("Index");
- else
- return BadRequest("失败");
- }
- return View(articleCategory);
- }<br>
- [HttpGet]
- public async Task<IActionResult> Edit(int id)
- {
- if (string.IsNullOrEmpty(id.ToString()))
- return NotFound();
- var articleCategory = await _articleCategoryService.FindArticleCategoryAsync(id);
- if (articleCategory == null)
- return NotFound();
- return View(articleCategory);
- }
- [HttpPost]
- [ValidateAntiForgeryToken]
- public async Task<IActionResult> Edit(int id, ArticleCategory articleCategory)
- {
- if (id != articleCategory.Id)
- {
- return NotFound();
- }
- if (ModelState.IsValid)
- {
- try
- {
- var result = await _articleCategoryService.UpdateArticleCategoryAsync(id, articleCategory);
- }
- catch (Exception ex)
- {
- return BadRequest("编辑失败");
- }
- return RedirectToAction("Index");
- }
- return View(articleCategory);
- }
- [HttpGet]
- public async Task<IActionResult> Delete(int id)
- {
- var result = await _articleCategoryService.DeleteArticleCategoryAsync(id);
- if (result)
- return RedirectToAction("Index");
- else
- return BadRequest("删除失败");
- }
- [HttpPost]
- [ValidateAntiForgeryToken]
- public async Task<IActionResult> DeleteAll(string[] ids)
- {
- int countSuccessDel = 0;//记录删除成功的数据条数
- int countFailedDel = 0;//记录删除成功的数据条数
- foreach (string id in ids)
- {
- if (await _articleCategoryService.DeleteArticleCategoryAsync(int.Parse(id)))
- countSuccessDel++;
- else
- countFailedDel++;
- }
- if (countSuccessDel > 0)
- return RedirectToAction("Index");
- else
- return BadRequest("删除失败");
- }
- public async Task<IActionResult> Details(int id)
- {
- if (string.IsNullOrEmpty(id.ToString()))
- return NotFound();
- var articleCategory = await _articleCategoryService.FindArticleCategoryAsync(id);
- if (articleCategory == null)
- return NotFound();
- return View(articleCategory);
- }
- [HttpGet]
- public async Task<IActionResult> GetCategory()
- {
- var items = await _articleCategoryService.GetArticleCategory();
- return View(items);
- }
- public IActionResult GetParentName(int id)
- {
- var category = _appDbContext.ArticleCategory.Where(x => x.Id == id).FirstOrDefault();
- if (category != null)
- return Content(category.Title);
- else
- return Content("");
- }
- }
复制代码
注意,在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)接口代码- public interface IArticleCategoryService
- {
- Task<ArticleCategory[]> GetArticleCategory();
- Task<ArticleCategory[]> GetArticleCategoryByParentId(int pId);
- Task<ArticleCategory> FindArticleCategoryAsync(int Id);
- Task<bool> AddArticleCategoryAysnc(ArticleCategory menu);
- Task<bool> UpdateArticleCategoryAsync(int id, ArticleCategory menu);
- Task<bool> DeleteArticleCategoryAsync(int Id);
- }
复制代码
(5.2)实现代码- public class ArticleCategoryService : IArticleCategoryService
- {
- private readonly AppDbContext _appDbContext;
- public ArticleCategoryService(AppDbContext appDbContext)
- {
- _appDbContext = appDbContext;
- }
- public async Task<bool> AddArticleCategoryAysnc(ArticleCategory articleCategory)
- {
- articleCategory.IsDeleted = false;
- if (articleCategory.ParentId == 0)
- articleCategory.ClassLayer = 1;
- else
- articleCategory.ClassLayer = 2;
- await _appDbContext.ArticleCategory.AddAsync(articleCategory);
- var result = await _appDbContext.SaveChangesAsync();
- return result == 1;
- }
- public async Task<bool> DeleteArticleCategoryAsync(int Id)
- {
- var articleCategory = await _appDbContext.ArticleCategory.FirstOrDefaultAsync(x => x.Id == Id);
- if (articleCategory != null)
- {
- _appDbContext.ArticleCategory.Remove(articleCategory);
- }
- var result = await _appDbContext.SaveChangesAsync();
- return result == 1; //注意(result==1 如果等式成立,则返回true,说明删除成功)
- }
- public async Task<ArticleCategory> FindArticleCategoryAsync(int Id)
- {
- var item = await _appDbContext.ArticleCategory.Where(x => x.Id == Id).FirstOrDefaultAsync();
- return item;
- }
- public async Task<ArticleCategory[]> GetArticleCategory()
- {
- var items = await _appDbContext.ArticleCategory.Where(x => x.IsDeleted==false).OrderByDescending(x => x.Sort).ToArrayAsync();
- return items;
- }
- public async Task<ArticleCategory[]> GetArticleCategoryByParentId(int pId)
- {
- var items = await _appDbContext.ArticleCategory.Where(x => x.ParentId == pId).OrderByDescending(x => x.Sort).ToArrayAsync();
- return items;
- }
- public async Task<bool> UpdateArticleCategoryAsync(int id, ArticleCategory articleCategory)
- {
- var oldArticleCategory = await FindArticleCategoryAsync(id); //找出旧对象
- //将新值赋到旧对象上
- oldArticleCategory.Title = articleCategory.Title;
- oldArticleCategory.ParentId = articleCategory.ParentId;
- oldArticleCategory.Sort = articleCategory.Sort;
- //对旧对象执行更新
- _appDbContext.Entry(oldArticleCategory).State = EntityState.Modified;
- var result = await _appDbContext.SaveChangesAsync();
- return result == 1;
- }
- }
复制代码
至此,文章管理功能编写完成。通篇只有2个主要知识点:linq子查询和ViewModel显示
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |