初学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:

 namespace xCodeMVC.Models
{
public class UserInfo
{
//ID
public int UserID { get; set; } //用户名
public string UserName { get; set; } //密码
public string UserPwd { get; set; } //邮箱
public string UserEmail { get; set; } //用户组:0代表管理员,1代表普通用户
public int UserRank { get; set; } //注册时间
public DateTime RegisterTime { get; set; }
}
}

初步设计已经完成了,下面需要对各个字段进行约束:

l UserID:主键、自增长

l UserName:长度为2到15个字符、必填

l UserPwd:长度为6到20个字符、必填

l UserEmail:必填

l UserRank:默认为1

l RegisterTime:注册时间(DateTime格式)

添加约束后的代码:

 namespace xCodeMVC.Models
{
public class UserInfo
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int UserID { get; set; } [Required(ErrorMessage="用户名不能为空")]
[Display(Name="用户名")]
[StringLength(,MinimumLength=,ErrorMessage="用户名必须为{2}到{1}个字符")]
public string UserName { get; set; } [Required(ErrorMessage="密码不能为空")]
[Display(Name="密码")]
[StringLength(, MinimumLength = , ErrorMessage = "密码必须为{2}到20个字符")]
[DataType(DataType.Password)]
public string UserPwd { get; set; } [Display(Name="邮箱")]
[Required(ErrorMessage="邮箱必填")]
[RegularExpression(@"^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$",
ErrorMessage = "请输入正确的Email格式\n示例:abc@123.com")]
public string UserEmail { get; set; } public int UserRank { get; set; } public DateTime RegisterTime { get; set; }
}
}

至此,一个model就建好了。

接着需要配置一下web.config,在configuration节点内添加数据库连接字符串,后面实体会用到:

 <configuration>
<connectionStrings>
<add name="connection" providerName="System.Data.SqlClient" connectionString="Data Source=.;Initial Catalog=cxyDB;Integrated Security=True" />
</connectionStrings>
</configuration>

在Models文件夹内再建一个新类DBContext.cs,用于进行数据库的相关操作:

 namespace xCodeMVC.Models
{
public class DBContext : DbContext
{
//connection是webconfig内的连接字符串
public DBContext() : base("connection") { } public DbSet<UserInfo> userInfo { get; set; }
}
}

最后需要在Global.asax文件中添加如下配置(如何为数据库初始化一些数据?):

 using xCodeMVC.Models;

 public class MvcApplication : System.Web.HttpApplication
{
//DropCreateDatabaseIfModelChanges表示当模型改变时删除并重新创建数据库
//还有一个Always表示总是在启动时执行删除并重建数据库操作
public class DBInit:DropCreateDatabaseIfModelChanges<DBContext>
{
protected override void Seed(DBContext context)
{
//为数据库insert一些初始数据
context.userInfo.Add(new UserInfo
{
UserName = "troy",
UserPwd = "",
UserEmail = "abc@163.com",
UserRank = ,
RegisterTime = DateTime.Now
});
base.Seed(context);
}
}
protected void Application_Start()
{
Database.SetInitializer(new DBInit());
//省略生成时的代码...
}
}

启动项目,会发现程序自动生成了cxyDB的数据库,并添加了一个名为UserInfoes的表,里面有一条初始记录:

不过需要注意,这里生成的表名是UserInfoes,后面会说明这种情况(为什么EF自动生成的表名后自动添加了s?)。

表单设计

l 客户端验证

首先焦点移出文本框时,需要远程访问一个API,查询数据库中用户名是否存在。在Controllers文件夹选中AccountController.cs控制器并添加如下代码:

 namespace xCodeMVC.Controllers
{
public class AccountController : Controller
{
private DBContext db = new DBContext();
// GET: /Account/CheckUser
[HttpGet]
public JsonResult CheckUser(string username)
{
var exists = db.userInfo.Where(a => a.UserName == username).Count() != ; return Json(exists, JsonRequestBehavior.AllowGet);
}
}
}

客户端用如下代码发起请求:

 $.getJSON("/Account/CheckUser/?username=" + username, function (data) {
if(data) {
//用户名存在
}
});

