AspNetCore-MVC實戰系列(三)之個人中心

神牛003發表於2017-05-02

AspNetCore - MVC實戰系列目錄

. 愛留圖網站誕生

. git原始碼:https://github.com/shenniubuxing3/LovePicture.Web

. AspNetCore - MVC實戰系列(一)之Sqlserver表對映實體模型

. AspNetCore-MVC實戰系列(二)之通過繫結郵箱找回密碼

AspNetCore-MVC實戰系列(三)之個人中心

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\">&laquo;</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\">&raquo;</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 }  

最後上傳一個效果圖吧,表示表示:

相關文章