PS:本文不是大牛的经验之谈,而确确实实是菜鸟的学习归纳,虽说也有几年开发经验,但对MVC也是只闻其声,不见其人,最近闲下来也就开始接触它。学东西以项目驱动效率最高,没项目的话亲手敲下代码做下demo也行,在网上搜了一轮,都是理论一堆,实例较少,或者实例本来简单偏却复杂化,是的,我力求东西简单,同时做为基础教程,也不打算引入JS框架,权作抛砖引石之用,真要用的话视乎项目调整就是了。因为是菜鸟,难免写得有不足之处,希望大牛路过给点建议,新人路过给点鼓励。
感谢:本文引用了张善友、老A、网魂小兵、洞庭夕照、蒋叶湖、ppy2790等人文章内容
模板创建示例项目
MVC的基础内容我就不说了,入门建议看看官方的MvcMovie示例。
打开VS2012,【新建项目】,选择【ASP.NET MVC 4 Web应用程序】,名称叫MyMvc(这随便取,但常规是公司.项目的命名空间),按【确定】,模板选【Internet应用程序】(右边有说明文字:带有使用窗体身份验证的帐户控制器的默认 ASP.NET MVC 4 项目。是的,就用它自带的身份验证,而且它还支持OAuth),其它默认,按【确定】完成项目的创建。然后点运行可以看看效果如下图:
图1 初始主页
可以看到这个模板示例已经实现了一些基本功能,包括注册和登录,可以试着注册一下(这里注册为wood),成功后自动转为登录状态,见下图:
图2 登录状态
点击wood这个账号名,可进入账户管理页面,如下图:
打开WebConfig,找到connectString节点,可以看到示例默认创建一个LocalDB数据库,这个不是很直观,修改DefaultConnection连接字符串即可指向我们自己的数据库(本来我想换成mySql的,但是CodeFirst报错,搜索了解决不了),改后别忘了启动SQL Server服务
<connectionStrings> <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=aspnet-MyMvc-20130905000410;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\aspnet-MyMvc-20130905000410.mdf" providerName="System.Data.SqlClient" /> </connectionStrings>
<connectionStrings> <add name="DefaultConnection" connectionString="Data Source=STREAM-PC\SQLEXPRESS;Initial Catalog=MyMvc;Integrated Security=SSPI;Pooling=False;Persist Security Info=true" providerName="System.Data.SqlClient" /> </connectionStrings>
改了之后,再次编译运行,重新注册一次,发现能注册成功的。这时,我们在VS里面,点击菜单【视图】-【SQL server对象资源管理器】,右键点击【SQL Server】节点,选择【添加SQL Server】完成后,发现已经多了MyMvc的数据库,展开后可以看到自动创建了几个表,其中dbo.UserProfile就是存放我们的账户名的,如下图:
图4 数据库表
明眼的人一看,发现有ID和账户名,密码呢?密码去哪了?它存放在dbo.webpages_Membership里,至于为什么这样的规则,打开Filters文件夹下的 InitializeSimpleMembershipAttribute.cs 文件的41行。
WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true);
可以看到,第二、三、四个参数分别为用户表名称、ID字段名称和登录名字段名称,已经默认定义,可以自行定义。至于还有几个表,有什么用,就是后续要说明的内容。
OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。同时,任何第三方都可以使用OAUTH认证服务,任何服务提供商都可以实现自身的OAUTH认证服务,因而OAUTH是开放的。业界提供了OAUTH的多种实现如PHP、JavaScript,Java,Ruby等各种语言开发包,大大节约了程序员的时间,因而OAUTH是简易的。互联网很多服务如Open API,很多大公司如Google,Yahoo,Microsoft等都提供了OAUTH认证服务,这些都足以说明OAUTH标准逐渐成为开放资源授权的标准。
简单说明,大家都应该试过注册一个网站的时候,可以选择用人人网、新浪微博、QQ账号登录吧?其实它们也就实现了OAuth规范,VS打开解决方案,发现在App_Start目录创建了一个名为AuthConfig.cs的文件
打开可以看到以下内容:
public static class AuthConfig { public static void RegisterAuth() { // 若要允许此站点的用户使用他们在其他站点(例如 Microsoft、Facebook 和 Twitter)上拥有的帐户登录, // 必须更新此站点。有关详细信息,请访问 http://go.microsoft.com/fwlink/?LinkID=252166 //OAuthWebSecurity.RegisterMicrosoftClient( // clientId: "", // clientSecret: ""); //OAuthWebSecurity.RegisterTwitterClient( // consumerKey: "", // consumerSecret: ""); //OAuthWebSecurity.RegisterFacebookClient( // appId: "", // appSecret: ""); //OAuthWebSecurity.RegisterGoogleClient(); } }
这些是ASP.NET MVC4带来的新的Membership系统的内容,从该文件名称可以看到,该模板示例默认实现了能让用户用外部提供方的证书(比如Facebook, Twitter, Microsoft,或Google)登陆方式,然后将源自那些提供方的一些信息集成进你的web应用,只是它们都做了注释,所以没有外部提供者被启用,也就是上图3 管理界面最下方提示的内容。假如我们要使用这些证书,只要反注释,填入相应的信息即可,只是这些都不符合中国国情,有兴趣可以尝试通过新浪微博API来测试实现,当成功以后,表dbo.webpages_OAuthMembership就会有记录了,这次我就不做演示了,因为我只想实现普通账户权限验证,而不需要用到外部资源验证。在这里,我们只要好好利用WebSecurity就是了,功能很强大,其方法描述如下。
打开AccountController.cs,找到Register方法,可以看到注册项调用了上面所说的WebSecurity实现用户创建和登录,
WebSecurity.CreateUserAndAccount(model.UserName, model.Password);
WebSecurity.Login(model.UserName, model.Password);
我们要好好利用,但不需要改动它们,要添加自己的用户信息,主要是修改示例自动生成的两个文件:AccountModels.cs、AccountController.cs.古有曹植七步成诗,这里也七步完成改造。
第一步:在AccountModels.cs, 增加一个名为ExtraUserInformation的新类。这个类代表了将在数据库创建的新表。
[Table("ExtraUserInformation")] public class ExternalUserInformation { [Key] [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)] public int Id { get; set; } [Required] public int UserId { get; set; } /// <summary> /// 用户组Id /// </summary> [Display(Name = "用户组Id")] public int GroupId { get; set; } /// <summary> /// Email /// </summary> [Display(Name = "Email", Description = "请输入您常用的Email。")] [Required(ErrorMessage = "×")] public string Email { get; set; } /// <summary> /// 密保问题 /// </summary> [Display(Name = "密保问题", Description = "请正确填写,在您忘记密码时用户找回密码。4-20个字符。")] [Required(ErrorMessage = "×")] [StringLength(20, MinimumLength = 4, ErrorMessage = "×")] public string SecurityQuestion { get; set; } /// <summary> /// 密保答案 /// </summary> [Display(Name = "密保答案", Description = "请认真填写,忘记密码后回答正确才能找回密码。2-20个字符。")] [Required(ErrorMessage = "×")] [StringLength(20, MinimumLength = 2, ErrorMessage = "×")] public string SecurityAnswer { get; set; } /// <summary> /// 注册时间 /// </summary> public DateTime? RegTime { get; set; } /// <summary> /// 上次登录时间 /// </summary> public DateTime? LastLoginTime { get; set; } }
然后把RegisterModel改为继承它,即把
public class RegisterModel
替换为下面(要记得加上[NotMapped],不然codefirst父类映射错误)
[NotMapped] public class RegisterModel : ExternalUserInformation
第二步:在UsersContext类里,增加ExternalUser,为新类创建一个DbSet属性,如下所示。
public class UsersContext : DbContext { public UsersContext() : base("DefaultConnection") { } public DbSet<UserProfile> UserProfiles { get; set; } public DbSet<ExternalUserInformation> ExternalUsers { get; set; } }
第三步:创建【Domain】文件夹,其下创建【Repository】文件夹,文件夹下创建一个仓储接口:
public interface IRepository<TModel> where TModel : class { /// <summary> /// 添加【继承类重写后才能正常使用】 /// </summary> bool Add(TModel Tmodel); /// <summary> /// 更新【继承类重写后才能正常使用】 /// </summary> bool Update(TModel Tmodel); /// <summary> /// 删除【继承类重写后才能正常使用】 /// </summary> bool Delete(int Id); /// <summary> /// 查找指定值【继承类重写后才能正常使用】 /// </summary> TModel Find(int Id); }
第四步:创建仓储基类,实现接口
public abstract class RepositoryBase<TModel> : IRepository<TModel> where TModel : class { public UsersContext dbContext; public RepositoryBase() { dbContext = new UsersContext(); } /// <summary> /// 添加【继承类重写后才能正常使用】 /// </summary> public virtual bool Add(TModel Tmodel) { return false; } /// <summary> /// 更新【继承类重写后才能正常使用】 /// </summary> public virtual bool Update(TModel Tmodel) { return false; } /// <summary> /// 删除【继承类重写后才能正常使用】 /// </summary> public virtual bool Delete(int Id) { return false; } /// <summary> /// 查找指定值【继承类重写后才能正常使用】 /// </summary> public virtual TModel Find(int Id) { return default(TModel); } ~RepositoryBase() { if (dbContext != null) { dbContext.Dispose(); } } }
第五步:创建用户管理仓储类继承基类。其实数据的增删改查,通过数据上下文DbContext进行处理就好了,每个表对应其下的DbSet,关于它,可以去看看MvcMovie。
public class UserRepository : RepositoryBase<RegisterModel> { /// <summary> /// 添加用户 /// </summary> /// <param name="user">用户信息</param> /// <returns></returns> public override bool Add(RegisterModel user) { bool isSuccess = false; if (user != null) { //dbContext.UserProfiles.Add(new UserProfile { UserName = user.UserName }); //dbContext.SaveChanges(); ExternalUserInformation newExtUser = new ExternalUserInformation { UserId = user.UserId, GroupId = user.GroupId, Email = user.Email, SecurityQuestion = user.SecurityQuestion, SecurityAnswer = user.SecurityAnswer, RegTime = user.RegTime, LastLoginTime = user.LastLoginTime }; dbContext.ExternalUsers.Add(newExtUser); isSuccess = dbContext.SaveChanges() > 0; } return isSuccess; } /// <summary> /// 更新用户信息 /// </summary> /// <param name="user"></param> /// <returns></returns> public override bool Update(RegisterModel user) { var userObj = dbContext.ExternalUsers.SingleOrDefault(u => u.UserId == user.UserId); if (userObj == null) return false; userObj = user; if (dbContext.SaveChanges() > 0) return true; else return false; } }
创建完成后就是以下目录结构:
第六步:修改注册方法,改为如下:
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public ActionResult Register(RegisterModel model) { if (ModelState.IsValid) { // 尝试注册用户 try { UserRepository userRsy = new UserRepository(); if (userRsy.Add(model)) { WebSecurity.CreateUserAndAccount(model.UserName, model.Password); WebSecurity.Login(model.UserName, model.Password); return RedirectToAction("Index", "Home"); } else { ModelState.AddModelError("", "在用户注册时,发生了未知错误"); } } catch (MembershipCreateUserException e) { ModelState.AddModelError("", ErrorCodeToString(e.StatusCode)); } } // 如果我们进行到这一步时某个地方出错,则重新显示表单 return View(model); }
第七步:最后就是修改注册页面:Views\Account\Register.cshtml,把扩展信息内容添加上去
<li>@Html.LabelFor(model => model.Email) @Html.EditorFor(model => model.Email) </li> <li> @Html.LabelFor(m => m.SecurityQuestion) @Html.EditorFor(m => m.SecurityQuestion) </li> <li> @Html.LabelFor(m => m.SecurityAnswer) @Html.EditorFor(m => m.SecurityAnswer) </li>
最后,编译运行,可以成功注册,同时数据库多了表dbo.ExtraUserInformation,
至此基本完成了注册,登录,管理功能,当然这是简单的,实际项目还有细节要处理。再留意上图,dbo.webpages_Roles和dbo.webpages_UserInRoles还没有派上用途呢,下一篇将讲解怎么利用它实行权限管理。