l 图形验证码

在解决方案中新建一个类库项目,编写生成图形验证码的代码,编译后在MVC项目中引用其生成的dll文件

 public ActionResult GetValidateImg()
{
int width = , height = , fontsize = ;
string code = string.Empty;
byte[] bytes = ValidateCode.CreateCode(out code, , width, height, fontsize); Session["v_code"] = code.ToLower(); return File(bytes,@"image/jpeg");
}

视图

这里没有使用原生的form表单,而是使用了MVC的html辅助方法。

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

@model xCodeMVC.Models.UserInfo

这样就能使用表单增强工具了(省略了一些代码):

 @using (Html.BeginForm("Register", "Account", FormMethod.Post, new { name = "register",onsubmit = "return checkform()"}))
{
@Html.LabelFor(model => model.UserName)
@Html.TextBoxFor(model => model.UserName, new { @class = "text-box" }) @Html.LabelFor(model => model.UserPwd)
@Html.EditorFor(model => model.UserPwd) @Html.LabelFor(model => model.UserEmail)
@Html.EditorFor(model => model.UserEmail) <input class="regBtn" type="submit" value="注册" />
<input class="resetBtn" type="reset" value="重置" /> //令牌,防止重复提交
@Html.AntiForgeryToken()
//模型错误信息汇总,也可以在每一项后面添加
//@Html.ValidationMessage
@Html.ValidationSummary(false)
}

不使用原生form是为了精简代码,将复杂的验证逻辑交给MVC框架去做。

完成后台注册

表单提交的地址是AccountController中的Register方法,该方法只接受HttpPost请求。

 // POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken] public ActionResult Register(UserInfo userInfo)
{
string checkPwd = Request["ChkUserPwd"].ToString();
string vCode = Request["vCode"].ToString().ToLower(); if(string.IsNullOrEmpty(checkPwd))
{
ModelState.AddModelError("ChkUserPwd", "确认密码不能为空");
}
else
{
if (Md5Hash(checkPwd) != Md5Hash(userInfo.UserPwd))
{
ModelState.AddModelError("PwdRepeatError", "确认密码不正确");
}
} if (!ChkValidateCode(vCode))
{
ModelState.AddModelError("vCode", "验证码不正确");
} bool isUserExists = db.userInfo.Where(a => a.UserName == userInfo.UserName).Count() != ;
bool isEmailExists = db.userInfo.Where(a => a.UserEmail == userInfo.UserEmail).Count() != ; if (isUserExists) ModelState.AddModelError("UserName", "用户名已被占用");
if (isEmailExists) ModelState.AddModelError("UserEmail", "邮箱已被注册"); if(!ModelState.IsValid)
{
return View(userInfo);
}
userInfo.RegisterTime = DateTime.Now;
userInfo.UserPwd = Md5Hash(userInfo.UserPwd);
try
{
db.userInfo.Add(userInfo);
db.SaveChanges();
return RedirectToAction("Index", "Home");
}
catch (DbEntityValidationException dbEx)
{
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
System.Diagnostics.Trace.TraceInformation("Property: {0} Error: {1}",
validationError.PropertyName,
validationError.ErrorMessage);
}
}
throw;
}
}

问题汇总

l 为什么EF自动生成的表名后自动添加了s?

这种情况是EF默认的,可以修改一些配置去掉默认规则。

方法一:

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

 namespace xCodeMVC.Models
{
[Table("UserInfo")]
public class UserInfo
{
public int UserID { get; set; }
//......
}
}

方法二:

在DBContext.cs中修改

 namespace xCodeMVC.Models
{
public class DBContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//modelBuilder.Entity<UserInfo>().ToTable("UserInfo");
//或者
//移除默认约定规则,比如在表名后默认加上“s”
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
base.OnModelCreating(modelBuilder);
} public DBContext() : base("connection") { } public DbSet<UserInfo> userInfo { get; set; }
}
}

l 如何为数据库初始化一些数据?

l 使用WebAPI如何返回JSON?

打开AppStart中的webapi配置文件

