Member区域(二) - 用户部分(1)用户注册

上一次把基本框架搭建起来了,这次开始整Web部分,终于可以看到界面了小激动一下。web项目部分从用户功能开始,基本有注册,登录、注销、查找、查看、删除等涉及Member区域和Manage区域。

一、默认Web项目的更改

在上一次中我们创建了一个使用个人用户账户的mvc项目,通过分析自动生成的用户管理代码(见《 VS2013中web项目中自动生成的ASP.NET Identity代码思考 》),我觉用户这部分还是自己做,所以删除自动生成的用户相关代码。

image_thumb

二、添加Member区域

在web项目上点右键 添加 区域Member。

image

添加Home控制器,选择MVC5控制器-空

image

我们给public ActionResult Index()添加一个视图,代码很简单就是显示下用户名

@{
    ViewBag.Title = "会员中心";
}

<h2>欢迎你!@User.Identity.Name 
    </h2>

我们先运行一下,出错啦。

image

这是因为项目中有两个名为Home的控制器,必须在路由中加上命名空间。先打开区域中的MemberAreaRegistration添加命名空间。

image

再打开项目中的RouteConfig,添加命名空间

image

再刷新浏览器,可以正常显示。

再添加用户控制器UserController。

三、模型类的更改

在这里先对Models项目User模型进行修改,原来考虑的是每个用户只能属于一个用户组,后来仔细考虑了一下,还是不太合适,比如一个用户兼任多个角色,所以还是把用户和用户组改成一对多的关系。

User模型。在模型中删除GroupID,删除外键Group。

Role模型。原来UserGroup(用户组)改为角色,考虑到权限管理感觉叫角色比叫户组更加合适,另外角色的含义更广,可以是指用户组,也可以指职位,还可以指部门……修改后代码如下:

using System.ComponentModel.DataAnnotations;

namespace Ninesky.Models
{
    /// <summary>
    /// 角色
    /// <remarks>
    /// 创建:2014.02.02
    /// 修改:2014.02.16
    /// </remarks>
    /// </summary>
    public class Role
    {
        [Key]
        public int RoleID { get; set; }

        /// <summary>
        /// 名称
        /// </summary>
        [Required(ErrorMessage="必填")]
        [StringLength(20, MinimumLength = 2, ErrorMessage = "{1}到{0}个字")]
        [Display(Name="名称")]
        public string Name { get; set; }

        /// <summary>
        /// 角色类型<br />
        /// 0普通(普通注册用户),1特权(像VIP之类的类型),3管理(管理权限的类型)
        /// </summary>
        [Required(ErrorMessage = "必填")]
        [Display(Name = "用户组类型")]
        public int Type { get; set; }

        /// <summary>
        /// 说明
        /// </summary>
        [Required(ErrorMessage = "必填")]
        [StringLength(50, ErrorMessage = "少于{0}个字")]
        [Display(Name = "说明")]
        public string Description { get; set; }

        /// <summary>
        /// 获取角色类型名称
        /// </summary>
        /// <returns></returns>
        public string TypeToString()
        {
            switch (Type)
            {
                case 0:
                    return "普通";
                case 1:
                    return "特权";
                case 2:
                    return "管理";
                default:
                    return "未知";
            }
        }
    }
}

UserRoleRelation类。在Models项目添加角色关系类UserRoleRelation类,代码:

using System.ComponentModel.DataAnnotations;

namespace Ninesky.Models
{
    /// <summary>
    /// 用户角色关系
    /// <remarks>
    /// 创建:2014.02.16
    /// </remarks>
    /// </summary>
    public class UserRoleRelation
    {
        [Key]
        public int RelationID { get; set; }

        /// <summary>
        /// 用户ID
        /// </summary>
        [Required()]
        public int UserID { get; set; }
        
        /// <summary>
        /// 角色ID
        /// </summary>
        [Required()]
        public int RoelID { get; set; }
    }
}

 NineskyDbContext类。如下图蓝色框为修改部分,红框为新增加

image

三、验证码及Sha256加密

1、验证码

现在验证码是网站的必须功能,我把验证码功能分成三块:创建验证码字符、根据验证码生成图片、User控制器action中保存验证码并返回图片。

创建验证码字符 CreateVerificationText()

在Common中添加Security类,在类中利用伪随机数生成器生成验证码字符串。

/// <summary>
/// 创建验证码字符
/// </summary>
/// <param name="length">字符长度</param>
/// <returns>验证码字符</returns>
public static string CreateVerificationText(int length) {
    char[] _verification = new char[length];
    char[] _dictionary = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T'
, 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l'
, 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
    Random _random = new Random();
    for (int i = 0; i < length; i++) {
        _verification[i] = _dictionary[_random.Next(_dictionary.Length - 1)];
    }
    return new string(_verification);
}

根据验证码生成图片CreateVerificationImage()

