AspNetCore-MVC实战系列(三)之个人中心
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的方法:
public static string GetUserIp(this Controller controller)
{
return controller.HttpContext.Connection.RemoteIpAddress.ToString();
}
没错客户端的ip信息存储于 HttpContext.Connection 中,上面是获取ip再来分享怎么获取网站的host和port吧,只需要使用 Request.Host 的代码:
var appUrl = $"http://{Request.Host.Host}:{Request.Host.Port}"
验证用户是否登录
对于个人中心来说往往需要验证用户是否登录,也就是是否存在咋们说通session,同样为了方便我重写了一个父Controller,凡是需要登录验证的Controller都继承于她:
public class BaseController : Controller
{
public MoUserInfo _MyUserInfo; public override void OnActionExecuting(ActionExecutingContext context)
{
_MyUserInfo = context.HttpContext.Session.Get<MoUserInfo>(context.HttpContext.Session.SessionKey()); //获取登录session
if (_MyUserInfo == null)
{
context.Result = new RedirectToActionResult(nameof(MemberController.Login), "Member", new { ReturnUrl = context.HttpContext.Request.Path });
} ViewData["MyUserInfo"] = _MyUserInfo; base.OnActionExecuting(context);
}
}
上面的这种方式通常也是大家对于登录验证用的最多的方式之一了,这里暂时没有用到过滤器等其他的验证方式;
自定义分页TagHelper
对于个人中心的登陆日志和爱心积分增加记录来说,一页是展示不完的,往往需要分页来展示,为此我采用了重写TagHelper的方式,来自定义一个分页控件;首先我定义一个这样的分页属性实体:
/// <summary>
/// 分页option属性
/// </summary>
public class MoPagerOption
{
/// <summary>
/// 当前页 必传
/// </summary>
public int CurrentPage { get; set; }
/// <summary>
/// 总条数 必传
/// </summary>
public int Total { get; set; } /// <summary>
/// 分页记录数(每页条数 默认每页15条)
/// </summary>
public int PageSize { get; set; } /// <summary>
/// 路由地址(格式如:/Controller/Action) 默认自动获取
/// </summary>
public string RouteUrl { get; set; } /// <summary>
/// 样式 默认 bootstrap样式 1
/// </summary>
public int StyleNum { get; set; } /// <summary>
/// 地址与分页数拼接符
/// </summary>
public string JoinOperateCode { get; set; }
}
然后自定个PagerTagHelper类来继承TagHelper,并且重写她的 public override void Process(TagHelperContext context, TagHelperOutput output) 函数,为了简洁我已经在代码注意的地方加上了注释:
/// <summary>
/// 分页标签
/// </summary>
public class PagerTagHelper : TagHelper
{ public MoPagerOption PagerOption { get; set; } public override void Process(TagHelperContext context, TagHelperOutput output)
{ output.TagName = "div"; //PagerOption.JoinOperateCode = string.IsNullOrWhiteSpace(PagerOption.JoinOperateCode) ? "/" : PagerOption.JoinOperateCode;
if (PagerOption.PageSize <= ) { PagerOption.PageSize = ; }
if (PagerOption.CurrentPage <= ) { PagerOption.CurrentPage = ; }
if (PagerOption.Total <= ) { return; } //总页数
var totalPage = PagerOption.Total / PagerOption.PageSize + (PagerOption.Total % PagerOption.PageSize > ? : );
if (totalPage <= ) { return; }
else if (totalPage <= PagerOption.CurrentPage)
{ PagerOption.CurrentPage = totalPage;
} //当前路由地址
if (string.IsNullOrEmpty(PagerOption.RouteUrl))
{ //PagerOption.RouteUrl = helper.ViewContext.HttpContext.Request.RawUrl;
if (!string.IsNullOrEmpty(PagerOption.RouteUrl))
{ var lastIndex = PagerOption.RouteUrl.LastIndexOf("/");
PagerOption.RouteUrl = PagerOption.RouteUrl.Substring(, lastIndex);
}
} //构造分页样式
var sbPage = new StringBuilder(string.Empty);
switch (PagerOption.StyleNum)
{
case :
{ break;
}
default:
{
#region 默认样式 PagerOption.RouteUrl = PagerOption.RouteUrl.TrimEnd('/');
sbPage.Append("<nav>");
sbPage.Append(" <ul class=\"pagination\">");
sbPage.AppendFormat(" <li><a href=\"{0}{2}{1}\" aria-label=\"Previous\"><span aria-hidden=\"true\">«</span></a></li>",
PagerOption.RouteUrl,
PagerOption.CurrentPage - <= ? : PagerOption.CurrentPage - ,
PagerOption.JoinOperateCode); for (int i = ; i <= totalPage; i++)
{ sbPage.AppendFormat(" <li {1}><a href=\"{2}{3}{0}\">{0}</a></li>",
i,
i == PagerOption.CurrentPage ? "class=\"active\"" : "",
PagerOption.RouteUrl,
PagerOption.JoinOperateCode); } sbPage.Append(" <li>");
sbPage.AppendFormat(" <a href=\"{0}{2}{1}\" aria-label=\"Next\">",
PagerOption.RouteUrl,
PagerOption.CurrentPage + > totalPage ? PagerOption.CurrentPage : PagerOption.CurrentPage + ,
PagerOption.JoinOperateCode);
sbPage.Append(" <span aria-hidden=\"true\">»</span>");
sbPage.Append(" </a>");
sbPage.Append(" </li>");
sbPage.Append(" </ul>");
sbPage.Append("</nav>");
#endregion
}
break;
} output.Content.SetHtmlContent(sbPage.ToString());
} }
到此后台封装的方法就完成了,此刻我们还需要再Views/_ViewImports.cshtml文件中增加如下内容,才能正常的只用我们自定义的标签:
@using LovePicture.Web
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper LovePicture.Web.Extends.PagerTagHelper, LovePicture.Web //这里是增加的内容
到此我们分页的配置就完成了,下面就开始介绍怎么来使用封装的分页标签了;先看Action中这样来写:
public IActionResult UserLogs(string id)
{
if (string.IsNullOrWhiteSpace(id)) { return BadRequest(); } #region 构造参数
var paramArr = id.Split('-');
if (paramArr.Length != ) { return BadRequest(); }
var page = Convert.ToInt32(paramArr[]);
var codeId = Convert.ToInt32(paramArr[]);
if (codeId != (int)EnumHelper.EmLogCode.登录 && codeId != (int)EnumHelper.EmLogCode.积分)
{
return BadRequest();
}
page = page <= ? : page; //初始化分页类
var pageOption = new MoPagerOption
{
CurrentPage = page,
PageSize = ,
Total = ,
RouteUrl = $"/usercenter/userlogs/{codeId}",
StyleNum = , JoinOperateCode = "-"
};
#endregion var userLogs = _db.ToUserLog.
Where(b => b.UserId == _MyUserInfo.Id && b.CodeId == codeId).AsEnumerable();
pageOption.Total = userLogs.Count();
userLogs = userLogs.OrderByDescending(b => b.Id).
Skip((pageOption.CurrentPage - ) * pageOption.PageSize).
Take(pageOption.PageSize).
ToList();
//通过ViewData方式分页属性数据传递到试图中
ViewBag.PagerOption = pageOption; var userLog = new ToUserLog
{
CodeId = codeId,
Des = $"{Enum.GetName(typeof(EnumHelper.EmLogCode), codeId)}记录"
};
ViewData["userLog"] = userLog; return View(userLogs);
}
最后需要再试图中绑定模型,和使用标签pager:
@using LovePicture.Com
@using LovePicture.Model.Models
@using LovePicture.Model.MoClass
@using LovePicture.Web.Extends
@model IEnumerable<ToUserLog>
@{
var userLog = ViewData["userLog"] as ToUserLog;
ViewData["Title"] = userLog.Des;
var userInfo = ViewData["MyUserInfo"] as MoUserInfo;
userInfo.Status = ;
}
<div class="row">
@await Html.PartialAsync("UserCenterGroup", userInfo)
<div class="col-md-10">
<h3><span class="glyphicon glyphicon-home" aria-hidden="true"></span> 个人中心</h3>
<hr />
<div class="row">
<div class="col-md-12">
<table id="tabLog" class="table">
<thead>
<tr>
<th class="text-center" style="border:0px">@userLog.Des</th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>
@item.Des
</td>
</tr>
}
</tbody>
</table>
<div class=" text-center">
<pager pager-option="ViewBag.PagerOption as MoPagerOption"></pager>
</div>
</div>
</div>
</div>
</div>
为什么通过ajax获取数据来绑定数据,而不用Action返回实体模型来绑定试图呢
由于什么统计数据,上传记录,登录记录,爱心记录等数据来源方式基本都是统一的来自数据库(暂未只用缓存),所以没有什么特别需要讲的地方,反而关注分析了为什么用ajax获取后再绑定数据到页面的方式;对于ajax的方式请求来说,通常都是前端异步的代名词,一个个人中心来说需要展示很多的信息,尤其是还有统计等信息,如果通过Action-View方式直接绑定Model来加载数据,那无疑是让用户体验变差了,因为这种方式需要等待后台把数据加载完后才能展示页面给客户,经过我对爱留图个人中心如果采用这种方式展示数据的统计,需要花10s(本地)因此在没有使用缓存的基础上,我果断的使用了前端异步展示的方式(ajax);当然对于在mvc模式中使用ajax的方式来绑定数据感觉有些异类,的确是这样,因为netcore的mvc目的就是让咋们快速构建系统的,不过有些时候还是需要更具具体的情况来选择具体的方式;来看看个人中心的统计信息对应的代码吧:
#region 统计信息 [HttpPost]
public JsonResult UserStatis()
{
var data = new MoLoveData();
var list = new List<dynamic>(); //留图数(公有)
var userContent = _db.ToContent.Where(b => b.UserId == _MyUserInfo.Id).AsEnumerable();
var total1 = userContent.Count(b => b.Status == (int)EnumHelper.EmContentStatus.公有);
list.Add(new
{
name = "留图数(公有)",
total = $"{ total1}(张)"
});
//留图数(私有)
var total2 = userContent.Count(b => b.Status == (int)EnumHelper.EmContentStatus.私有);
list.Add(new
{
name = "留图数(私有)",
total = $"{ total2}(张)"
});
//点赞数
var total3 = userContent.Where(b => b.Status != (int)EnumHelper.EmContentStatus.删除).Sum(b => b.ZanNum);
list.Add(new
{
name = "点赞数",
total = $"{ total3}(个)"
});
//浏览数
var total4 = userContent.Where(b => b.Status != (int)EnumHelper.EmContentStatus.删除).Sum(b => b.ReadNum);
list.Add(new
{
name = "浏览数",
total = $"{ total4}(次)"
});
//爱心积分
var total5 = _MyUserInfo.LevelNum;
list.Add(new
{
name = "爱心积分",
total = $"{ total5}(分)"
});
data.Data = list;
data.IsOk = true;
return Json(data);
} #endregion
通过 return Json(data); 方法json对象给请求方;然后前端需要通过ajax来请求:
getUserStatis: function (tabId) {
if (tabId.length <= ) { return; }
$.post("/usercenter/UserStatis", { x: }, function (data) {
if (data) { if (!data.isOk) { $("#" + tabId + " tbody").html('<tr><td colspan="2">获取失败,稍后重试</td></tr>'); return; }
var trArr = [];
$.each(data.data, function (i, item) {
//console.log(item);
trArr.push('<tr><td>' + item.name + '</td><td>' + item.total + '</td></tr>');
});
if (trArr.length > ) {
$("#" + tabId + " tbody").html(trArr.join(''));
} else {
$("#" + tabId + " tbody").html('<tr><td>暂无</td></tr>');
}
}
});
}
为了更好的用户体验,我在ajax还没有请求加载数据前,默认在数据展示区域增加一个加载的图片背景:
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">统计信息</div>
<div class="panel-body">
<table id="tabStatis" class="table text-center">
<thead>
<tr>
<th class="text-center" style="border:0px">统计名称</th>
<th class="text-center" style="border:0px">数值</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="">
<div class="loading"></div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
对应的loading类样式:
.loading {
width: %;
height: 32px;
background: url('../images/load.gif') no-repeat center top;
}
最后上传一个效果图吧,表示表示:
AspNetCore-MVC实战系列(三)之个人中心的更多相关文章
- AspNetCore - MVC实战系列(一)
本章开篇先简单介绍下最近两周自己利用业余时间做的一个图片收集网站,当然这个是靠用户自己上传来收集不是去抓某些个网站的图片,那样没意义,这里我取名为“爱留图”:该网站的简单介绍大家可以参考下上篇的内容爱 ...
- WCF开发实战系列三:自运行WCF服务
WCF开发实战系列三:自运行WCF服务 (原创:灰灰虫的家 http://hi.baidu.com/grayworm)上一篇文章中我们建立了一个WCF服务站点,为WCF服务库运行提供WEB支持,我们把 ...
- ElasticSearch实战系列三: ElasticSearch的JAVA API使用教程
前言 在上一篇中介绍了ElasticSearch实战系列二: ElasticSearch的DSL语句使用教程---图文详解,本篇文章就来讲解下 ElasticSearch 6.x官方Java API的 ...
- MP实战系列(三)之实体类讲解
首先说一句,mybatis plus实在太好用了! mybaits plus的实体类: 以我博客的用户类作为讲解 package com.blog.entity; import com.baomido ...
- shiro实战系列(三)之架构
Apache Shiro 的设计目标是通过直观和易于使用来简化应用程序安全.Shiro 的核心设计体现了大多数人们是如何考虑应用程序安全的——在某些人(或某些事)与应用程序交互的背景下. 应用软件 ...
- memcached实战系列(三)memcached命令使用
memcached命令的使用,在这里我们最好了解一下命令的含义,对命令有一个大致的了解,在了解的基础上进行使用.这里的命名是常用的crud命令的演示. 1.1.1. memcached命令的格式 标准 ...
- Drools实战系列(三)之eclipse创建工程
web工程和maven工程是目前比较常用的,当然对现在而言,maven工程是开发中最常用的. 两种Drools项目的创建方式,一种是直接创建Drools项目,另一种是基于Maven创建Drools项目 ...
- AspNetCore-MVC实战系列(二)之通过绑定邮箱找回密码
AspNetCore - MVC实战系列目录 . 爱留图网站诞生 . AspNetCore - MVC实战系列(一)之Sqlserver表映射实体模型 . AspNetCore-MVC实战系列(二)之 ...
- AspNetCore-MVC实战系列(四)之结尾
AspNetCore - MVC实战系列目录 . 爱留图网站诞生 . git源码:https://github.com/shenniubuxing3/LovePicture.Web . AspNetC ...
随机推荐
- 记 suds 模块循环依赖的坑-RuntimeError: maximum recursion depth exceeded
下面是soa接口调用的核心代码 #! /usr/bin/python # coding:utf-8 from suds.client import Clientdef SoaRequest(wsdl, ...
- Spring-boot中使用@ConditionalOnExpression注解,在特定情况下初始化bean
想要实现的功能: 我想在配置文件中设置一个开关,enabled,在开关为true的时候才实例化bean,进行相关业务逻辑的操作. 具体实现: 1:要实例化的bean 2. 配置类 代码: 想要实例化的 ...
- 重启osd服务失败:Start request repeated too quickly
背景 OS:Ubuntu 16.04 修改了osd的一些配置,修改后,需要重启osd服务才能生效.第一次重启后,配置立刻生效.再改了一些配置,重启osd服务后,配置却不再生效了.ps命令查看进程,发现 ...
- (8)集合之List,ArrayList,LinkedList
集合的体系结构 Collection 单列集合的接口 |----List 如果实现了List接口的集合类,具备的特点是有序,可重复 |----Set 如果实现了Set接口的集合类,集合特点无序不可重复 ...
- 关于npm安装全局模块,require时报Error: Cannot find module 'XXX'的解决办法
系统环境:centos 下午使用npm安装"cheerio",想搞爬虫玩玩. npm安装有两种模式: 本地 # npm install cheerio 全局 # npm insta ...
- AutoIt 脚本小试——刷网易云音乐歌单
AutoIt 确实是个很强大的脚本工具. 如果早知道有这个,当初是怎么都不会去学易语言的 (๑•̀ω•́๑) 这是个简单脚本 = ๛ก(ー̀ωー́ก) 用来增加歌单播放次数和个人的听歌量. 原理不过 ...
- iOS开发之Xib和storyboard对比
相同点: (2)都用来描述软件界面 (2)都用Interface Builder工具来编辑 不同点: (1)Xib是轻量级的,用来描述局部的UI界面 (2)Storyboard是重量级的,用来描述整个 ...
- Android基本控件和事件以及消息总结
Android学生空间界面设计涉及到的常用基本控件有TextView,EditText,Button,ImageView,CheckBox,RadioButton,基本事件有触屏和键盘事件,包括onT ...
- PL/SQL编程重点语句输出整理
create or replace procedure pr_mytest is v_test number() :=; v_char varchar2():='数据库'; c_changl cons ...
- linux系统使用python监测网络接口获取网络的输入输出
#!/usr/bin/env Pythonimport timeimport sys if len(sys.argv) > 1: INTERFACE = sys.argv[1]else: INT ...