将以下代码添加到Register中:

 //webapi默认返回xml格式,添加如下代码将返回json格式
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(
new MediaTypeHeaderValue("text/html"));

在webapi的Controller中使用object返回json,例如:

 public object GetUserInfoByName(string username)
{
username = HttpUtility.UrlDecode(username);
return GetUserInfo(a=>a.UserName == username);
}

l 让Action接受Get请求

在方法名前添加属性或者为方法名添加Get前缀

 [System.Web.Http.HttpGet]
public bool GetUserExists(string username)

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

可以参考下面的匹配模式,重点在于为每个路由指定相应的action,url里可以没有action和controller,但为其指定一些值有助于区分各个路由。

 //api/getuser/1
config.Routes.MapHttpRoute(
name: "getUserInfoByID",
routeTemplate: "api/{controller}/{id}",
constraints: new { id = @"^\d*$" },
defaults: new { controller = "getuser", id = RouteParameter.Optional }
); //api/getuser/troy
config.Routes.MapHttpRoute(
name: "getUserInfoByName",
routeTemplate: "api/{controller}/{username}",
constraints: new { username = @"^\w*$" },
defaults: new { controller = "getuser", action = "GetUserInfoByName" }
); //访问形式 api/getuser/?ids=1,3,52,100...
config.Routes.MapHttpRoute(
name: "getUserInfoByCoupleOfIds",
routeTemplate: "api/{controller}/ids={ids}",
constraints: new { ids = @"^\d+,?$" },
defaults: new { controller = "getuser" }
); //api/getuser/check=troy
config.Routes.MapHttpRoute(
name: "ChkUserExists",
routeTemplate: "api/{controller}/check={username}",
constraints: new { username = @"\w*" },
defaults: new { controller = "getuser", action = "ChkUserExists" }
);

l 如何调试路由

很多时候不知道程序采用了哪个路由,可以安装RouteDebugger来查看当前匹配了哪个路由。

安装方法:

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

等待安装完成,在web.config的appsettings节点下可以看到

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

表示路由调试已经打开了,运行程序就可以看到。

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

在脚本中添加如下代码:

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

l 为何在Session中的验证码打印出来后与上一次的相同?

这其实是正确的,因为页面生成在前,而访问验证码在后,Session是在生成验证码时记录的,此时页面的Session还是空的,随后它的值才被赋为验证码的值,所以刷新页面就看到了上一次Session中的验证码。

客户端通过以下代码访问验证码:

 <img id="v_code" class="imgborder"                        src="@Url.Action("GetValidateImg", "Account")?t=@DateTime.Now.Ticks"
alt="看不清,点击换一张" />

l 对一个或多个实体的验证失败(或db.SaveChanges不起作用)

检查模型的约束要求与数据库设计是否一致,字符串长度超限等等这样的错误是不能保存成功的,但往往VS调试时又不能给出具体的错误在哪,所以可以添加一些代码查看错误详细信息。这样就能在输出窗口中可以看到具体的错误。

l 数据库正在使用,无法删除

当模型改动时,之前在Global中的设置会删除并重建数据库,但如果此时你对这个数据库有操作,比如查询之类的,删除就会失败,提示你数据库在使用。这个没找到好的解决方法,我只好采取关掉SQL Server服务再重启这样的笨方法来解决。

