俗話說,磨刀不費砍柴工。為了更方便的進行項目管理,我們先將個人網站項目配置一下,滿足以下2個目標:
- VS2017中支持Git存儲庫,綁定Github項目,實現本地VS程序與線上Github一鍵代碼提交和同步;
- 搭建服務器FTP站點,VS2017中配置一鍵部署網站文件到服務器;
有了以上的配置,我們可以不用每次拉取和同步我們的程序到Github中,也不用每次在本地發布,拷貝服務器,我們只用在VS2017中簡單的一鍵同步到Github或網站服務器。這樣我們的開發效率有了很大的提高,也方便線上驗證我們的程序代碼。
VS2017支持Github
選擇 工具-->擴展和更新,搜索GitHub,安裝GitHub的VS插件
安裝完插件,打開視圖-->團隊資源管理器,我們可以看到Git插件菜單。通過菜單我們可以新建Git存儲庫,可以提交修改的代碼,並一鍵同步提交后的代碼到自己的GitHub項目中。
再打開GitHub,可以看我們的代碼已經同步了,是不是很方便?
VS2017支持FTP遠程發布
要VS支持FTP發布,首先要將網站服務器配置成FTP服務器。
Server2008添加新的角色,選中文件服務並安裝新角色:
再次選中已安裝的IIS服務,增加FTP服務器相關的角色:
接着,在IIS網站右鍵選擇“添加FTP站點”,選擇FTP文件物理路徑和添加站點名稱:
端口默認21,不用選擇SSL證書,身份驗證這里選擇基本驗證(為了一定的安全性,不要勾選匿名),授權訪問里,指定administrator才能訪問FTP站點,並具有讀取和寫入的權限;
完成后,我們建好的FTP就自動啟動了,這時瀏覽器中輸入ftp://localhost,輸入用戶名和密碼,就可以訪問對應的文件目錄了。當然,我們外網還是無法訪問,為什么呢?相信大家看過上一篇,應該知道是防火牆的原因,我們按照上一篇的配置,增加FTP 21端口的允許入站規則,這樣我們外網就通過FTP訪問網站發布目錄。
配置完外網服務器,我們來配置一下本地VS2017,右鍵項目-->發布,選擇FTP發布,選項配置如下:
這樣我們就已經配置好本地一鍵發布站點到遠程服務器了。以后直接點發布按鈕,就可以看到自動將生成的發布文件,同步到網站服務器:
替換前端框架
准備工作做完,瀏覽器輸入網站服務器IP,可以看到可以正常訪問,但是.net core mvc幫我們自動生成的界面,不一定符合我們的需求,那還是自己找一個前端的UI框架,替換一下既有界面。這里我選擇的是 AdminLTE ,這是一個基於 bootstrap 的輕量級后台模板,相關的資料大家可以去官網研究一下。
我們把下載的文件解壓縮到wwwroot/lib目錄下,第一步先重構一下登錄的界面:
1 @model LoginViewModel 2 3 @{ 4 Layout = null; 5 ViewData["Title"] = "登錄"; 6 } 7 8 <!DOCTYPE html> 9 <html> 10 <head> 11 <meta charset="utf-8" /> 12 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 13 <title>@ViewData["Title"] - LanceL0t</title> 14 15 @await Html.PartialAsync("_SiteCssPartial") 16 </head> 17 <body class="hold-transition login-page"> 18 <div class="login-box"> 19 <div class="login-box-body"> 20 <p class="login-box-msg">歡迎,由此登錄</p> 21 <form asp-route-returnurl="@ViewData["ReturnUrl"]" method="post"> 22 <div asp-validation-summary="All" class="text-danger"></div> 23 <div class="form-group has-feedback"> 24 <input asp-for="Email" class="form-control" placeholder="郵箱"> 25 <span class="glyphicon glyphicon-envelope form-control-feedback"></span> 26 </div> 27 <div class="form-group has-feedback"> 28 <input asp-for="Password" class="form-control" placeholder="密碼"> 29 <span class="glyphicon glyphicon-lock form-control-feedback"></span> 30 </div> 31 <div class="row"> 32 <div class="col-xs-8"> 33 <div class="checkbox icheck"> 34 <label asp-for="RememberMe"> 35 <input asp-for="RememberMe"> @Html.DisplayNameFor(m => m.RememberMe) 36 </label> 37 </div> 38 </div> 39 <div class="col-xs-4"> 40 <button type="submit" class="btn btn-primary btn-block btn-flat">登錄</button> 41 </div> 42 </div> 43 </form> 44 <div class="social-auth-links text-center"> 45 <p>- 或者 -</p> 46 <a href="#" class="btn btn-block btn-social btn-facebook btn-flat"> 47 <i class="fa fa-facebook"></i> Sign in using 48 Facebook 49 </a> 50 <a href="#" class="btn btn-block btn-social btn-google btn-flat"> 51 <i class="fa fa-google-plus"></i> Sign in using 52 Google+ 53 </a> 54 </div> 55 <a asp-action="ForgotPassword">忘記密碼</a><br> 56 <a asp-action="Register" asp-route-returnurl="@ViewData["ReturnUrl"]" class="text-center">立即注冊</a> 57 </div> 58 </div> 59 </body> 60 </html> 61 62 @await Html.PartialAsync("_SiteScriptsPartial") 63 @await Html.PartialAsync("_ValidationScriptsPartial")
接着第二步,優化一下之前的新用戶注冊界面:
1 @model RegisterViewModel 2 3 @{ 4 Layout = null; 5 ViewData["Title"] = "注冊"; 6 } 7 8 <!DOCTYPE html> 9 <html> 10 <head> 11 <meta charset="utf-8" /> 12 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 13 <title>@ViewData["Title"] - LanceL0t</title> 14 15 @await Html.PartialAsync("_SiteCssPartial") 16 </head> 17 <body class="hold-transition login-page"> 18 <div class="login-box"> 19 <div class="login-box-body"> 20 <p class="login-box-msg">歡迎,注冊新用戶</p> 21 <form asp-route-returnurl="@ViewData["ReturnUrl"]" method="post"> 22 <div asp-validation-summary="All" class="text-danger"></div> 23 <div class="form-group has-feedback"> 24 <input asp-for="Email" class="form-control" placeholder="請輸入郵箱"> 25 <span class="glyphicon glyphicon-envelope form-control-feedback"></span> 26 </div> 27 <div class="form-group has-feedback"> 28 <input asp-for="Password" class="form-control" placeholder="請輸入密碼"> 29 <span class="glyphicon glyphicon-lock form-control-feedback"></span> 30 31 </div> 32 <div class="form-group has-feedback"> 33 <input asp-for="ConfirmPassword" class="form-control" placeholder="請確認密碼"> 34 <span class="glyphicon glyphicon-lock form-control-feedback"></span> 35 </div> 36 <div class="row"> 37 <div class="col-xs-8"> 38 <div class="checkbox icheck"> 39 <label asp-for="IsAgree"> 40 <input asp-for="IsAgree"> 閱讀並接受《<a href="#">用戶協議</a>》 41 </label> 42 </div> 43 </div> 44 <div class="col-xs-4"> 45 <button type="submit" class="btn btn-primary btn-block btn-flat">注冊</button> 46 </div> 47 </div> 48 </form> 49 <div class="social-auth-links text-center"> 50 <p>- 或者 -</p> 51 <a href="#" class="btn btn-block btn-social btn-facebook btn-flat"> 52 <i class="fa fa-facebook"></i> Sign in using 53 Facebook 54 </a> 55 <a href="#" class="btn btn-block btn-social btn-google btn-flat"> 56 <i class="fa fa-google-plus"></i> Sign in using 57 Google+ 58 </a> 59 </div> 60 <a asp-controller="Account" asp-action="Login">已有賬號</a><br> 61 </div> 62 </div> 63 </body> 64 </html> 65 66 @await Html.PartialAsync("_SiteScriptsPartial") 67 @await Html.PartialAsync("_ValidationScriptsPartial")
這里的知識很簡單,就不在祥述了,不過因為用的是.net core提供的identity用戶管理和驗證,有些個人遇到的問題,我還是列出來,以免再走彎路。
自定義的服務器端和客戶單的驗證
比如,新用戶注冊時,要保證用戶已勾選“閱讀並接受用戶協議”。而MVC本身校驗機制沒有提供bool型必須為true的校驗,這里我們自己實現一個服務器端屬性的校驗,需要繼承
ValidationAttribute和IClientModelValidator:
1 /// <summary> 2 /// 復選框必須選中驗證 3 /// </summary> 4 [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)] 5 public sealed class MustBeTrueAttribute : ValidationAttribute, IClientModelValidator 6 { 7 //服務器端驗證 8 public override bool IsValid(object value) 9 { 10 return value != null && (bool)value; 11 } 12 13 public void AddValidation(ClientModelValidationContext context) 14 { 15 MergeAttribute(context.Attributes, "data-val", "true"); 16 var errorMessage = FormatErrorMessage(context.ModelMetadata.GetDisplayName()); 17 MergeAttribute(context.Attributes, "data-val-mustbetrue", errorMessage); 18 } 19 20 private bool MergeAttribute( 21 IDictionary<string, string> attributes, 22 string key, 23 string value) 24 { 25 if (attributes.ContainsKey(key)) 26 { 27 return false; 28 } 29 attributes.Add(key, value); 30 return true; 31 } 32 }
再加上客戶端的驗證方法:
1 <script> 2 //必須復選框勾選驗證 3 $.validator.addMethod("mustbetrue", 4 function (value, element, parameters) { 5 return value === "true"; 6 }); 7 8 $.validator.unobtrusive.adapters.add("mustbetrue", [], function (options) { 9 options.rules.mustbetrue = {}; 10 options.messages["mustbetrue"] = options.message; 11 }); 12 </script>
identity的本地化
目前使用identity默認的錯誤描述是英文,這里我們需要顯示成中文,所以新增一個IdentityExtensions類,繼承IdentityErrorDescriber,重寫錯誤描述
1 public class IdentityExtensions : IdentityErrorDescriber 2 { 3 public override IdentityError PasswordRequiresNonAlphanumeric() 4 { 5 return new IdentityError 6 { 7 Code = nameof(PasswordRequiresNonAlphanumeric), 8 Description = "密碼至少包含1位非數字字母的特殊字符" 9 }; 10 } 11 12 public override IdentityError PasswordRequiresDigit() 13 { 14 return new IdentityError 15 { 16 Code = nameof(PasswordRequiresDigit), 17 Description = "密碼至少包含1位數字('0'-'9')" 18 }; 19 } 20 21 public override IdentityError PasswordRequiresLower() 22 { 23 return new IdentityError 24 { 25 Code = nameof(PasswordRequiresLower), 26 Description = "密碼至少包含1位小寫字符 ('a'-'z')" 27 }; 28 } 29 30 public override IdentityError PasswordRequiresUpper() 31 { 32 return new IdentityError 33 { 34 Code = nameof(PasswordRequiresUpper), 35 Description = "密碼至少包含1位大寫寫字符 ('A'-'Z')" 36 }; 37 } 38 }
重寫中文錯誤描述后,我們還得在Startup.cs文件中的服務配置中注冊:
1 public void ConfigureServices(IServiceCollection services) 2 { 3 services.AddDbContext<ApplicationDbContext>(options => 4 options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); 5 6 services.AddIdentity<ApplicationUser, IdentityRole>() 7 .AddEntityFrameworkStores<ApplicationDbContext>() 8 .AddDefaultTokenProviders() 9 .AddErrorDescriber<IdentityExtensions>(); 10 11 // Add application services. 12 services.AddTransient<IEmailSender, EmailSender>(); 13 14 services.AddMvc(); 15 }
登錄和注冊新用戶沒有問題了,再來改造一下登錄后主頁的布局,把_Layout布局視圖分割成頂部區域、左側導航菜單、內容區域、底部區域、右側側邊欄,並用部分視圖分別渲染:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 <title>@ViewData["Title"] - LanceL0t</title> 7 8 @await Html.PartialAsync("_SiteCssPartial") 9 </head> 10 <body class="hold-transition skin-blue sidebar-mini"> 11 <div class="wrapper"> 12 <!-- 頂部區域 --> 13 @await Html.PartialAsync("_LayoutHeaderPartial") 14 <!-- 導航欄 --> 15 @await Html.PartialAsync("_LayoutNavbarPartial") 16 <!-- 內容區域 --> 17 <div class="content-wrapper"> 18 <section class="content-header"> 19 <h1> 20 Dashboard 21 <small>Version 2.0</small> 22 </h1> 23 <ol class="breadcrumb"> 24 <li><a href="#"><i class="fa fa-dashboard"></i> 主頁</a></li> 25 <li class="active">Dashboard</li> 26 </ol> 27 </section> 28 <section class="content"> 29 @RenderBody() 30 </section> 31 </div> 32 <!-- 底部區域 --> 33 @await Html.PartialAsync("_LayoutFooterPartial") 34 <!-- 側邊欄 --> 35 @await Html.PartialAsync("_LayoutSidebarPartial") 36 </div> 37 38 @await Html.PartialAsync("_SiteScriptsPartial") 39 @RenderSection("Scripts", required: false) 40 </body> 41 </html>
這樣,我們登錄和注冊功能大體完成了,我們看下效果: