上章节咱们已经定制好动态配置的菜单,用户登陆网站的第一步就是进入首页内容,那咱们先搭建一下咱们的首页内容。想着本身的网站内容主要是我的博客类型,因此,首页就展现博主本人的一些基本信息吧,哈哈。固然,作成静态的界面很简单,直接将信息填进html中就好了,基本没有什么技术含量,那咱们这里要作成可配置的:将我的信息配置在json文件中(也能够存储在数据库,考虑信息内容结构的不可预期性和易变性,这里不采用数据库保存)。这样,之后咱们要更新主页上的信息时,就不用编译发布网站,只要修改对应的json配置文件便可。html
提到“配置”二字,咱们脑海中会立马浮现出两个特殊文件,那就是咱们再熟悉不过的app.config和web.config,一直以来咱们已经习惯了将结构化的配置信息定义在这两个文件之中。到了.NET Core的时候,不少咱们习觉得常的东西都发生了改变,其中也包括定义配置的方式。有兴趣的同窗能够移步官方文档介绍:.net core 配置前端
.NET Core不只支持以键-值对的形式、结构化的形式读取相关的配置信息(这里本身去研究,不在祥述),更重要的是,现在能够将定义的结构化配置绑定为对象。以前咱们必须逐条的读取配置信息,若是配置项太多的话,读取配置项实际上是一项很是繁琐的工做。如今只用再定义对应结构的Option对象,框架自动帮咱们绑定配置信息到这个对象,咱们直接访问对象中的属性便可。除此以外,.NET Core采用依赖注入的方式来使用Option模型,这样咱们就和方便的在须要的地方使用配置信息了。jquery
接下来看具体怎么使用,首先,在项目中增长json配置文件,这里结构以下:web
咱们按照Json的格式,定义一个接收配置信息的类(结构彻底等同json中的定义)ajax
1 /// <summary> 2 /// 我的资料 3 /// </summary> 4 public class MyProfile 5 { 6 public IEnumerable<Project> Projects { set; get; } 7 } 8 9 /// <summary> 10 /// 项目经历 11 /// </summary> 12 public class Project 13 { 14 /// <summary> 15 /// 持续至日期 16 /// </summary> 17 public string LastToDate { set; get; } 18 /// <summary> 19 /// 项目名称 20 /// </summary> 21 public string Name { set; get; } 22 /// <summary> 23 /// 持续时间 24 /// </summary> 25 public int Lasting { set; get; } 26 /// <summary> 27 /// 所在公司 28 /// </summary> 29 public string Company { set; get; } 30 /// <summary> 31 /// 项目职务 32 /// </summary> 33 public string MyTitle { set; get; } 34 /// <summary> 35 /// 项目职责 36 /// </summary> 37 public string MyDuty { set; get; } 38 /// <summary> 39 /// 项目技术 40 /// </summary> 41 public string Technology { set; get; } 42 /// <summary> 43 /// 项目规格 44 /// </summary> 45 public int NumOfPeople { set; get; } 46 /// <summary> 47 /// 项目描述 48 /// </summary> 49 public IEnumerable<string> ProjectDescs { set; get; } 50 /// <summary> 51 /// 项目图片 52 /// </summary> 53 public IEnumerable<string> ProjectImgs { set; get; } 54 }
那怎么创建json数据和对象之间的映射呢,见证奇迹的时候到了,在Startup.cs文件中ConfigureServices方法中,增长如下代码:数据库
1 var builder = new ConfigurationBuilder() 2 .SetBasePath(Directory.GetCurrentDirectory()) 3 .AddJsonFile("Datas/Config/MyProfile.json"); 4 5 var config = builder.Build(); 6 7 services.Configure<MyProfile>(config.GetSection("MyProfile"));
在须要使用配置信息地方经过构造器注册,能够看到相关信息完整的加载到对象中,是否是新的配置系统显得更加轻量级,具备更好的扩展性,而且支持多样化的数据源。json
有了可配置的我的信息数据,咱们只须要在主页上将之展现出来便可,后台管理--菜单管理,添加一个主页菜单,排序最前,设置访问路径/Home/Index,图标样式,保存后左侧导航便出现新增的主页菜单项。在HomeController控制器的Index方法中,已经读取到我的信息数据,返回前端渲染,效果以下:浏览器
1 <div class="tab-pane active" id="timeline"> 2 <ul class="timeline timeline-inverse"> 3 4 @foreach (var project in Model.Projects.Reverse()) 5 { 6 <li class="time-label"> 7 <span class="bg-light-blue">@project.LastToDate</span> 8 </li> 9 <li> 10 <i class="fa fa-tags bg-blue"></i> 11 <div class="timeline-item"> 12 <span class="time">持续时间:@(project.Lasting)个月 <i class="fa fa-clock-o"></i></span> 13 <h3 class="timeline-header"> 14 <a>项目名称</a> @project.Name 15 </h3> 16 <div class="timeline-body"> 17 <p class="text-muted">所在公司: @project.Company</p> 18 <p class="text-muted">项目职务: @project.MyTitle</p> 19 <p class="text-muted">项目职责: @project.MyDuty</p> 20 <p class="text-muted">项目技术: @project.Technology</p> 21 </div> 22 <div class="timeline-footer"> 23 <a class="btn btn-primary btn-xs">更多 >></a> 24 </div> 25 </div> 26 </li> 27 <li> 28 <i class="fa fa-thumb-tack bg-aqua"></i> 29 <div class="timeline-item"> 30 <span class="time">规模:@(project.NumOfPeople)我的 <i class="fa fa-cube"></i></span> 31 <h3 class="timeline-header no-border"> 32 <a>项目描述</a> 33 </h3> 34 <div class="timeline-body"> 35 @if (project.ProjectDescs != null) 36 { 37 38 var descIndex = 0; 39 40 foreach (var projectDesc in project.ProjectDescs) 41 { 42 <p class="text-muted">@(++descIndex). @projectDesc.</p> 43 } 44 } 45 </div> 46 </div> 47 </li> 48 if (project.ProjectImgs != null) 49 { 50 <li> 51 <i class="fa fa-camera bg-purple"></i> 52 <div class="timeline-item"> 53 <span class="time"><i class="fa fa-photo"></i></span> 54 <h3 class="timeline-header"> 55 <a>效果预览</a> 56 </h3> 57 <div class="timeline-body"> 58 <div class="row"> 59 @foreach (var projectImg in project.ProjectImgs) 60 { 61 <div class="col-sm-6 col-md-4"> 62 <img style="cursor: pointer; height: 150px;" src="@projectImg" 63 alt="..." class="img-responsive thumbnail center-block img-more"> 64 </div> 65 } 66 </div> 67 </div> 68 </div> 69 </li> 70 } 71 72 } 73 74 <!-- start --> 75 <li class="time-label"> 76 <span class="bg-green">2009.02</span> 77 </li> 78 <li> 79 <i class="fa fa-clock-o bg-gray"></i> 80 </li> 81 </ul> 82 </div>
目前图片是用压缩后的缩略图显示的(为了网页快速加载),可能用户想要展开看原始图,全部咱们这块能够优化一下,点击缩略图,弹出模态框,展现原始尺寸图片。ok,主页部分大功告成,后台修改json文件配置,咱们的主页内容也和方便的更新了。服务器
1 <script> 2 $('.img-more').click(function () { 3 var file = $(this).attr('src') 4 var fileNames = file.split('.') 5 $("#myModal").find("#img_show").html("<image src='" + 6 fileNames[0] + '-lg.' + fileNames[1] + 7 "' class='carousel-inner img-responsive img-rounded' />") 8 $("#myModal").modal(); 9 }) 10 </script>
还记得第2章内容吗,咱们已经实现了用户的注册和登陆,可是目前没有作相关的登陆验证和权限管理(权限管理之后等后面完成用户角色单独开一章说,本章主要说一下登陆验证)。好比咱们注销用户时,再次经过浏览器连接,输入http://localhost:16546/Configuration/Menu/Index,就能够跳过登陆,直接访问菜单界面。这块咱们要本身实现的话,不外乎2种方案:app
第1种:定义一个公共的控制器,其余全部的控制器继承它,公共的控制器实现重写以下方法:
1 public class AuthenticationControllor : Controller 2 { 3 protected override void OnActionExecuting(ActionExecutingContext filterContext) 4 { 5 if (filterContext.HttpContext.Session["username"] == null) 6 filterContext.Result = new RedirectToRouteResult("Login", new RouteValueDictionary { { "from", Request.Url.ToString() } }); 7 8 base.OnActionExecuting(filterContext); 9 } 10 }
第2种:定义认证特性,继承ActionFilterAttribute,在须要验证的地方,增长属性认证:
1 // 登陆认证特性 2 public class AuthenticationAttribute : ActionFilterAttribute 3 { 4 public override void OnActionExecuting(ActionExecutingContext filterContext) 5 { 6 if (filterContext.HttpContext.Session["username"] == null) 7 filterContext.Result = new RedirectToRouteResult("Login", new RouteValueDictionary { { "from", Request.Url.ToString() } }); 8 9 base.OnActionExecuting(filterContext); 10 } 11 }
那咱们采用哪一种方法呢?都不用,哈哈,由于Indentity帮咱们已经完成了全部的认证工做,直接在须要验证的控制器或方法上,增长属性过滤器[Authorize]便可。
咱们再试验下,注销登陆后,咱们浏览器中输入菜单界面地址连接,网站判断此时还没有登陆,就会跳转到登陆界面。用户输入登陆信息无误后,跳转到须要访问的菜单界面。
上图咱们能够看到,每次页面加载的时候页面头部会出现一条进度条和一个转动的loading,这样给用户的体验会很好,这是怎么实现的呢。其实AdminLTE已经提供了很方便的使用:首先咱们在母版页Layout.cshtml引入<script src="~/lib/AdminLTE/plugins/pace/pace.js"></script>,而后加入如下脚本,就实现上述的效果,很简单吧:
1 //ajax请求Pace效果 2 $(document).ajaxStart(function () { 3 Pace.restart() 4 })
以前实现的菜单管理,存在一个小问题,好比用户在保存的时候,网速慢或服务器反应延迟的状况,用户等不急再次点击form表单中的提交按钮,就会形成重复提交的场景,通常会致使异常。虽说在后台增长逻辑判断也能够解决,可是重复提交形成的异常各类各样,要就增长各类验证,得不偿失。个人想法是在前端提交后,服务器未返回结果以前,将提交按钮置为不可用状态,这样用户没法再次点击,从而避免服务器端异常。可是还有个问题,必须在jquery前端验证以后,才能开始置为不可用,不然前端验证失效。因此咱们修改下jquery前端验证的默认的submitHandler。每当用户点击提交按钮时,会首先触发前端验证,验证不经过,直接显示错误提示;验证经过后,将按钮置不可用,直到服务器返回结果。
1 //防止重复提交 2 $.validator.setDefaults({ 3 submitHandler: function (form) { 4 $(form).find('[type="submit"]').attr('disabled', true); 5 form.submit(); 6 } 7 });
以前咱们建了一些菜单项,由于没有指定访问路径,点击后会有404错误;另外,一些服务器异常,网页抛出500错误。这些须要捕获一下,并提供友好的页面显示给用户。在Startup.cs文件Configure方法中,配置以下:
1 if (env.IsDevelopment()) 2 { 3 app.UseExceptionHandler("/Home/Error/500"); 4 app.UseStatusCodePagesWithReExecute("/Home/Error/{0}"); 5 6 app.UseDeveloperExceptionPage(); 7 app.UseBrowserLink(); 8 app.UseDatabaseErrorPage(); 9 } 10 else 11 { 12 app.UseExceptionHandler("/Home/Error/500"); 13 app.UseStatusCodePagesWithReExecute("/Home/Error/{0}"); 14 }
对应的控制器代码更新以下:
1 [Route("Home/Error/{statusCode}")] 2 public IActionResult Error(int statusCode) 3 { 4 return View(new ErrorViewModel 5 { 6 StatusCode = statusCode, 7 RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier 8 }); 9 }
对应视图代码也须要更新一下:
1 @model MyWebSite.ViewModels.ErrorViewModel 2 @{ 3 ViewData["Title"] = "错误"; 4 } 5 <div class="error-page" style="margin-top: 100px;"> 6 @if (Model.StatusCode == 500) 7 { 8 <!-- 500类型错误 --> 9 <h2 class="headline text-red"> @Model.StatusCode</h2> 10 11 <div class="error-content"> 12 <h3> 13 <i class="fa fa-warning text-red"></i> Oops! Something went wrong. 14 </h3> 15 <div> 16 StatusCode:<code>@Model.StatusCode</code> 17 </div> 18 <div> 19 Request ID: <code>@Model.RequestId</code> 20 </div> 21 <p> 22 We will work on fixing that right away. 23 Meanwhile, you may <a asp-area="" asp-controller="Home" asp-action="Index">return to index</a> or try using the search form. 24 </p> 25 <form class="search-form"> 26 <div class="input-group"> 27 <input type="text" name="search" class="form-control" placeholder="Search"> 28 <div class="input-group-btn"> 29 <button type="submit" name="submit" class="btn btn-danger btn-flat"> 30 <i class="fa fa-search"></i> 31 </button> 32 </div> 33 </div> 34 </form> 35 </div> 36 } 37 else 38 { 39 <!-- 其余类型错误 --> 40 <h2 class="headline text-yellow"> @Model.StatusCode</h2> 41 42 <div class="error-content"> 43 <h3> 44 <i class="fa fa-warning text-yellow"></i> Oops! Page not found. 45 </h3> 46 <div> 47 StatusCode:<code>@Model.StatusCode</code> 48 </div> 49 <div> 50 Request ID: <code>@Model.RequestId</code> 51 </div> 52 <p> 53 We could not find the page you were looking for. 54 Meanwhile, you may <a asp-area="" asp-controller="Home" asp-action="Index">return to index</a> or try using the search form. 55 </p> 56 <form class="search-form"> 57 <div class="input-group"> 58 <input type="text" name="search" class="form-control" placeholder="Search"> 59 <div class="input-group-btn"> 60 <button type="submit" name="submit" class="btn btn-warning btn-flat"> 61 <i class="fa fa-search"></i> 62 </button> 63 </div> 64 </div> 65 </form> 66 </div> 67 } 68 </div>
以上,这期关于网站的调整已介绍完毕,最后,后台管理--菜单管理,添加对应博客连接的菜单项,为了方便用户经过连接访问个人博客,看下演示效果吧: