初学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. [OJ#63]树句节够提

    [OJ#63]树句节够提 试题描述 给定一棵节点数为 N 的有根树,其中 1 号点是根节点,除此之外第 i 个节点的父亲为 fi.每个节点有一个权值 Ai,所有边权均为 1. 给定 Q 个询问,每个询 ...

  2. OpenMP 并行编程

    OpenMP 并行编程 最近开始学习并行编程,目的是为了提高图像处理的运行速度,用的是VS2012自带的OpenMP. 如何让自己的编译器支持OpenMP: 1) 点击 项目属性页 2)点击 配置 3 ...

  3. 【Vijos1534】高性能计算机(DP)

    题意:有NA个A与NB个B两种任务需要完成,完成一段长度为X的A任务需要时间ta+ka*x*x,B任务类似,连续的同一种任务不能分成两段运行 有P台可以并行运算的计算机,求最快完成所有任务的时间 1≤ ...

  4. 视频流传输协议RTP/RTCP/RTSP/HTTP的区别 (转)

    用一句简单的话总结:RTSP发起/终结流媒体.RTP传输流媒体数据 .RTCP对RTP进行控制,同步.之所以以前对这几个有点分不清,是因为CTC标准里没有对RTCP进行要求,因此在标准RTSP的代码中 ...

  5. scanf()总结--从网上收来的,感觉很好,用来提醒自己,c语言真是博大精深!!【转】

    转自:http://www.cnblogs.com/xiaocai905767378/archive/2011/06/01/2067526.html scanf杂谈          不得不说C语言真 ...

  6. VirtualBox 與 Vmware 差異

    VirtualBox 4.3.36_Ubuntu r105129 與 VMware® Workstation 12 Player  12.5.2 build-4638234, 分別在各自的 Ubunt ...

  7. django使用logging记录日志

    django使用logging记录日志,我没有用这方式去记录日志,主要还是项目小的原因吧, 有机会遇见大项目的话可以回头研究. 配置setting.py配置文件 import logging impo ...

  8. AGC006

    AtCoder Grand Contest 006 <br > 心血来潮,开了一套AGC..... 然后发现各种不会做.........感觉智商被AGC摁在地上摩擦...... <b ...

  9. spring security 单一账户多地方登陆提醒, ajax 拦截器 Interceptor

    spring-security.xml部分代码: <http auto-config="false" > <access-denied-handler ref=& ...

  10. MyBatis_SelectKey使用oracle 序列插入主键

    mapper 如下: 使用<selectkey>实现 也可以使用oracle的row 级触发器trigger实现: <?xml version="1.0" enc ...