ASP.NET MVC4 菜鳥專案之路(一)改造示例程式碼擴充套件使用者資訊管理
模板建立示例專案
MVC的基礎內容我就不說了,入門建議看看官方的MvcMovie示例。
開啟VS2012,【新建專案】,選擇【ASP.NET MVC 4 Web應用程式】,名稱叫MyMvc(這隨便取,但常規是公司.專案的名稱空間),按【確定】,模板選【Internet應用程式】(右邊有說明文字:帶有使用窗體身份驗證的帳戶控制器的預設 ASP.NET MVC 4 專案。是的,就用它自帶的身份驗證,而且它還支援OAuth),其它預設,按【確定】完成專案的建立。然後點執行可以看看效果如下圖:
圖1 初始主頁
可以看到這個模板示例已經實現了一些基本功能,包括註冊和登入,可以試著註冊一下(這裡註冊為wood),成功後自動轉為登入狀態,見下圖:
圖2 登入狀態
點選wood這個賬號名,可進入賬戶管理頁面,如下圖:
圖3 管理介面
可以看到Internet Application模板創建出的示例省掉了我們很多初始化工作,而往往我看到的一些專案例子都是自己重新實現一次賬戶管理(包含註冊、登入、維護)。當然,這是通用的,想要新增自定義使用者資訊:Email、密保問題、密保答案、註冊時間,上次登入時間等,那就要對程式碼進行改動。在改動之前,我們先做幾個事情:
1、改動WebConfig的資料庫連線(這不是必須的)
開啟WebConfig,找到connectString節點,可以看到示例預設建立一個LocalDB資料庫,這個不是很直觀,修改DefaultConnection連線字串即可指向我們自己的資料庫(安裝VS2012後自帶的SQLEXPRESS,本來我想換成mySql的,但是CodeFirst報錯,搜尋瞭解決不了),改後別忘了啟動SQL Server服務
<connectionStrings> <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=aspnet-MyMvc-20130905000410;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\aspnet-MyMvc-20130905000410.mdf" providerName="System.Data.SqlClient" /> </connectionStrings>
<connectionStrings> <add name="DefaultConnection" connectionString="Data Source=STREAM-PC\SQLEXPRESS;Initial Catalog=MyMvc;Integrated Security=SSPI;Pooling=False;Persist Security Info=true" providerName="System.Data.SqlClient" /> </connectionStrings>
改了之後,再次編譯執行,重新註冊一次,發現能註冊成功的。這時,我們在VS裡面,點選選單【檢視】-【SQL server物件資源管理器】,右鍵點選【SQL Server】節點,選擇【新增SQL Server】完成後,發現已經多了MyMvc的資料庫,展開後可以看到自動建立了幾個表,其中dbo.UserProfile就是存放我們的賬戶名的,如下圖:
圖4 資料庫表
明眼的人一看,發現有ID和賬戶名,密碼呢?密碼去哪了?它存放在dbo.webpages_Membership裡,至於為什麼這樣的規則,開啟Filters資料夾下的 InitializeSimpleMembershipAttribute.cs 檔案的41行。
WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true);
可以看到,第二、三、四個引數分別為使用者表名稱、ID欄位名稱和登入名欄位名稱,已經預設定義,可以自行定義。至於還有幾個表,有什麼用,就是後續要說明的內容。
2、OAuth相關(知識點介紹,可跳過)
OAUTH協議為使用者資源的授權提供了一個安全的、開放而又簡易的標準。同時,任何第三方都可以使用OAUTH認證服務,任何服務提供商都可以實現自身的OAUTH認證服務,因而OAUTH是開放的。業界提供了OAUTH的多種實現如PHP、JavaScript,Java,Ruby等各種語言開發包,大大節約了程式設計師的時間,因而OAUTH是簡易的。網際網路很多服務如Open API,很多大公司如Google,Yahoo,Microsoft等都提供了OAUTH認證服務,這些都足以說明OAUTH標準逐漸成為開放資源授權的標準。
簡單說明,大家都應該試過註冊一個網站的時候,可以選擇用人人網、新浪微博、QQ賬號登入吧?其實它們也就實現了OAuth規範,VS開啟解決方案,發現在App_Start目錄建立了一個名為AuthConfig.cs的檔案
開啟可以看到以下內容:
public static class AuthConfig { public static void RegisterAuth() { // 若要允許此站點的使用者使用他們在其他站點(例如 Microsoft、Facebook 和 Twitter)上擁有的帳戶登入, // 必須更新此站點。有關詳細資訊,請訪問 http://go.microsoft.com/fwlink/?LinkID=252166 //OAuthWebSecurity.RegisterMicrosoftClient( // clientId: "", // clientSecret: ""); //OAuthWebSecurity.RegisterTwitterClient( // consumerKey: "", // consumerSecret: ""); //OAuthWebSecurity.RegisterFacebookClient( // appId: "", // appSecret: ""); //OAuthWebSecurity.RegisterGoogleClient(); } }
這些是ASP.NET MVC4帶來的新的Membership系統的內容,從該檔名稱可以看到,該模板示例預設實現了能讓使用者用外部提供方的證書(比如Facebook, Twitter, Microsoft,或Google)登陸方式,然後將源自那些提供方的一些資訊整合進你的web應用,只是它們都做了註釋,所以沒有外部提供者被啟用,也就是上圖3 管理介面最下方提示的內容。假如我們要使用這些證書,只要反註釋,填入相應的資訊即可,只是這些都不符合中國國情,有興趣可以嘗試通過新浪微博API來測試實現,當成功以後,表dbo.webpages_OAuthMembership就會有記錄了,這次我就不做演示了,因為我只想實現普通賬戶許可權驗證,而不需要用到外部資源驗證。在這裡,我們只要好好利用WebSecurity就是了,功能很強大,其方法描述如下。
3、程式碼改造擴充套件資訊
開啟AccountController.cs,找到Register方法,可以看到註冊項呼叫了上面所說的WebSecurity實現使用者建立和登入,
WebSecurity.CreateUserAndAccount(model.UserName, model.Password);
WebSecurity.Login(model.UserName, model.Password);
我們要好好利用,但不需要改動它們,要新增自己的使用者資訊,主要是修改示例自動生成的兩個檔案:AccountModels.cs、AccountController.cs.古有曹植七步成詩,這裡也七步完成改造。
第一步:在AccountModels.cs, 增加一個名為ExtraUserInfo的新類。這個類代表了將在資料庫建立的新表。
[Table("ExtraUserInfo")] public class ExternalUserInfo { [Key] [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)] public int Id { get; set; } [Required] public int UserId { get; set; } /// <summary> /// 使用者組Id /// </summary> [Display(Name = "使用者組Id")] public int GroupId { get; set; } /// <summary> /// Email /// </summary> [Display(Name = "Email", Description = "請輸入您常用的Email。")] [Required(ErrorMessage = "×")] public string Email { get; set; } /// <summary> /// 密保問題 /// </summary> [Display(Name = "密保問題", Description = "請正確填寫,在您忘記密碼時使用者找回密碼。4-20個字元。")] [Required(ErrorMessage = "×")] [StringLength(20, MinimumLength = 4, ErrorMessage = "×")] public string SecurityQuestion { get; set; } /// <summary> /// 密保答案 /// </summary> [Display(Name = "密保答案", Description = "請認真填寫,忘記密碼後回答正確才能找回密碼。2-20個字元。")] [Required(ErrorMessage = "×")] [StringLength(20, MinimumLength = 2, ErrorMessage = "×")] public string SecurityAnswer { get; set; } /// <summary> /// 註冊時間 /// </summary> public DateTime? RegTime { get; set; } /// <summary> /// 上次登入時間 /// </summary> public DateTime? LastLoginTime { get; set; } }
然後把RegisterModel改為繼承它,即把
public class RegisterModel
替換為下面(要記得加上[NotMapped],不然codefirst父類對映錯誤)
[NotMapped] public class RegisterModel : ExternalUserInfo
第二步:在UsersContext類裡,增加一靜態UsersContext變數,這使得我們每次呼叫的時候不需要都建立例項,減少資源浪費,同時建立一個DbSet屬性ExternalUserInfos,如下所示。
public class UsersContext : DbContext { public static UsersContext Instance = new UsersContext(); public UsersContext() : base("DefaultConnection") { } public DbSet<UserProfile> UserProfiles { get; set; } public DbSet<ExternalUserInfo> ExternalUserInfos { get; set; } }
第三步:建立【Domain】資料夾,其下建立【Repository】資料夾,資料夾下建立一個倉儲介面:
public interface IBaseRepository<T> where T : class { bool Add(T entity); bool Delete(T entity); bool Delete(int id); T Find(int id); IQueryable<T> Load(Func<T, bool> whereLambda); IQueryable<T> LoadPage<S>(int pageIndex, int pageSize, out int total, Func<T, bool> whereLambda, bool isAsc, Func<T, S> orderByLambda); bool Update(T entity); }
第四步:建立倉儲基類,實現介面(注:其中Delete和Find我定義為virtual方式,是為了適應不同的表結構,如果每個表統一規範ID為Key,可以採用反射的方式直接在基類實現好)
public class BaseRepository<T> : IBaseRepository<T> where T : class { public ResponseDbContext dbContext = ResponseDbContext.Instance; // 實現對資料庫的新增功能,新增實現EF框架的引用 public bool Add(T entity) { dbContext.Entry<T>(entity).State = EntityState.Added; //下面的寫法統一 return dbContext.SaveChanges() > 0; } //實現對資料庫的修改功能 public bool Update(T entity) { dbContext.Set<T>().Attach(entity); dbContext.Entry<T>(entity).State = EntityState.Modified; return dbContext.SaveChanges() > 0; } //實現對資料庫的刪除功能 public bool Delete(T entity) { dbContext.Set<T>().Attach(entity); dbContext.Entry<T>(entity).State = EntityState.Deleted; return dbContext.SaveChanges() > 0; } public virtual bool Delete(int id) { return false; } public virtual T Find(int id){ return null; } //實現對資料庫的查詢 --簡單查詢 public IQueryable<T> Load(Func<T, bool> whereLambda) { return dbContext.Set<T>().Where<T>(whereLambda).AsQueryable(); } /// <summary> /// 實現對資料的分頁查詢 /// </summary> /// <typeparam name="S">按照某個類進行排序</typeparam> /// <param name="pageIndex">當前第幾頁</param> /// <param name="pageSize">一頁顯示多少條資料</param> /// <param name="total">總條數</param> /// <param name="whereLambda">取得排序的條件</param> /// <param name="isAsc">如何排序,根據倒敘還是升序</param> /// <param name="orderByLambda">根據那個欄位進行排序</param> /// <returns></returns> public IQueryable<T> LoadPage<S>(int pageIndex, int pageSize, out int total, Func<T, bool> whereLambda, bool isAsc, Func<T, S> orderByLambda) { var temp = dbContext.Set<T>().Where<T>(whereLambda); total = temp.Count(); //得到總的條數 //排序,獲取當前頁的資料 if (isAsc) { temp = temp.OrderBy<T, S>(orderByLambda) .Skip<T>(pageSize * (pageIndex - 1)) //越過多少條 .Take<T>(pageSize).AsQueryable(); //取出多少條 } else { temp = temp.OrderByDescending<T, S>(orderByLambda) .Skip<T>(pageSize * (pageIndex - 1)) //越過多少條 .Take<T>(pageSize).AsQueryable(); //取出多少條 } return temp.AsQueryable(); } }
第五步:建立使用者管理倉儲類繼承基類。其實資料的增刪改查,通過資料上下文DbContext進行處理就好了,每個表對應其下的DbSet,這樣我們每個model對應的操作,直接通過繼承基類就可以實現了。
public class UserRepository : BaseRepository<UserProfile> { public override UserInfo Find(int id) { return dbContext.UserProfiles.SingleOrDefault(u => u.UserId== id); } }
public class ExtraUserInfoRepository:BaseRepository<ExtraUserInfo> { public override ExtraUserInfo Find(int id) { return dbContext.ExtraUserInfos.SingleOrDefault(u => u.ID == id); } }
建立完成後就是以下目錄結構:
第六步:修改註冊方法,改為如下:(注:其實UserRepository和ExtraUserInfo可以抽調為全域性變數,供其它方法呼叫,在此為了演示寫在方法內部)
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public ActionResult Register(RegisterModel model) { if (ModelState.IsValid) { // 嘗試註冊使用者 try { UserRepository userRsy = new UserRepository(); UserInfo userModel = userRsy.Find(model.UserId); if (userModel != null) { ModelState.AddModelError("", "當前使用者已存在,請重新選擇"); return View(model); } ExtraUserInfo extraUserModel = new ExtraUserInfo { UserId = model.UserId, GroupId = model.GroupId, Gender = model.Gender, Email = model.Email, SecurityQuestion = model.SecurityQuestion, SecurityAnswer = model.SecurityAnswer, RegTime = model.RegTime, LastLoginTime = model.LastLoginTime }; ExtraUserInfoRepository extraUserRsy = new ExtraUserInfoRepository(); if (extraUserRsy.Add(extraUserModel)) { WebSecurity.CreateUserAndAccount(model.UserName, model.Password); WebSecurity.Login(model.UserName, model.Password); return RedirectToAction("Index", "Home"); } else { ModelState.AddModelError("", "在使用者註冊時,發生了未知錯誤"); } } catch (MembershipCreateUserException e) { ModelState.AddModelError("", ErrorCodeToString(e.StatusCode)); } } // 如果我們進行到這一步時某個地方出錯,則重新顯示錶單 return View(model); }
第七步:最後就是修改註冊頁面:Views\Account\Register.cshtml,把擴充套件資訊內容新增上去
<li>@Html.LabelFor(model => model.Email) @Html.EditorFor(model => model.Email) </li> <li> @Html.LabelFor(m => m.SecurityQuestion) @Html.EditorFor(m =>