.NET MVC 4 實現用戶注冊功能


初學MVC,踩了不少坑,所以通過實現一個用戶注冊功能把近段時間學習到的知識梳理一遍,方便以后改進和查閱。

 

問題清單:

l 為什么EF自動生成的表名后自動添加了s

l 如何為數據庫初始化一些數據?

l 使用WebAPI如何返回JSON

l 讓Action接受Get請求

l 如何使路由匹配不同的URL

l 如何調試路由

l VS2013如何添加jQuery智能提示?

l 為何在Session中的驗證碼打印出來后與上一次的相同?

l 對一個或多個實體的驗證失敗(db.SaveChanges不起作用)

l 數據庫正在使用,無法刪除

 

數據庫設計(Code First)

這里並沒有采用傳統的數據庫設計方案,而是使用了 代碼優先(code first)這種模式適用於開發初期,數據庫設計目標還不明確的階段,可以隨時修改表和字段。打開VS,新建一個項目,選擇ASP>NET MVC 4 Web應用程序:

 

操作完成后,可以看到以下目錄結構:

 

選擇Models文件夾,新建一個類Model.cs

 

 1 namespace xCodeMVC.Models
 2 {
 3     public class UserInfo
 4    {
 5         //ID
 6         public int UserID { get; set; }
 7 
 8         //用戶名
 9         public string UserName { get; set; }
10 
11         //密碼
12         public string UserPwd { get; set; }
13 
14         //郵箱
15         public string UserEmail { get; set; }
16 
17         //用戶組:0代表管理員,1代表普通用戶
18         public int UserRank { get; set; }
19 
20         //注冊時間
21         public DateTime RegisterTime { get; set; }
22     }
23 }
View Code

 

 

初步設計已經完成了,下面需要對各個字段進行約束:

l UserID:主鍵、自增長

l UserName:長度為215個字符、必填

l UserPwd:長度為620個字符、必填

l UserEmail:必填

l UserRank:默認為1

l RegisterTime:注冊時間(DateTime格式)

 