.NET MVC 4 实现用户注册功能的更多相关文章

  1. (二)学习MVC之实现用户注册功能

    学习地址:http://www.cnblogs.com/mzwhj/archive/2012/10/22/2720089.html 本文和学习地址不一样的地方是我自己添加了一些简单的注释和理解. 1. ...

  2. Web---创建Servlet的3种方式、简单的用户注册功能

    说明: 创建Servlet的方式,在上篇博客中,已经用了方式1(实现Servlet接口),接下来本节讲的是另外2种方式. 上篇博客地址:http://blog.csdn.net/qq_26525215 ...

  3. Asp.Net MVC页面静态化功能实现二:用递归算法来实现

    上一篇提到采用IHttpModule来实现当用户访问网站的时候,通过重新定义Response.Filter来实现将返回给客户端的html代码保存,以便用户下一次访问是直接访问静态页面. Asp.Net ...

  4. Asp.Net MVC页面静态化功能实现一:利用IHttpModule,摒弃ResultFilter

    上一篇有提到利用IHttpModule和ResultFilter实现页面静态化功能.后来经过一些改动,将ResultFilter中要实现的功能全部转移到IHttpModule中来实现 Asp.Net ...

  5. Asp.Net MVC页面静态化功能实现一:利用IHttpModule和ResultFilter

    由于公司现在所采用的是一套CMS内容管理系统的框架,所以最近项目中有一个需求提到要求实现页面静态化的功能.在网上查询了一些资料和文献,最后采用的是小尾鱼的池塘提供的 利用ResultFilter实现a ...

  6. 微信开发】【Asp.net MVC】-- 微信分享功能

    [微信开发][Asp.net MVC]-- 微信分享功能 2017-01-15 09:09 by stoneniqiu, 12886 阅读, 15 评论, 收藏, 编辑 内嵌在微信中的网页,右上角都会 ...

  7. 9、Django实战第9天:用户注册功能

    今天完成的是用户注册功能... 首先把注册页面的前端文件register.html复制到templates目录下 编辑users.views.py,创建一个注册的类 class RegisterVie ...

  8. 如何巧妙地在基于 TCP Socket 的应用中实现用户注册功能?

    通常,在基于TCP的应用中(比如我开源的GGTalk即时通信系统),当TCP连接建立之后,第一个请求就是登录请求,只有登录成功以后,服务器才会允许客户端进行其它性质的业务请求.但是,注册用户这个功能比 ...

  9. RandomAccessFile()实现用户注册功能, 新增,查询,更新

    package seday03.raf;import java.io.IOException;import java.io.RandomAccessFile;import java.util.Arra ...

随机推荐

  1. 模拟select框

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. [luoguP2526] [SHOI2001]小狗散步(二分图最大匹配)

    传送门 简直就是模板题啊! #include <cmath> #include <cstdio> #include <cstring> #include <i ...

  3. 使用JWT实现Token认证

    为什么使用JWT? 随着技术的发展,分布式web应用的普及,通过session管理用户登录状态成本越来越高,因此慢慢发展成为token的方式做登录身份校验,然后通过token去取redis中的缓存的用 ...

  4. vue.js源码学习分享(七)

    var _Set; /* istanbul ignore if */ if (typeof Set !== 'undefined' && isNative(Set)) { // use ...

  5. ubuntu登入死循环问题 解决!!

    把/etc/environment文件中的 PATH="/usr/local//sbin:/usr/local/bin:/usr/bin:/sbin:/bin:/usr/games" ...

  6. C#学习笔记---区分StringWriter(Reader)和StreamWriter(Reader),TextWriter(Reader),BinaryWriter(Reader)

    1.TextWriter(Reader)分别是对连续字符系列处理的编写器(读写器),来自System.IO 2.StringWriter(Reader)继承TextWriter(Reader),它主要 ...

  7. [原创][SW]一些实用软件的小tips(长期更新)

    0. 简介 生活中我们经常使用许多的小工具或软件,来提高我们的工作效率,比如UltraEdit.Notepad++等.本文主要做一些记录,目的呢就是防止自己遗忘或者是快速的查询,来源是自己的摸索和网络 ...

  8. 树(弱化版)(lca)

    3306: 树 时间限制: 10 Sec  内存限制: 256 MB 题目描述 给定一棵大小为 n 的有根点权树,支持以下操作:  • 换根  • 修改点权      • 查询子树最小值 输入 第一行 ...

  9. Ural 1780 Gray Code 乱搞暴力

    原题链接:http://acm.timus.ru/problem.aspx?space=1&num=1780 1780. Gray Code Time limit: 0.5 secondMem ...

  10. luogu P2158 [SDOI2008]仪仗队

    题目描述 作为体育委员,C君负责这次运动会仪仗队的训练.仪仗队是由学生组成的N * N的方阵,为了保证队伍在行进中整齐划一,C君会跟在仪仗队的左后方,根据其视线所及的学生人数来判断队伍是否整齐(如下图 ...