思路是使用GDI+创建画布,使用伪随机数生成器生成渐变画刷,然后创建渐变文字。

/// <summary>
/// 创建验证码图片
/// </summary>
/// <param name="verificationText">验证码字符串</param>
/// <param name="width">图片宽度</param>
/// <param name="height">图片长度</param>
/// <returns>图片</returns>
public static Bitmap CreateVerificationImage(string verificationText, int width, int height) {
    Pen _pen = new Pen(Color.Black);
    Font _font = new Font("Arial", 14, FontStyle.Bold);
    Brush _brush = null;
    Bitmap _bitmap = new Bitmap(width, height);
    Graphics _g = Graphics.FromImage(_bitmap);
    SizeF _totalSizeF = _g.MeasureString(verificationText, _font);
    SizeF _curCharSizeF;
    PointF _startPointF = new PointF((width - _totalSizeF.Width) / 2, (height - _totalSizeF.Height) / 2);
    //随机数产生器
    Random _random = new Random();
    _g.Clear(Color.White);
    for (int i = 0; i < verificationText.Length; i++) {
        _brush = new LinearGradientBrush(new Point(0, 0), new Point(1, 1), Color.FromArgb(_random.Next(255), _random.Next(255), _random.Next(255)), Color.FromArgb(_random.Next(255), _random.Next(255), _random.Next(255)));
        _g.DrawString(verificationText[i].ToString(), _font, _brush, _startPointF);
        _curCharSizeF = _g.MeasureString(verificationText[i].ToString(), _font);
        _startPointF.X += _curCharSizeF.Width;
    }
    _g.Dispose();
    return _bitmap;
}

User控制器action中保存验证码并返回图片

首先添加User控制器,在Member区域中添加控制器UserController。在控制器中写一个VerificationCode方法。过程是:在方法中我们先创建6位验证码字符串->使用CreateVerificationImage创建验证码图片->把图片写入OutputStream中->把验证码字符串写入TempData中。

保存在TempData中和Session中的区别:TempData只传递一次,也就是传递到下一个action后,action代码执行完毕就会销毁,Session会持续保存,所以验证码用TempData比较合适。

/// <summary>
        /// 验证码
        /// </summary>
        /// <returns></returns>
        public ActionResult VerificationCode()
        {
            string verificationCode = Security.CreateVerificationText(6);
            Bitmap _img = Security.CreateVerificationImage(verificationCode, 160, 30);
            _img.Save(Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg);
            TempData["VerificationCode"] = verificationCode.ToUpper();
            return null;
        }

我们看看生成图验证码效果 image

2、Sha256加密

在COmmon项目的Security类中添加静态方法Sha256(string plainText)

/// <summary>
        /// 256位散列加密
        /// </summary>
        /// <param name="plainText">明文</param>
        /// <returns>密文</returns>
        public static string Sha256(string plainText)
        {
            SHA256Managed _sha256 = new SHA256Managed();
            byte[] _cipherText = _sha256.ComputeHash(Encoding.Default.GetBytes(plainText));
            return Convert.ToBase64String(_cipherText);
        }

四、注册

在Ninesky.Web.Areas.Member.Models中添加注册视图模型

using System.ComponentModel.DataAnnotations;

namespace Ninesky.Web.Areas.Member.Models
{
    public class RegisterViewModel
    {
        /// <summary>
        /// 用户名
        /// </summary>
        [Required(ErrorMessage = "必填")]
        [StringLength(20, MinimumLength = 4, ErrorMessage = "{2}到{1}个字符")]
        [Display(Name = "用户名")]
        public string UserName { get; set; }

        /// <summary>
        /// 显示名
        /// </summary>
        [Required(ErrorMessage = "必填")]
        [StringLength(20, MinimumLength = 2, ErrorMessage = "{2}到{1}个字符")]
        [Display(Name = "显示名")]
        public string DisplayName { get; set; }

        /// <summary>
        /// 密码
        /// </summary>
        [Required(ErrorMessage = "必填")]
        [Display(Name = "密码")]
        [StringLength(20,MinimumLength=6,ErrorMessage="{2}到{1}个字符")]
        [DataType(DataType.Password)]
        public string Password { get; set; }

        /// <summary>
        /// 确认密码
        /// </summary>
        [Required(ErrorMessage = "必填")]
        [Compare("Password", ErrorMessage = "两次输入的密码不一致")]
        [Display(Name = "确认密码")]
        [DataType(DataType.Password)]
        public string ConfirmPassword { get; set; }

        /// <summary>
        /// 邮箱
        /// </summary>
        [Required(ErrorMessage = "必填")]
        [Display(Name = "邮箱")]
        [DataType(DataType.EmailAddress,ErrorMessage="Email格式不正确")]
        public string Email { get; set; }

        /// <summary>
        /// 验证码
        /// </summary>
        [Required(ErrorMessage = "必填")]
        [StringLength(6, MinimumLength = 6, ErrorMessage = "验证码不正确")]
        [Display(Name = "验证码")]
        public string VerificationCode { get; set; }
    }
}