添加約束后的代碼:

 1 namespace xCodeMVC.Models
 2 {
 3     public class UserInfo
 4     {
 5         [Key]
 6         [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
 7         public int UserID { get; set; }
 8 
 9         [Required(ErrorMessage="用戶名不能為空")]
10         [Display(Name="用戶名")]
11         [StringLength(20,MinimumLength=2,ErrorMessage="用戶名必須為{2}到{1}個字符")]
12         public string UserName { get; set; }
13 
14         [Required(ErrorMessage="密碼不能為空")]
15         [Display(Name="密碼")]
16         [StringLength(50, MinimumLength = 6, ErrorMessage = "密碼必須為{2}到20個字符")]
17         [DataType(DataType.Password)]
18         public string UserPwd { get; set; }
19 
20 
21         [Display(Name="郵箱")]
22         [Required(ErrorMessage="郵箱必填")]
23 [RegularExpression(@"^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$",
24  ErrorMessage = "請輸入正確的Email格式\n示例:abc@123.com")]
25         public string UserEmail { get; set; }
26 
27         public int UserRank { get; set; }
28 
29         public DateTime RegisterTime { get; set; }
30     }
31 }
View Code

至此,一個model就建好了。

接着需要配置一下web.config,在configuration節點內添加數據庫連接字符串,后面實體會用到:

 

1 <configuration>
2   <connectionStrings>
3     <add name="connection" providerName="System.Data.SqlClient" connectionString="Data Source=.;Initial Catalog=cxyDB;Integrated Security=True" />
4   </connectionStrings>
5 </configuration>
View Code

 

Models文件夾內再建一個新類DBContext.cs,用於進行數據庫的相關操作:

 

 1 namespace xCodeMVC.Models
 2 {
 3     public class DBContext : DbContext
 4    {
 5     //connection是webconfig內的連接字符串
 6         public DBContext() : base("connection") { }
 7 
 8         public DbSet<UserInfo> userInfo { get; set; }
 9     }
10 }
View Code

 

最后需要在Global.asax文件中添加如下配置(如何為數據庫初始化一些數據?)

 1 using xCodeMVC.Models;
 2 
 3 public class MvcApplication : System.Web.HttpApplication
 4 {
 5 //DropCreateDatabaseIfModelChanges表示當模型改變時刪除並重新創建數據庫
 6 //還有一個Always表示總是在啟動時執行刪除並重建數據庫操作
 7     public class DBInit:DropCreateDatabaseIfModelChanges<DBContext>
 8         {
 9             protected override void Seed(DBContext context)
10             {
11                 //為數據庫insert一些初始數據
12                 context.userInfo.Add(new UserInfo
13                 {
14                     UserName = "troy",
15                     UserPwd = "111111",
16                     UserEmail = "abc@163.com",
17                     UserRank = 0,
18                     RegisterTime = DateTime.Now
19                 });
20                 base.Seed(context);
21             }
22         }
23     protected void Application_Start()
24     {
25         Database.SetInitializer(new DBInit());
26         //省略生成時的代碼...
27     }
28 }
View Code

啟動項目,會發現程序自動生成了cxyDB的數據庫,並添加了一個名為UserInfoes的表,里面有一條初始記錄:

 

不過需要注意,這里生成的表名是UserInfoes,后面會說明這種情況(為什么EF自動生成的表名后自動添加了s)。

 

表單設計

 

l 客戶端驗證

 

首先焦點移出文本框時,需要遠程訪問一個API,查詢數據庫中用戶名是否存在。在Controllers文件夾選中AccountController.cs控制器並添加如下代碼:

 1 namespace xCodeMVC.Controllers
 2 {
 3     public class AccountController : Controller
 4     {
 5         private DBContext db = new DBContext();
 6         // GET: /Account/CheckUser
 7         [HttpGet]
 8         public JsonResult CheckUser(string username)
 9         {
10             var exists = db.userInfo.Where(a => a.UserName == username).Count() != 0;
11 
12             return Json(exists, JsonRequestBehavior.AllowGet);
13         }
14     }
15 }
View Code

客戶端用如下代碼發起請求:

1 $.getJSON("/Account/CheckUser/?username=" + username, function (data) {
2 if(data) {
3    //用戶名存在
4 }
5 });
View Code

 

l 圖形驗證碼

在解決方案中新建一個類庫項目,編寫生成圖形驗證碼的代碼,編譯后在MVC項目中引用其生成的dll文件

 

 1 public ActionResult GetValidateImg()
 2 {
 3      int width = 60, height = 28, fontsize = 12;
 4      string code = string.Empty;
 5      byte[] bytes = ValidateCode.CreateCode(out code, 4, width, height, fontsize);
 6 
 7      Session["v_code"] = code.ToLower();
 8 
 9      return File(bytes,@"image/jpeg");
10 }
View Code

 

視圖

這里沒有使用原生的form表單,而是使用了MVChtml輔助方法。

首先要在頁面中引入所需的model

 

@model xCodeMVC.Models.UserInfo

 

這樣就能使用表單增強工具了(省略了一些代碼):

 1 @using (Html.BeginForm("Register", "Account", FormMethod.Post, new { name = "register",onsubmit = "return checkform()"}))
 2 {
 3 @Html.LabelFor(model => model.UserName)
 4 @Html.TextBoxFor(model => model.UserName, new { @class = "text-box" })
 5 
 6 @Html.LabelFor(model => model.UserPwd)
 7 @Html.EditorFor(model => model.UserPwd)
 8 
 9 @Html.LabelFor(model => model.UserEmail)
10 @Html.EditorFor(model => model.UserEmail)
11 
12     <input class="regBtn" type="submit" value="注冊" />
13 <input class="resetBtn" type="reset" value="重置" />
14 
15 //令牌,防止重復提交
16 @Html.AntiForgeryToken()
17 //模型錯誤信息匯總,也可以在每一項后面添加
18 //@Html.ValidationMessage
19 @Html.ValidationSummary(false)
20 }
View Code

不使用原生form是為了精簡代碼,將復雜的驗證邏輯交給MVC框架去做。

 

 

完成后台注冊

表單提交的地址是AccountController中的Register方法,該方法只接受HttpPost請求。

 

 1 // POST: /Account/Register
 2 [HttpPost]
 3 [AllowAnonymous]
 4 [ValidateAntiForgeryToken]
 5         
 6 public ActionResult Register(UserInfo userInfo)
 7 {
 8     string checkPwd = Request["ChkUserPwd"].ToString();
 9     string vCode = Request["vCode"].ToString().ToLower();
10 
11     if(string.IsNullOrEmpty(checkPwd))
12     {
13         ModelState.AddModelError("ChkUserPwd", "確認密碼不能為空");
14     }
15     else
16 {
17     if (Md5Hash(checkPwd) != Md5Hash(userInfo.UserPwd))
18         {
19               ModelState.AddModelError("PwdRepeatError", "確認密碼不正確");
20         }
21     }
22             
23 
24     if (!ChkValidateCode(vCode))
25     {
26          ModelState.AddModelError("vCode", "驗證碼不正確");
27     }
28 
29     bool isUserExists = db.userInfo.Where(a => a.UserName == userInfo.UserName).Count() != 0;
30     bool isEmailExists = db.userInfo.Where(a => a.UserEmail == userInfo.UserEmail).Count() != 0;
31 
32     if (isUserExists) ModelState.AddModelError("UserName", "用戶名已被占用");
33     if (isEmailExists) ModelState.AddModelError("UserEmail", "郵箱已被注冊");
34 
35             
36     if(!ModelState.IsValid)
37     {
38         return View(userInfo);
39     }
40     userInfo.RegisterTime = DateTime.Now;
41     userInfo.UserPwd = Md5Hash(userInfo.UserPwd);
42     try
43     {
44         db.userInfo.Add(userInfo);        
45         db.SaveChanges();
46         return RedirectToAction("Index", "Home");
47     }
48     catch (DbEntityValidationException dbEx)
49     {
50         foreach (var validationErrors in dbEx.EntityValidationErrors)
51         {
52             foreach (var validationError in validationErrors.ValidationErrors)
53             {
54             System.Diagnostics.Trace.TraceInformation("Property: {0} Error: {1}",
55             validationError.PropertyName,
56             validationError.ErrorMessage);
57             }
58         }
59         throw;
60     }
61 }
View Code

 

 

 

問題匯總

l 為什么EF自動生成的表名后自動添加了s

 

這種情況是EF默認的,可以修改一些配置去掉默認規則。

方法一:

Models.cs中修改,在類名前加上屬性[Table(TableName)]

 

 

1 namespace xCodeMVC.Models
2 {
3     [Table("UserInfo")]
4     public class UserInfo
5     {
6         public int UserID { get; set; }
7         //......
8     }
9 }
View Code

方法二:

DBContext.cs中修改

 

 1 namespace xCodeMVC.Models
 2 {
 3     public class DBContext : DbContext
 4 {
 5         protected override void OnModelCreating(DbModelBuilder modelBuilder)
 6         {
 7             //modelBuilder.Entity<UserInfo>().ToTable("UserInfo");
 8 //或者
 9 //移除默認約定規則,比如在表名后默認加上“s”
10 modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
11             base.OnModelCreating(modelBuilder);
12     }
13 
14         public DBContext() : base("connection") { }
15 
16     public DbSet<UserInfo> userInfo { get; set; }
17 }
18 }
View Code

 

 

l 如何為數據庫初始化一些數據?

l 使用WebAPI如何返回JSON

 

打開AppStart中的webapi配置文件

將以下代碼添加到Register中:

 

1 //webapi默認返回xml格式,添加如下代碼將返回json格式
2 config.Formatters.JsonFormatter.SupportedMediaTypes.Add(
3                 new MediaTypeHeaderValue("text/html"));
View Code

 

webapiController中使用object返回json,例如:

 

1 public object GetUserInfoByName(string username)
2 {
3     username = HttpUtility.UrlDecode(username);
4     return GetUserInfo(a=>a.UserName == username);
5 }
View Code

 

 

l 讓Action接受Get請求

 

在方法名前添加屬性或者為方法名添加Get前綴

1 [System.Web.Http.HttpGet]
2 public bool GetUserExists(string username)
View Code

 

l 如何使路由匹配不同的URL

 

可以參考下面的匹配模式,重點在於為每個路由指定相應的actionurl里可以沒有actioncontroller,但為其指定一些值有助於區分各個路由。

 

 1 //api/getuser/1
 2 config.Routes.MapHttpRoute(
 3       name: "getUserInfoByID",
 4       routeTemplate: "api/{controller}/{id}",
 5       constraints: new { id = @"^\d*$" },
 6       defaults: new { controller = "getuser", id = RouteParameter.Optional }
 7 );
 8 
 9 //api/getuser/troy
10 config.Routes.MapHttpRoute(
11        name: "getUserInfoByName",
12        routeTemplate: "api/{controller}/{username}",
13        constraints: new { username = @"^\w*$" },
14        defaults: new { controller = "getuser", action = "GetUserInfoByName" }
15 );
16 
17 //訪問形式 api/getuser/?ids=1,3,52,100...
18 config.Routes.MapHttpRoute(
19       name: "getUserInfoByCoupleOfIds",
20       routeTemplate: "api/{controller}/ids={ids}",
21       constraints: new { ids = @"^\d+,?$" },
22       defaults: new { controller = "getuser" }
23 );
24 
25 //api/getuser/check=troy
26 config.Routes.MapHttpRoute(
27       name: "ChkUserExists",
28       routeTemplate: "api/{controller}/check={username}",
29       constraints: new { username = @"\w*" },
30       defaults: new { controller = "getuser", action = "ChkUserExists" }
31 );
View Code

 

 

l 如何調試路由

很多時候不知道程序采用了哪個路由,可以安裝RouteDebugger來查看當前匹配了哪個路由。

安裝方法:

工具->NuGet程序包管理器->控制台->Install-Package RouteDebugger

等待安裝完成,在web.configappsettings節點下可以看到

 

<add key="RouteDebugger:Enabled" value="true" />

 

表示路由調試已經打開了,運行程序就可以看到。

 

l VS2013如何添加jQuery智能提示?

 

在腳本中添加如下代碼:

/// <reference path="jquery-1.11.1.js" />

l 為何在Session中的驗證碼打印出來后與上一次的相同?

 

這其實是正確的,因為頁面生成在前,而訪問驗證碼在后,Session是在生成驗證碼時記錄的,此時頁面的Session還是空的,隨后它的值才被賦為驗證碼的值,所以刷新頁面就看到了上一次Session中的驗證碼。

 

 

 

客戶端通過以下代碼訪問驗證碼:

1 <img id="v_code" class="imgborder"                        src="@Url.Action("GetValidateImg", "Account")?t=@DateTime.Now.Ticks"
2 alt="看不清,點擊換一張" />
View Code

l 對一個或多個實體的驗證失敗(db.SaveChanges不起作用)

 

檢查模型的約束要求與數據庫設計是否一致,字符串長度超限等等這樣的錯誤是不能保存成功的,但往往VS調試時又不能給出具體的錯誤在哪,所以可以添加一些代碼查看錯誤詳細信息。這樣就能在輸出窗口中可以看到具體的錯誤。

 

l 數據庫正在使用,無法刪除

當模型改動時,之前在Global中的設置會刪除並重建數據庫,但如果此時你對這個數據庫有操作,比如查詢之類的,刪除就會失敗,提示你數據庫在使用。這個沒找到好的解決方法,我只好采取關掉SQL Server服務再重啟這樣的笨方法來解決。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM