以前实习研究过几个月.net mvc,后来转去别的技术就没深入研究。最近打算做个小项目,选来选去决定用mvc5。以此记录在学习过程中问题与心得。(目前工作重点在IIS,有相关问题也可一起讨论)
.net有一个好处是新建项目时可以选择是否用使用账户验证,这是微软封装好的。为了锻炼自己决定手写这部分,代码质量与效率肯定比不上封装好的,不过也正是有了手写这个过程,才能学习到identity验证。
首先是数据库设计,数据库使用SQL Server2019,创建表什么的就不自己创建了,交给EF就可以。所以在项目里新建Model时就是在设计表。用户部分暂时设计三张表:User, Role, UserRoleRelationship。字段,字段类型,有哪些属性都使用Attribute定义好。
public class User
{
/// <summary>
/// 数据库表主键
/// </summary>
[Key]
public int UserId { get; set; }
///<summary>
///注册时输入用户名,用于登录
///</summary>
[Required(ErrorMessage ="务必填写")]
[StringLength(20,MinimumLength =3,ErrorMessage ="3到20个字符,谢谢!")]
[Display(Name ="这是用户名")]
public string UserName { get; set; }
/// <summary>
/// 密码
/// </summary>
[Required(ErrorMessage ="想什么呢,怎么可能不要密码!")]
[StringLength (70,MinimumLength =6,ErrorMessage ="6到20位,谢谢!")]
[Display(Name ="这是密码")]
[DataType(DataType.Password)]
public string PassWord { get; set; }
/// <summary>
/// 邮箱
/// </summary>
[Required(ErrorMessage ="必填,填了也不会发验证邮件。")]
[Display(Name ="电子邮箱")]
[DataType(DataType.EmailAddress,ErrorMessage ="格式不正确")]
public string Email { get; set; }
[Display(Name ="生日")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime Birthday { get; set; }
/// <summary>
/// 注册时间
/// </summary>
public DateTime RegistrationTime { get; set; }
/// <summary>
/// 最后一次登录时间
/// </summary>
public DateTime LoginTime { get; set; }
}
public class Role
{
[Key]
public int RoleId { get; set; }
/// <summary>
/// 用户身份权限
/// 0 普通用户,1 VIP,2 管理员
/// </summary>
[Required]
[Display(Name = "用户身份")]
public int UType { get; set; }
public string UTypeToString(int UType)
{
switch (UType)
{
case 0:
return "普通会员";
case 1:
return "尊贵的VIP会员";
case 2:
return "高贵的管理员";
default:
return "Unknow";
}
}
public class UserRoleRelation
{
[Key]
public int RelationID { get; set; }
[Required]
public int UserId { get; set; }
[Required]
public int RoleId { get; set; }
}
接着新建DbContext,新建WebDbcontext,它需要继承DbContext并在构造函数中指定Connection string,然后设置User,Role,UserRoleRelationship。Connection String在web.config中赋值,便于后期修改。
public class WebDbContext : DbContext
{
public WebDbContext() : base("WebContext")
{
}
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<UserRoleRelation> UserRoleRelations { get; set; }
}
<connectionStrings>
<add name="WebContext" connectionString="Data Source=(LocalDb)\MSSQLLocalDB;Initial Catalog=MyWebSite;Integrated Security=SSPI;" providerName="System.Data.SqlClient" />
</connectionStrings>
接着可以使用nuget命令直接在数据库生成表,也可以跳过此步等项目完成运行时EF自动发送建表命令。PassWord字段最开始设计时设置最大长度是20位,后来加密发现不够,数据存不进去,多次修改Model,这时就必须靠nuget命令更新表,否则项目无法运行。
Model设计完成基本也意味着数据库设计完成。用户注册的逻辑简单来说就是将注册表单中的数据取出存储到数据库中,那么这部分逻辑我不打算在controller中做,如果所有详细逻辑全在controller中,随着代码量的增多,后期更新会增加许多工作量。目前只是初代,后续肯定会有更新和完善。所以逻辑放到UserService中,controller用来接收发送数据。
先在controller中新建UserController,可以以User为Model搭建基架,也可以不建。View中会生成相应的文件夹。前端和后端数据交互方式首选Model,但是注册时除了UserName, Password和Email,还需要ConfirmPassWord,而这个在设计User时并没有考虑进去,所以需要再创建一个Model,专门处理注册。前端数据就封装到RegisterModel中发送到后端。
public class RegisterModel
{
///<summary>
///注册时输入用户名,用于登录
///</summary>
[Required(ErrorMessage = "务必填写")]
[StringLength(20, MinimumLength = 3, ErrorMessage = "3到20个字符,谢谢!")]
[Display(Name = "这是用户名")]
public string UserName { get; set; }
/// <summary>
/// 密码
/// </summary>
[Required(ErrorMessage = "想什么呢,怎么可能不要密码!")]
[StringLength(20, MinimumLength = 6, ErrorMessage = "6到20位,谢谢!")]
[Display(Name = "这是密码")]
[DataType(DataType.Password)]
public string PassWord { get; set; }
[Required(ErrorMessage = "确认一下 ,防止你瞎填密码。")]
[StringLength(20, MinimumLength = 6, ErrorMessage = "6到20位,谢谢!")]
[Compare("PassWord", ErrorMessage = "再确认一遍")]
[Display(Name = "这是确认密码")]
[DataType(DataType.Password)]
public string ConfirmPwd { get; set; }
/// <summary>
/// 邮箱,必填
/// </summary>
[Required(ErrorMessage = "必填,填了也不会发验证邮件。")]
[Display(Name = "电子邮箱")]
[DataType(DataType.EmailAddress, ErrorMessage = "格式不正确")]
public string Email { get; set; }
}
[HttpGet]
public ActionResult Register()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Register([Bind(Include = "UserName,PassWord,ConfirmPwd,Email")] RegisterModel register)
{
if (ModelState.IsValid)
{
if (userService.UserExist(register.UserName))
{
ModelState.AddModelError("UserName","用户已存在!");
}
else
{
try
{
var _user= userService.AddUser(register);
var _identity = userService.CreateIdentity(_user, DefaultAuthenticationTypes.ApplicationCookie);
AuthManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
AuthManager.SignIn(_identity);
return RedirectToAction("Index", "Home");
}
catch(Exception e)
{
ModelState.AddModelError("", "注册失败");
}
return RedirectToAction("Index");
}
}
return View();
}
Controller中逻辑很简单,验证RegisterModel的有效性,先用UserName检测用户是否存在,然后完成注册,注册成功后直接登录,登录代码会在登录部分详细贴出。检测用户是否存在和保存用户的具体操作就在Userservice中完成。
根目录新建Service文件夹,创建UserService类。
public class UserService
{
private WebDbContext db = new WebDbContext();
/// <summary>
/// 判断用户名是否存在
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public Boolean UserExist(string userName)
{
User user = (from u in db.Users where u.UserName == userName select u).FirstOrDefault();
if (user == null) { return false;//不存在
}else { return true; //存在
}
}
/// <summary>
/// 添加用户
/// </summary>
/// <param name="register"></param>
public User AddUser(RegisterModel register)
{
User user = new User();
user.UserName = register.UserName;
user.PassWord = Sha256Encrypt64(register.PassWord);
user.Email = register.Email;
user.RegistrationTime = System.DateTime.Now;
user.LoginTime = user.RegistrationTime;
user.Birthday = new DateTime(1970, 1, 1);
db.Users.Add(user);
db.SaveChanges();
//int a = user.UserId;
UserRoleRelation userRoleRelation = new UserRoleRelation();
userRoleRelation.UserId = user.UserId;
userRoleRelation.RoleId = 3;
db.SaveChanges();
return user;
}
/// <summary>
/// sha256加密方法
/// </summary>
/// <param name="pwd"></param>
/// <returns></returns>
public string Sha256Encrypt64(string pwd)
{
SHA256Managed _sha256 = new SHA256Managed();
byte[] s = _sha256.ComputeHash(Encoding.UTF8.GetBytes(pwd));
return Convert.ToBase64String(s);
}
}
Service中还有个Sha256加密方法,用来对密码加密,需要在Nuget中安装System.Security.Cryptography.Algorithms。可以看到保存时savechanges两次,这是目前比较头疼的地方,因为第一次保存user,第二次是User RoleRelationship。每保存一次就是对数据库连接的一次调用,势必会消耗很多资源。UserRoleRelationship是通过UserId和RoleId来确定用户的权限,而UserId的生成我交给了数据库,必须取回才能保存到UserRoleRelationship。我既希望id交给数据库,又希望只连接数据库一次。如果有好的建议或方法,欢迎指点。
View部分的代码相对简单,使用了Create模板,暂时对于前端就使用最简单的样式。
@model MyWebSite.Models.Account.RegisterModel
@{
ViewBag.Title = "Register";
}
<h2>注册</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>User</h4>
<hr />
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
<div class="form-group">
@Html.LabelFor(model => model.UserName, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.UserName, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.UserName, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.PassWord, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.PassWord, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.PassWord, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.ConfirmPwd, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.ConfirmPwd, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.ConfirmPwd, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Email, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Email, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.Email, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="注册" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}