AspNetCore - MVC實戰系列目錄
. 愛留圖網站誕生
. git原始碼:https://github.com/shenniubuxing3/LovePicture.Web
. AspNetCore - MVC實戰系列(一)之Sqlserver表對映實體模型
. AspNetCore-MVC實戰系列(二)之通過繫結郵箱找回密碼
開篇嘮嗑
這實戰第三章,也就是本章的時候為了響應博友們的要求,已經把愛留圖網站的程式碼開源到了git了,也為了NetCore的美好明天貢獻自己微薄的一份力,儘管已經開源了原始碼,我還是會堅持寫完netcore實戰系列的博文,希望大家多多支援;還有就是今天為了緩解51假期綜合徵能進入學習正規,早上習慣性的開啟部落格園,讓我吃驚了,看著今天編輯推薦的博文還以為部落格園出現什麼故障了,如此不堪的文章盡然被編輯推薦,我在想怎麼了是稽核人員的不負責麼,要說不負責為什麼我寫了這麼久的博文,一篇都沒被編輯推薦過呢呵呵,這裡還是希望部落格園能對內容的稽核更嚴謹,而不是馬馬虎虎,畢竟我們很多人還是支援園區的;
個人中心模組設計
對於一個暫時只有上傳,點贊,閱覽的雛形網站來說,個人中心沒有太多能夠展示的,因此我這裡把使用者常用操作的記錄也展示出來了,和個簡單的統計模組,所以就有了以下的個人中心模組:
由上圖能夠看到內容不多,但是從技術角度上來說也涉及到了幾個知識點和注意事項;
程式碼知識點分享
獲取客戶端ip和服務端ip+port
首先我們就從使用者記錄日誌開始講起,對剛剛使用netcore的朋友,對怎麼獲取操作人的ip有點疑惑,這裡我就分享下我的經驗,避免您們再做各種測試或者查資料了,這裡我為了程式碼使用方便,直接通過靜態擴充套件方法對Controller擴充套件了獲取ip的方法:
1 public static string GetUserIp(this Controller controller) 2 { 3 return controller.HttpContext.Connection.RemoteIpAddress.ToString(); 4 }
沒錯客戶端的ip資訊儲存於 HttpContext.Connection 中,上面是獲取ip再來分享怎麼獲取網站的host和port吧,只需要使用 Request.Host 的程式碼:
1 var appUrl = $"http://{Request.Host.Host}:{Request.Host.Port}"
驗證使用者是否登入
對於個人中心來說往往需要驗證使用者是否登入,也就是是否存在咋們說通session,同樣為了方便我重寫了一個父Controller,凡是需要登入驗證的Controller都繼承於她:
1 public class BaseController : Controller 2 { 3 public MoUserInfo _MyUserInfo; 4 5 public override void OnActionExecuting(ActionExecutingContext context) 6 { 7 _MyUserInfo = context.HttpContext.Session.Get<MoUserInfo>(context.HttpContext.Session.SessionKey()); //獲取登入session 8 if (_MyUserInfo == null) 9 { 10 context.Result = new RedirectToActionResult(nameof(MemberController.Login), "Member", new { ReturnUrl = context.HttpContext.Request.Path }); 11 } 12 13 ViewData["MyUserInfo"] = _MyUserInfo; 14 15 base.OnActionExecuting(context); 16 } 17 }
上面的這種方式通常也是大家對於登入驗證用的最多的方式之一了,這裡暫時沒有用到過濾器等其他的驗證方式;
自定義分頁TagHelper
對於個人中心的登陸日誌和愛心積分增加記錄來說,一頁是展示不完的,往往需要分頁來展示,為此我採用了重寫TagHelper的方式,來自定義一個分頁控制元件;首先我定義一個這樣的分頁屬性實體:
1 /// <summary> 2 /// 分頁option屬性 3 /// </summary> 4 public class MoPagerOption 5 { 6 /// <summary> 7 /// 當前頁 必傳 8 /// </summary> 9 public int CurrentPage { get; set; } 10 /// <summary> 11 /// 總條數 必傳 12 /// </summary> 13 public int Total { get; set; } 14 15 /// <summary> 16 /// 分頁記錄數(每頁條數 預設每頁15條) 17 /// </summary> 18 public int PageSize { get; set; } 19 20 /// <summary> 21 /// 路由地址(格式如:/Controller/Action) 預設自動獲取 22 /// </summary> 23 public string RouteUrl { get; set; } 24 25 /// <summary> 26 /// 樣式 預設 bootstrap樣式 1 27 /// </summary> 28 public int StyleNum { get; set; } 29 30 /// <summary> 31 /// 地址與分頁數拼接符 32 /// </summary> 33 public string JoinOperateCode { get; set; } 34 }
然後自定個PagerTagHelper類來繼承TagHelper,並且重寫她的 public override void Process(TagHelperContext context, TagHelperOutput output) 函式,為了簡潔我已經在程式碼注意的地方加上了註釋:
1 /// <summary> 2 /// 分頁標籤 3 /// </summary> 4 public class PagerTagHelper : TagHelper 5 { 6 7 public MoPagerOption PagerOption { get; set; } 8 9 10 public override void Process(TagHelperContext context, TagHelperOutput output) 11 { 12 13 output.TagName = "div"; 14 15 //PagerOption.JoinOperateCode = string.IsNullOrWhiteSpace(PagerOption.JoinOperateCode) ? "/" : PagerOption.JoinOperateCode; 16 if (PagerOption.PageSize <= 0) { PagerOption.PageSize = 15; } 17 if (PagerOption.CurrentPage <= 0) { PagerOption.CurrentPage = 1; } 18 if (PagerOption.Total <= 0) { return; } 19 20 //總頁數 21 var totalPage = PagerOption.Total / PagerOption.PageSize + (PagerOption.Total % PagerOption.PageSize > 0 ? 1 : 0); 22 if (totalPage <= 0) { return; } 23 else if (totalPage <= PagerOption.CurrentPage) 24 { 25 26 PagerOption.CurrentPage = totalPage; 27 } 28 29 30 //當前路由地址 31 if (string.IsNullOrEmpty(PagerOption.RouteUrl)) 32 { 33 34 //PagerOption.RouteUrl = helper.ViewContext.HttpContext.Request.RawUrl; 35 if (!string.IsNullOrEmpty(PagerOption.RouteUrl)) 36 { 37 38 var lastIndex = PagerOption.RouteUrl.LastIndexOf("/"); 39 PagerOption.RouteUrl = PagerOption.RouteUrl.Substring(0, lastIndex); 40 } 41 } 42 43 //構造分頁樣式 44 var sbPage = new StringBuilder(string.Empty); 45 switch (PagerOption.StyleNum) 46 { 47 case 2: 48 { 49 50 break; 51 } 52 default: 53 { 54 #region 預設樣式 55 56 PagerOption.RouteUrl = PagerOption.RouteUrl.TrimEnd('/'); 57 sbPage.Append("<nav>"); 58 sbPage.Append(" <ul class=\"pagination\">"); 59 sbPage.AppendFormat(" <li><a href=\"{0}{2}{1}\" aria-label=\"Previous\"><span aria-hidden=\"true\">«</span></a></li>", 60 PagerOption.RouteUrl, 61 PagerOption.CurrentPage - 1 <= 0 ? 1 : PagerOption.CurrentPage - 1, 62 PagerOption.JoinOperateCode); 63 64 for (int i = 1; i <= totalPage; i++) 65 { 66 67 sbPage.AppendFormat(" <li {1}><a href=\"{2}{3}{0}\">{0}</a></li>", 68 i, 69 i == PagerOption.CurrentPage ? "class=\"active\"" : "", 70 PagerOption.RouteUrl, 71 PagerOption.JoinOperateCode); 72 73 } 74 75 sbPage.Append(" <li>"); 76 sbPage.AppendFormat(" <a href=\"{0}{2}{1}\" aria-label=\"Next\">", 77 PagerOption.RouteUrl, 78 PagerOption.CurrentPage + 1 > totalPage ? PagerOption.CurrentPage : PagerOption.CurrentPage + 1, 79 PagerOption.JoinOperateCode); 80 sbPage.Append(" <span aria-hidden=\"true\">»</span>"); 81 sbPage.Append(" </a>"); 82 sbPage.Append(" </li>"); 83 sbPage.Append(" </ul>"); 84 sbPage.Append("</nav>"); 85 #endregion 86 } 87 break; 88 } 89 90 output.Content.SetHtmlContent(sbPage.ToString()); 91 } 92 93 }
到此後臺封裝的方法就完成了,此刻我們還需要再Views/_ViewImports.cshtml檔案中增加如下內容,才能正常的只用我們自定義的標籤:
1 @using LovePicture.Web 2 @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 3 @addTagHelper LovePicture.Web.Extends.PagerTagHelper, LovePicture.Web //這裡是增加的內容
到此我們分頁的配置就完成了,下面就開始介紹怎麼來使用封裝的分頁標籤了;先看Action中這樣來寫:
1 public IActionResult UserLogs(string id) 2 { 3 if (string.IsNullOrWhiteSpace(id)) { return BadRequest(); } 4 5 #region 構造引數 6 var paramArr = id.Split('-'); 7 if (paramArr.Length != 2) { return BadRequest(); } 8 var page = Convert.ToInt32(paramArr[1]); 9 var codeId = Convert.ToInt32(paramArr[0]); 10 if (codeId != (int)EnumHelper.EmLogCode.登入 && codeId != (int)EnumHelper.EmLogCode.積分) 11 { 12 return BadRequest(); 13 } 14 page = page <= 0 ? 1 : page; 15 16 //初始化分頁類 17 var pageOption = new MoPagerOption 18 { 19 CurrentPage = page, 20 PageSize = 15, 21 Total = 0, 22 RouteUrl = $"/usercenter/userlogs/{codeId}", 23 StyleNum = 1, 24 25 JoinOperateCode = "-" 26 }; 27 #endregion 28 29 var userLogs = _db.ToUserLog. 30 Where(b => b.UserId == _MyUserInfo.Id && b.CodeId == codeId).AsEnumerable(); 31 pageOption.Total = userLogs.Count(); 32 userLogs = userLogs.OrderByDescending(b => b.Id). 33 Skip((pageOption.CurrentPage - 1) * pageOption.PageSize). 34 Take(pageOption.PageSize). 35 ToList(); 36 //通過ViewData方式分頁屬性資料傳遞到試圖中 37 ViewBag.PagerOption = pageOption; 38 39 var userLog = new ToUserLog 40 { 41 CodeId = codeId, 42 Des = $"{Enum.GetName(typeof(EnumHelper.EmLogCode), codeId)}記錄" 43 }; 44 ViewData["userLog"] = userLog; 45 46 return View(userLogs); 47 }
最後需要再試圖中繫結模型,和使用標籤pager:
1 @using LovePicture.Com 2 @using LovePicture.Model.Models 3 @using LovePicture.Model.MoClass 4 @using LovePicture.Web.Extends 5 @model IEnumerable<ToUserLog> 6 @{ 7 var userLog = ViewData["userLog"] as ToUserLog; 8 ViewData["Title"] = userLog.Des; 9 var userInfo = ViewData["MyUserInfo"] as MoUserInfo; 10 userInfo.Status = 1; 11 } 12 <div class="row"> 13 @await Html.PartialAsync("UserCenterGroup", userInfo) 14 <div class="col-md-10"> 15 <h3><span class="glyphicon glyphicon-home" aria-hidden="true"></span> 個人中心</h3> 16 <hr /> 17 <div class="row"> 18 <div class="col-md-12"> 19 <table id="tabLog" class="table"> 20 <thead> 21 <tr> 22 <th class="text-center" style="border:0px">@userLog.Des</th> 23 </tr> 24 </thead> 25 <tbody> 26 @foreach (var item in Model) 27 { 28 <tr> 29 <td> 30 @item.Des 31 </td> 32 </tr> 33 } 34 </tbody> 35 </table> 36 <div class=" text-center"> 37 <pager pager-option="ViewBag.PagerOption as MoPagerOption"></pager> 38 </div> 39 </div> 40 </div> 41 </div> 42 </div>
為什麼通過ajax獲取資料來繫結資料,而不用Action返回實體模型來繫結試圖呢
由於什麼統計資料,上傳記錄,登入記錄,愛心記錄等資料來源方式基本都是統一的來自資料庫(暫未只用快取),所以沒有什麼特別需要講的地方,反而關注分析了為什麼用ajax獲取後再繫結資料到頁面的方式;對於ajax的方式請求來說,通常都是前端非同步的代名詞,一個個人中心來說需要展示很多的資訊,尤其是還有統計等資訊,如果通過Action-View方式直接繫結Model來載入資料,那無疑是讓使用者體驗變差了,因為這種方式需要等待後臺把資料載入完後才能展示頁面給客戶,經過我對愛留圖個人中心如果採用這種方式展示資料的統計,需要花10s(本地)因此在沒有使用快取的基礎上,我果斷的使用了前端非同步展示的方式(ajax);當然對於在mvc模式中使用ajax的方式來繫結資料感覺有些異類,的確是這樣,因為netcore的mvc目的就是讓咋們快速構建系統的,不過有些時候還是需要更具具體的情況來選擇具體的方式;來看看個人中心的統計資訊對應的程式碼吧:
1 #region 統計資訊 2 3 [HttpPost] 4 public JsonResult UserStatis() 5 { 6 var data = new MoLoveData(); 7 var list = new List<dynamic>(); 8 9 //留圖數(公有) 10 var userContent = _db.ToContent.Where(b => b.UserId == _MyUserInfo.Id).AsEnumerable(); 11 var total1 = userContent.Count(b => b.Status == (int)EnumHelper.EmContentStatus.公有); 12 list.Add(new 13 { 14 name = "留圖數(公有)", 15 total = $"{ total1}(張)" 16 }); 17 //留圖數(私有) 18 var total2 = userContent.Count(b => b.Status == (int)EnumHelper.EmContentStatus.私有); 19 list.Add(new 20 { 21 name = "留圖數(私有)", 22 total = $"{ total2}(張)" 23 }); 24 //點贊數 25 var total3 = userContent.Where(b => b.Status != (int)EnumHelper.EmContentStatus.刪除).Sum(b => b.ZanNum); 26 list.Add(new 27 { 28 name = "點贊數", 29 total = $"{ total3}(個)" 30 }); 31 //瀏覽數 32 var total4 = userContent.Where(b => b.Status != (int)EnumHelper.EmContentStatus.刪除).Sum(b => b.ReadNum); 33 list.Add(new 34 { 35 name = "瀏覽數", 36 total = $"{ total4}(次)" 37 }); 38 //愛心積分 39 var total5 = _MyUserInfo.LevelNum; 40 list.Add(new 41 { 42 name = "愛心積分", 43 total = $"{ total5}(分)" 44 }); 45 data.Data = list; 46 data.IsOk = true; 47 return Json(data); 48 } 49 50 #endregion
通過 return Json(data); 方法json物件給請求方;然後前端需要通過ajax來請求:
1 getUserStatis: function (tabId) { 2 if (tabId.length <= 0) { return; } 3 $.post("/usercenter/UserStatis", { x: 520 }, function (data) { 4 if (data) { 5 6 if (!data.isOk) { $("#" + tabId + " tbody").html('<tr><td colspan="2">獲取失敗,稍後重試</td></tr>'); return; } 7 var trArr = []; 8 $.each(data.data, function (i, item) { 9 //console.log(item); 10 trArr.push('<tr><td>' + item.name + '</td><td>' + item.total + '</td></tr>'); 11 }); 12 if (trArr.length > 0) { 13 $("#" + tabId + " tbody").html(trArr.join('')); 14 } else { 15 $("#" + tabId + " tbody").html('<tr><td>暫無</td></tr>'); 16 } 17 } 18 }); 19 }
為了更好的使用者體驗,我在ajax還沒有請求載入資料前,預設在資料展示區域增加一個載入的圖片背景:
1 <div class="row"> 2 <div class="col-md-6"> 3 <div class="panel panel-default"> 4 <div class="panel-heading">統計資訊</div> 5 <div class="panel-body"> 6 <table id="tabStatis" class="table text-center"> 7 <thead> 8 <tr> 9 <th class="text-center" style="border:0px">統計名稱</th> 10 <th class="text-center" style="border:0px">數值</th> 11 </tr> 12 </thead> 13 <tbody> 14 <tr> 15 <td colspan="2"> 16 <div class="loading"></div> 17 </td> 18 </tr> 19 </tbody> 20 </table> 21 </div> 22 </div> 23 </div>
對應的loading類樣式:
1 .loading { 2 width: 100%; 3 height: 32px; 4 background: url('../images/load.gif') no-repeat center top; 5 }
最後上傳一個效果圖吧,表示表示: