1. 程式人生 > >ASP.NET MVC4 菜鳥專案之路(一)改造示例程式碼擴充套件使用者資訊管理

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 =>