http://blog.csdn.net/hanqilin/article/details/15811235
模板创建示例项目
MVC的基础内容我就不说了,入门建议看看官方的MvcMovie示例。
提供个链接(共9篇):http://www.cnblogs.com/powertoolsteam/archive/2012/11/01/2749906.html
打开VS2012,【新建项目】,选择【ASP.NET MVC 4 Web应用程序】,名称叫MyMvc(这随便取,但常规是公司.项目的命名空间),按【确定】,模板选【Internet应用程序】(右边有说明文字:带有使用窗体身份验证的帐户控制器的默认 ASP.NET MVC 4 项目。是的,就用它自带的身份验证,而且它还支持OAuth),其它默认,按【确定】完成项目的创建。然后点运行可以看看效果如下图:
图1 初始主页
可以看到这个模板示例已经实现了一些基本功能,包括注册和登录,可以试着注册一下(这里注册为wood),成功后自动转为登录状态,见下图:
图2 登录状态
点击wood这个账号名,可进入账户管理页面,如下图:
图3 管理界面
可以看到Internet Application模板创建出的示例省掉了我们很多初始化工作,而往往我看到的一些项目例子都是自己重新实现一次账户管理(包含注册、登录、维护)。当然,这是通用的,想要添加自定义用户信息:Email、密保问题、密保答案、注册时间,上次登录时间等,那就要对代码进行改动。在改动之前,我们先做几个事情:
1、改动WebConfig的数据库连接(这不是必须的)
打开WebConfig,找到connectString节点,可以看到示例默认创建一个LocalDB数据库,这个不是很直观,修改DefaultConnection连接字符串即可指向我们自己的数据库(安装VS2012后自带的SQLEXPRESS,本来我想换成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字段名称和登录名字段名称,已经默认定义,可以自行定义。至于还有几个表,有什么用,就是后续要说明的内容。
2、OAuth相关(知识点介绍,可跳过)
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就是了,功能很强大,其方法描述如下。
3、代码改造扩展信息
打开AccountController.cs,找到Register方法,可以看到注册项调用了上面所说的WebSecurity实现用户创建和登录,
WebSecurity.CreateUserAndAccount(model.UserName, model.Password);
WebSecurity.Login(model.UserName, model.Password);
我们要好好利用,但不需要改动它们,要添加自己的用户信息,主要是修改示例自动生成的两个文件:AccountModels.cs、AccountController.cs.古有曹植七步成诗,这里也七步完成改造。
第一步:在AccountModels.cs, 增加一个名为ExtraUserInfo的新类。这个类代表了将在数据库创建的新表。
[Table("ExtraUserInfo")] public class ExternalUserInfo { [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 : ExternalUserInfo
第二步:在UsersContext类里,增加一静态UsersContext变量,这使得我们每次调用的时候不需要都创建实例,减少资源浪费,同时创建一个DbSet属性ExternalUserInfos,如下所示。
public class UsersContext : DbContext { public static UsersContext Instance = new UsersContext(); public UsersContext() : base("DefaultConnection") { } public DbSet<UserProfile> UserProfiles { get; set; } public DbSet<ExternalUserInfo> ExternalUserInfos { get; set; } }
第三步:创建【Domain】文件夹,其下创建【Repository】文件夹,文件夹下创建一个仓储接口:
public interface IBaseRepository<T> where T : class { bool Add(T entity); bool Delete(T entity); bool Delete(int id); T Find(int id); IQueryable<T> Load(Func<T, bool> whereLambda); IQueryable<T> LoadPage<S>(int pageIndex, int pageSize, out int total, Func<T, bool> whereLambda, bool isAsc, Func<T, S> orderByLambda); bool Update(T entity); }
第四步:创建仓储基类,实现接口(注:其中Delete和Find我定义为virtual方式,是为了适应不同的表结构,如果每个表统一规范ID为Key,可以采用反射的方式直接在基类实现好)
public class BaseRepository<T> : IBaseRepository<T> where T : class { public ResponseDbContext dbContext = ResponseDbContext.Instance; // 实现对数据库的添加功能,添加实现EF框架的引用 public bool Add(T entity) { dbContext.Entry<T>(entity).State = EntityState.Added; //下面的写法统一 return dbContext.SaveChanges() > 0; } //实现对数据库的修改功能 public bool Update(T entity) { dbContext.Set<T>().Attach(entity); dbContext.Entry<T>(entity).State = EntityState.Modified; return dbContext.SaveChanges() > 0; } //实现对数据库的删除功能 public bool Delete(T entity) { dbContext.Set<T>().Attach(entity); dbContext.Entry<T>(entity).State = EntityState.Deleted; return dbContext.SaveChanges() > 0; } public virtual bool Delete(int id) { return false; } public virtual T Find(int id){ return null; } //实现对数据库的查询 --简单查询 public IQueryable<T> Load(Func<T, bool> whereLambda) { return dbContext.Set<T>().Where<T>(whereLambda).AsQueryable(); } /// <summary> /// 实现对数据的分页查询 /// </summary> /// <typeparam name="S">按照某个类进行排序</typeparam> /// <param name="pageIndex">当前第几页</param> /// <param name="pageSize">一页显示多少条数据</param> /// <param name="total">总条数</param> /// <param name="whereLambda">取得排序的条件</param> /// <param name="isAsc">如何排序,根据倒叙还是升序</param> /// <param name="orderByLambda">根据那个字段进行排序</param> /// <returns></returns> public IQueryable<T> LoadPage<S>(int pageIndex, int pageSize, out int total, Func<T, bool> whereLambda, bool isAsc, Func<T, S> orderByLambda) { var temp = dbContext.Set<T>().Where<T>(whereLambda); total = temp.Count(); //得到总的条数 //排序,获取当前页的数据 if (isAsc) { temp = temp.OrderBy<T, S>(orderByLambda) .Skip<T>(pageSize * (pageIndex - 1)) //越过多少条 .Take<T>(pageSize).AsQueryable(); //取出多少条 } else { temp = temp.OrderByDescending<T, S>(orderByLambda) .Skip<T>(pageSize * (pageIndex - 1)) //越过多少条 .Take<T>(pageSize).AsQueryable(); //取出多少条 } return temp.AsQueryable(); } }
第五步:创建用户管理仓储类继承基类。其实数据的增删改查,通过数据上下文DbContext进行处理就好了,每个表对应其下的DbSet,这样我们每个model对应的操作,直接通过继承基类就可以实现了。
public class UserRepository : BaseRepository<UserProfile> { public override UserInfo Find(int id) { return dbContext.UserProfiles.SingleOrDefault(u => u.UserId== id); } }
public class ExtraUserInfoRepository:BaseRepository<ExtraUserInfo> { public override ExtraUserInfo Find(int id) { return dbContext.ExtraUserInfos.SingleOrDefault(u => u.ID == id); } }
创建完成后就是以下目录结构:
第六步:修改注册方法,改为如下:(注:其实UserRepository和ExtraUserInfo可以抽调为全局变量,供其它方法调用,在此为了演示写在方法内部)
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public ActionResult Register(RegisterModel model) { if (ModelState.IsValid) { // 尝试注册用户 try { UserRepository userRsy = new UserRepository(); UserInfo userModel = userRsy.Find(model.UserId); if (userModel != null) { ModelState.AddModelError("", "当前用户已存在,请重新选择"); return View(model); } ExtraUserInfo extraUserModel = new ExtraUserInfo { UserId = model.UserId, GroupId = model.GroupId, Gender = model.Gender, Email = model.Email, SecurityQuestion = model.SecurityQuestion, SecurityAnswer = model.SecurityAnswer, RegTime = model.RegTime, LastLoginTime = model.LastLoginTime }; ExtraUserInfoRepository extraUserRsy = new ExtraUserInfoRepository(); if (extraUserRsy.Add(extraUserModel)) { 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.ExtraUserInfo,数据也有(注:为了符合个人习惯,我后面是把UserProfile改为了UserInfo,其字段UserId改为ID)
至此基本完成了注册,登录,管理功能,当然这是简单的,实际项目还有细节要处理。再留意上图,dbo.webpages_Roles和dbo.webpages_UserInRoles还没有派上用途呢,下一篇将讲解怎么利用它实行权限管理。