在UserController中添加Register() action ,并返回直接返回强类型(RegisterViewModel)视图

/// <summary>
        /// 注册
        /// </summary>
        /// <returns></returns>
        public ActionResult Register()
        {
            return View();
        }

视图

@model Ninesky.Web.Areas.Member.Models.RegisterViewModel

@ {
    ViewBag.Title = "注册";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

@using(Html.BeginForm()) {@Html.AntiForgeryToken()

    < div class = "form-horizontal" > <h4 > 用户注册 < /h4>
        <hr / > @Html.ValidationSummary(true)

    < div class = "form-group" > @Html.LabelFor(model = >model.UserName, new {@class = "control-label col-md-2"
    }) < div class = "col-md-10" > @Html.EditorFor(model = >model.UserName)@Html.ValidationMessageFor(model = >model.UserName) < /div>
        </div >

    <div class = "form-group" > @Html.LabelFor(model = >model.DisplayName, new {@class = "control-label col-md-2"
    }) < div class = "col-md-10" > @Html.EditorFor(model = >model.DisplayName)@Html.ValidationMessageFor(model = >model.DisplayName) < /div>
        </div >

    <div class = "form-group" > @Html.LabelFor(model = >model.Password, new {@class = "control-label col-md-2"
    }) < div class = "col-md-10" > @Html.EditorFor(model = >model.Password)@Html.ValidationMessageFor(model = >model.Password) < /div>
        </div >

    <div class = "form-group" > @Html.LabelFor(model = >model.ConfirmPassword, new {@class = "control-label col-md-2"
    }) < div class = "col-md-10" > @Html.EditorFor(model = >model.ConfirmPassword)@Html.ValidationMessageFor(model = >model.ConfirmPassword) < /div>
        </div >

    <div class = "form-group" > @Html.LabelFor(model = >model.Email, new {@class = "control-label col-md-2"
    }) < div class = "col-md-10" > @Html.EditorFor(model = >model.Email)@Html.ValidationMessageFor(model = >model.Email) < /div>
        </div >

    <div class = "form-group" > @Html.LabelFor(model = >model.VerificationCode, new {@class = "control-label col-md-2"
    }) < div class = "col-md-10" > @Html.EditorFor(model = >model.VerificationCode) < img id = "verificationcode"
    title = "点击刷新"
    src = "@Url.Action("
    VerificationCode ")"
    style = "cursor:pointer" / >@Html.ValidationMessageFor(model = >model.VerificationCode) < /div>
        </div > <div class = "checkbox" > <input type = "checkbox"
    checked = "checked"
    required / >我同意 < a href = "#" > 《用户注册协议》 < /a> 
        </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>
}
<script type="text/javascript">
    $("#verificationcode ").click(function () {
        $("#verificationcode ").attr("
    src ", "@Url.Action("VerificationCode") ? " + new Date());
    })
</script>

@section Scripts {
    @Scripts.Render("~ / bundles / jqueryval ")
}"

再在用户控制器中添加public ActionResult Register(RegisterViewModel register)用来处理用户提交的注册数据

[HttpPost][ValidateAntiForgeryToken]
public ActionResult Register(RegisterViewModel register) {
    if (TempData["VerificationCode"] == null || TempData["VerificationCode"].ToString() != register.VerificationCode.ToUpper()) {
        ModelState.AddModelError("VerificationCode", "验证码不正确");
        return View(register);
    }
    if (ModelState.IsValid) {

        if (userService.Exist(register.UserName)) ModelState.AddModelError("UserName", "用户名已存在");
        else {
            User _user = new User() {
                UserName = register.UserName,
                //默认用户组代码写这里
                DisplayName = register.DisplayName,
                Password = Security.Sha256(register.Password),
                //邮箱验证与邮箱唯一性问题
                Email = register.Email,
                //用户状态问题
                Status = 0,
                RegistrationTime = System.DateTime.Now
            };
            _user = userService.Add(_user);
            if (_user.UserID > 0) {
                return Content("注册成功!");
                //AuthenticationManager.SignIn();
            }
            else {
                ModelState.AddModelError("", "注册失败!");
            }
        }
    }
    return View(register);
}

代码中很多根用户设置相关的内容先不考虑,等做到用户设置时在会后来修改。注册失败时返回视图并显示错误;成功时返回视图注册成功,等下次做用户登录时可以让用户注册完毕直接进行登录。在运行项目,浏览器地址中输入 http://localhost:52051/Member/User/register ,看看效果。

image

点击注册,注册成功。

四、总结

一个简单的用户注册完成了,主要有验证码、sha256加密、注册视图模型、验证用户提交数据并保存等步骤。后面就是用户注册,注册会用到ClaimsIdentity和HttpContext.GetOwinContext().Authentication.SignIn();