zoukankan      html  css  js  c++  java
  • .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格式
    示例: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服务再重启这样的笨方法来解决。

  • 相关阅读:
    Anaconda(4.8.3)(Anaconda3-2020.02-Windows-x86_64)安装日志和启动问题排查日志
    abp学习日志九(总结)
    abp学习日志八(多租户)
    abp学习日志六(模块化开发)
    abp学习日志七(动态API)
    abp学习日志五(领域服务)
    abp学习日志四(仓储)
    ug主菜单men文件按书写格式,这样写有利单个dll调用
    NX开发,blockUI窗口调用blockUI窗口
    VS2013快捷键大全
  • 原文地址:https://www.cnblogs.com/undefined000/p/5084365.html
Copyright © 2011-2022 走看看