ASP.NET Identity教程一:(配置 ASP.NET Identity)-从这往后是我系列教程(mvc5+ef+adminlte)的开始。请顺着看。
ASP.NET Identity 提供了许多的 API接口供使用,但是都是以继承的方式来实现的。因此,在使用某个功能之前,需要继承该功能的基类。
1. 继承 IdentityUser
用户是 ASP.NET Identity 其它操作的基础,所以在使用 ASP.NET Identity 之前,首先要创建 User 类。在 ASP.NET Identity 中,用户的基本信息是很容易扩展的,也就是可以添加我们自己的用户属性,如 QQ、微信、爱好、职业等等。
IdentityUser类是定义在Microsoft.AspNet.Identity.EntityFramework名称空间下的:
namespace Microsoft.AspNet.Identity.EntityFramework { public class IdentityUser : IdentityUser<string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>, IUser, IUser<string> { public IdentityUser(); public IdentityUser(string userName); } }
从定义上看,IdentityUser 类是继承了泛型的 IdentityUser<>、IUser 和泛型的 IUser<>接口。而 IUser 接口又是继承了 IUser<string>泛型接口。如下代码:
namespace Microsoft.AspNet.Identity { // // 摘要: // Minimal interface for a user with id and username // // 类型参数: ASP.NET Identity 的配置及在 MVC5 框架中的应用详细开发实战培训教程 22 / 136 一都 (Yidosoft.cn) 软件 // TKey: public interface IUser<out TKey> { // // 摘要: // Unique key for the user TKey Id { get; } // // 摘要: // Unique username string UserName { get; set; } } }
在泛型的 IUser 接口中,定义了 Id 和 UserName 属性,并且是唯一值。而更多的用户属性是定义在泛型的 IdentityUser 基类中的。如下代码:
namespace Microsoft.AspNet.Identity.EntityFramework { public class IdentityUser<TKey, TLogin, TRole, TClaim> : IUser<TKey> where TLogin : IdentityUserLogin<TKey> where TRole : IdentityUserRole<TKey> where TClaim : IdentityUserClaim<TKey> { public IdentityUser(); public virtual int AccessFailedCount { get; set; } public virtual ICollection<TClaim> Claims { get; } public virtual string Email { get; set; } public virtual bool EmailConfirmed { get; set; } public virtual TKey Id { get; set; } public virtual bool LockoutEnabled { get; set; } public virtual DateTime? LockoutEndDateUtc { get; set; } public virtual ICollection<TLogin> Logins { get; } public virtual string PasswordHash { get; set; } public virtual string PhoneNumber { get; set; } public virtual bool PhoneNumberConfirmed { get; set; } public virtual ICollection<TRole> Roles { get; } public virtual string SecurityStamp { get; set; } public virtual bool TwoFactorEnabled { get; set; } public virtual string UserName { get; set; } } }
在这里可以看到 User 的所有自带的相关属性,另外,还可以看到 IdentityUser 还受到了 IdentityUserLogin、IdentityUserRole、IdentityUserClaim 的约束。
现在我们在“Yidosoft.Identity”项目的“Models”文件夹下添加一个名称为“User”的类。然后继承 IdentityUser 基类。代码如下:
using Microsoft.AspNet.Identity.EntityFramework; namespace Yidosoft.Identity.Models { public class User : IdentityUser { } }
而我们自定义的扩展的用户属性,只需要在 User 类中编写即可。如下代码,我们添加用户的 QQ 和微信号属性:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using Microsoft.AspNet.Identity.EntityFramework; using System.Threading.Tasks; using Microsoft.AspNet.Identity; using System.Security.Claims; namespace jsdhh2.Models { public class User: IdentityUser { public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<User> manager) { // 请注意,authenticationType 必须与 CookieAuthenticationOptions.AuthenticationType 中定义的相应项匹配 var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie); // 在此处添加自定义用户声明 return userIdentity; } #region 添加字段 public virtual string WX { get; set; } public virtual string QQ { get; set; } public virtual DateTime CreateTime { get; set; } public virtual int? DepartmentId { get; set; } public virtual string Address { get; set; } public virtual int? Gender { get; set; } public virtual DateTime? BirthDate { get; set; } public virtual string TheHour { get; set; } public virtual DateTime? DetailedTime { get; set; } public virtual string RealName { get; set; } public virtual int? SpouseId { get; set; } public virtual string HeaderPic { get; set; } #endregion } }
这样在使用 Entity Framework 的 Code First 特性时,就会将这些自定义的属性映射为相应的字段。
2. 继承 IdentityDbContext<User>
ASP.NET Identity 是使用 Entity Framework 来操作并与数据库交互的。所以需要创建数据上下文类,并且该类必须是继承了 IdentityDbContext<T>类的,并且泛型类中的类型参数 T 必须是在 4.1 节中创建的 User 类,User 类必须是继承了IdentityUser 类的。它们之间都存在约束,可见这也呈现的编程的严谨性。
这里也将数据上下文类创建在“Models”文件夹中,当然如果你的项目具有分层结构,则可以放在合适的层中,这里重点是讲解 ASP.NET Identity,不存在分层。
在项目中新建一个文件夹DAL。在DAL中新建一个名称为“IdentityDbContext”的类文件,并继承 IdentityDbContext<User>类。IdentityDbContext<T>也是定义在:Microsoft.AspNet.Identity.EntityFramework 名称空间下的。如下代码:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using jsdhh2.Models; using Microsoft.AspNet.Identity.EntityFramework; namespace jsdhh2.DAL { public class IdentityDbContext : IdentityDbContext<User> { } }
然后在构造函数中传入数据库连接字符串或 web.config 配置文件中配置的连接字符串的名称。建议使用连接字符串的名称,这样以后变更了数据库连接字符串,直接在 web.config 文件中修改即可。最终的代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using jsdhh2.Models; using Microsoft.AspNet.Identity.EntityFramework; namespace jsdhh2.DAL { public class IdentityDbContext : IdentityDbContext<User> { public IdentityDbContext() : base("MyOaContent", throwIfV1Schema: false) { } /// <summary> /// 实例化一个IdentityDbContext。 /// </summary> /// <returns></returns> public static IdentityDbContext Create() { return new IdentityDbContext(); } } }
在此代码中,IdentityDbContext 构造函数使用基类的构造函数传入参数。基类构造函数代码如下:
namespace Microsoft.AspNet.Identity.EntityFramework { public class IdentityDbContext<TUser> : IdentityDbContext<TUser, IdentityRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim> where TUser : IdentityUser { public IdentityDbContext(); public IdentityDbContext(string nameOrConnectionString); public IdentityDbContext(DbCompiledModel model); public IdentityDbContext(string nameOrConnectionString, bool throwIfV1Schema); public IdentityDbContext(DbConnection existingConnection, bool contextOwnsConnection); public IdentityDbContext(string nameOrConnectionString, DbCompiledModel model); public IdentityDbContext(DbConnection existingConnection, DbCompiledModel model, bool contextOwnsConnection); } }
这里使用的是第 4 个构造函数,包含两个参数:一个是数据库连接字符串的名称或连接字符串,这里使用 web.config 配置文件中的名称 YDDbConnection。另一个是布尔类型 throwIfV1Schema 参数,表示如果模式匹配 Identity 1.0.0,是否将抛出一个异常,这里设置为 false。另外,还有一个静态方法 Create(),用来实例化 Identity DbContext 数据上下文。
3. 配置数据库连接字符串
打开“Yidosoft.Identity”项目中的 web.config 文件,在<configuration>节点添加<connectionStrings>节点。如下代码:
<configuration> <configSections> <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /> </configSections> <appSettings> <add key="webpages:Version" value="3.0.0.0" /> <add key="webpages:Enabled" value="false" /> <add key="ClientValidationEnabled" value="true" /> <add key="UnobtrusiveJavaScriptEnabled" value="true" /> </appSettings> <connectionStrings> <add name="MyOaContent" connectionString="Data Source=192.168.1.206;Initial Catalog=YDIdentityDb;uid=sa;pwd=12345678" providerName="System.Data.SqlClient"/> </connectionStrings> </configuration>
注意:这里的 name 属性的值一定要与在 IdentityDbContext 构造函数中配置的名称完全一致。并且数据库的名称可以任意起,不必事先在数据库中创建好,EntityFramework 会帮我们创建。
4. 继承 UserManager<User>
UserManager<T>泛型类是一个用来管理用户的类,T 就是我们在 4.1 节中创建的User 类,并且 User 类必须继承 IdentityUser 类。同样我们也需要创建一个类并继承该基类。
在“Yidosoft.Identity”项目的“App_Start”文件夹中创建一个名称为“IdentityConfig”的类文件,并将自动创建的“IdentityConfig”类删除掉,名称空间修改为:Yidosoft.Identity。“IdentityConfig.cs”用来配置与 Identity 相关的操作。然后添加一个名称为“UserManager”的类,并继承 UserManager<User>基类。代码如下:(备注:要注意把ROLES的先注释“)
实际上用户的操作都是定义在 IUserStore<User>接口中的,所以必须在UserManager 构造函数中将 IUserStore<User>作为参数传入。最终的代码修改如
using jsdhh2.Models; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Microsoft.AspNet.Identity.EntityFramework; using System.Threading.Tasks; using Microsoft.Owin.Security; using System.Security.Claims; namespace jsdhh2 { public class EmailService : IIdentityMessageService { public Task SendAsync(IdentityMessage message) { //编写发送邮件的代码 //Common.SendMail("smtp.126.com", 25, "lbj$4561200", "yidosoft@126.com", message.Destination, message.Subject, message.Body); return Task.FromResult(0); } } public class SmsService : IIdentityMessageService { public Task SendAsync(IdentityMessage message) { // 在此处插入 SMS 服务可发送短信。 return Task.FromResult(0); } } /// <summary> /// 配置用户管理器 /// </summary> public class UserManager : UserManager<User> { public UserManager(IUserStore<User> store) : base(store) { } public static UserManager Create(IdentityFactoryOptions<UserManager> options, IOwinContext context) { var manager = new UserManager(new UserStore<User>(context.Get<DAL.IdentityDbContext>())); manager.EmailService = new EmailService(); var dataProtectionProvider = options.DataProtectionProvider; if (dataProtectionProvider != null) { manager.UserTokenProvider = new DataProtectorTokenProvider<User>(dataProtectionProvider.Create("EmailConfirmation")); } return manager; } } /// <summary> /// 配置应用程序登录管理器 /// </summary> public class SignInManager : SignInManager<User, string> { public SignInManager(UserManager userManager, IAuthenticationManager authenticationManager) : base(userManager, authenticationManager) { } public override Task<ClaimsIdentity> CreateUserIdentityAsync(User user) { return user.GenerateUserIdentityAsync((UserManager)UserManager); } public static SignInManager Create(IdentityFactoryOptions<SignInManager> options, IOwinContext context) { return new SignInManager(context.GetUserManager<UserManager>(), context.Authentication); } } /// <summary> /// 配置角色管理器 /// </summary> //public class RoleManager : RoleManager<Role> //{ // public RoleManager(RoleStore<Role> store) : base(store) { } // public static RoleManager Create(IdentityFactoryOptions<RoleManager> options, IOwinContext context) // { // var manager = new RoleManager(new RoleStore<Role>(context.Get<Models.IdentityDbContext>())); // return manager; // } //} }
这里还有一个静态的 Create()方法,用于实例化 UserManager 类。用来管理用户的所有相关操作。
5. 注册 UserManager
配置身份验证主要是使用 CreatePerOwinContext()方法配置数据库上下文和用户管理器,以便为每个请求使用单个实例。
在“Yidosoft.Identity”项目的“App_Start”文件夹中添加一个名称为“Startup.Auth.cs”的部分(partial)类文件,类名为“Startup”,作为 OWIN Startup 类的一部分,名称空间修改为:Yidosoft.Identity。然后编写如下代码:
using System; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Microsoft.Owin.Security.Cookies; using Microsoft.Owin.Security.Google; using Owin; using jsdhh2.Models; using jsdhh2.DAL; namespace jsdhh2 { public partial class Startup { // For more information on configuring authentication, please visit https://go.microsoft.com/fwlink/?LinkId=301864 public void ConfigureAuth(IAppBuilder app) { // 配置数据库上下文、用户管理器和登录管理器,以便为每个请求使用单个实例 //app.CreatePerOwinContext(ApplicationDbContext.Create); //app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create); //app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create); app.CreatePerOwinContext(IdentityDbContext.Create); //注册UserManger管理器 app.CreatePerOwinContext<UserManager>(UserManager.Create); //注册SignInManager管理器 app.CreatePerOwinContext<SignInManager>(SignInManager.Create); //注册RoleManager管理器 //app.CreatePerOwinContext<RoleManager>(RoleManager.Create); app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/User/Login") }); }
在此代码中,还使用了 DefaultAuthenticationTypes.ApplicationCookie 配置默认的认证类型是ApplicationCookie。并且使用LoginPath指定认证失败时转向到的URL。
6. 创建 OWIN Startup
备注,我一般以新建WEB项目时,选了个人验证方式,他自动生成了这些类,不用新建。
在 Visual Studio 2015 中,是可以直接创建“OWIN Startup 类”的。右击“Yidosoft.Identity”项目,如图 4-1 所示:
在图 4-1 中点击“添加”“OWIN Startup 类”,如图 4-2 所示:
由于我们在“Startup.Auth.cs”文件中创建了部分类 Startup,所以在“Startup.cs”文件中的“Startup”类也必须是部分类。完整的代码如下:
using System; using System.Threading.Tasks; using Microsoft.Owin; using Owin; [assembly: OwinStartupAttribute(typeof(jsdhh2.Startup))] namespace jsdhh2 { public partial class Startup { public void Configuration(IAppBuilder app) { ConfigureAuth(app); } } }
这里只是在Configuration()方法中调用在Startup.Auth.cs中编写的ConfigureAuth()方法,并将 IAppBuilder 类型的参数传入。创建的 OWIN Startup 类作为身份验证的启动类。主要是与 OWIN 相关的配置。
5. 创建控制器
在“Yidosoft.Identity”项目的“Controllers”文件夹上右击,点击“添加”“控制器”,如图 5-2 所示:
选择“MVC 5 控制器-空”,点击“添加”按钮。如图 5-3 所示:
中将控制器名称修改为“UserController”,点击“添加”按钮。
在控制器中可以使用 HttpContext.GetOwinContext().Get()方法将使用CreatePerOwinContext()方法注册的IdentityDbContext 和UserManager的实例获取。
然后就可以在控制器中使用了。如下代码:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using jsdhh2.Models; using System.Threading.Tasks; using Microsoft.AspNet.Identity.Owin; using Microsoft.AspNet.Identity; using jsdhh2.ViewModels; using jsdhh2; namespace Yidosoft.Identity.Controllers { public class UserController : Controller { private UserManager _userManager; public UserManager UserManager { get { return _userManager ?? HttpContext.GetOwinContext().GetUserManager<UserManager>(); } private set { _userManager = value; } } private SignInManager _signInManager; public SignInManager SignInManager { get { return _signInManager ?? HttpContext.GetOwinContext().Get<SignInManager>(); } private set { _signInManager = value; } } // GET: User public ActionResult Index() { return View(); } [HttpGet] [AllowAnonymous] public ActionResult Add() { return View(); } [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Add(AddUserViewModel addUserModel) { if (ModelState.IsValid) { var _creatTime = DateTime.Now; var _headerPic = "/Content/noheaderpic.png"; var user = new User { UserName = addUserModel.UserName, Email = addUserModel.Email, PhoneNumber = addUserModel.PhoneNumber, CreateTime =_creatTime, HeaderPic = _headerPic }; var result = await UserManager.CreateAsync(user, addUserModel.Password); if (result.Succeeded) { //登录 await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); // 发送包含此链接的电子邮件 string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id); var callbackUrl = Url.Action("ConfirmEmail", "User", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme); await UserManager.SendEmailAsync(user.Id, "确认你的帐户", "请通过单击 <a href=\"" + callbackUrl + "\">这里</a>来确认你的帐户"); //return RedirectToAction("List", "User"); return RedirectToAction("Index", "Home"); } AddErrors(result); } return View(addUserModel); } //[HttpGet] //public ActionResult List() //{ // var users = UserManager.Users.ToList(); // return View(users); //} //[HttpGet] //public ActionResult Edit(string id) //{ // if (string.IsNullOrWhiteSpace(id)) // { // return new HttpStatusCodeResult(System.Net.HttpStatusCode.BadRequest); // } // User user = UserManager.FindById(id); // if (user == null) // { // return HttpNotFound(); // } // var editUserViewModel = new EditUserViewModel() // { // Email = user.Email, // QQNumber = user.QQNumber, // WechatNumber = user.WechatNumber // }; // return View(editUserViewModel); //} //[HttpPost] //[ValidateAntiForgeryToken] //public async Task<ActionResult> Edit(string id, EditUserViewModel editUserViewModel) //{ // if (ModelState.IsValid && !string.IsNullOrEmpty(id)) // { // User user = UserManager.FindById(id); // user.Email = editUserViewModel.Email; // user.QQNumber = editUserViewModel.QQNumber; // user.WechatNumber = editUserViewModel.WechatNumber; // user.UserName = editUserViewModel.Email; // var result = await UserManager.UpdateAsync(user); // if (result.Succeeded) // { // return RedirectToAction("List", "User"); // } // AddErrors(result); // } // return View(editUserViewModel); //} ///// <summary> ///// 登录 ///// </summary> ///// <param name="returnUrl"></param> ///// <returns></returns> //[HttpGet] //[AllowAnonymous] //public ActionResult Login(string returnUrl) //{ // ViewBag.ReturnUrl = returnUrl; // return View(); //} ///// <summary> ///// 登录 ///// </summary> ///// <param name="model"></param> ///// <param name="returnUrl"></param> ///// <returns></returns> //[HttpPost] //[AllowAnonymous] //[ValidateAntiForgeryToken] //public async Task<ActionResult> Login(LoginViewModel model, string returnUrl) //{ // if (!ModelState.IsValid) // { // return View(model); // } // // 这不会计入到为执行帐户锁定而统计的登录失败次数中 // // 若要在多次输入错误密码的情况下触发帐户锁定,请更改为 shouldLockout: true // var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false); // switch (result) // { // case SignInStatus.Success: // return RedirectToLocal(returnUrl); // case SignInStatus.LockedOut: // return View("Lockout"); // case SignInStatus.RequiresVerification: // return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe }); // case SignInStatus.Failure: // default: // ModelState.AddModelError("", "无效的登录尝试。"); // return View(model); // } //} ///// <summary> ///// 邮箱验证 ///// </summary> ///// <param name="userId"></param> ///// <param name="code"></param> ///// <returns></returns> //[HttpGet] //[AllowAnonymous] //public async Task<ActionResult> ConfirmEmail(string userId, string code) //{ // if (userId == null || code == null) // { // return View("Error"); // } // var result = await UserManager.ConfirmEmailAsync(userId, code); // return View(result.Succeeded ? "ConfirmEmail" : "Error"); //} ///// <summary> ///// 忘记密码 ///// </summary> ///// <returns></returns> //[AllowAnonymous] //public ActionResult ForgotPassword() //{ // return View(); //} ///// <summary> ///// 忘记密码 ///// </summary> ///// <param name="model"></param> ///// <returns></returns> //[HttpPost] //[AllowAnonymous] //[ValidateAntiForgeryToken] //public async Task<ActionResult> ForgotPassword(ForgotPasswordViewModel model) //{ // if (ModelState.IsValid) // { // var user = await UserManager.FindByNameAsync(model.Email); // if (user == null) // { // return View("Error"); // } // //发送包含此链接的电子邮件 // string code = await UserManager.GeneratePasswordResetTokenAsync(user.Id); // var callbackUrl = Url.Action("ResetPassword", "User", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme); // await UserManager.SendEmailAsync(user.Id, "重置密码", "请通过单击 <a href=\"" + callbackUrl + "\">此处</a>来重置你的密码"); // return RedirectToAction("ForgotPasswordConfirmation", "User"); // } // // 如果我们进行到这一步时某个地方出错,则重新显示表单 // return View(model); //} //[AllowAnonymous] //public ActionResult ForgotPasswordConfirmation() //{ // return View(); //} ///// <summary> ///// 重置密码 ///// </summary> ///// <param name="code"></param> ///// <returns></returns> //[HttpGet] //[AllowAnonymous] //public ActionResult ResetPassword(string code) //{ // return code == null ? View("Error") : View(); //} ///// <summary> ///// 重置密码 ///// </summary> ///// <param name="model"></param> ///// <returns></returns> //[HttpPost] //[AllowAnonymous] //[ValidateAntiForgeryToken] //public async Task<ActionResult> ResetPassword(ResetPasswordViewModel model) //{ // if (!ModelState.IsValid) // { // return View(model); // } // var user = await UserManager.FindByNameAsync(model.Email); // if (user == null) // { // return View("Error"); // } // var result = await UserManager.ResetPasswordAsync(user.Id, model.Code, model.Password); // if (result.Succeeded) // { // return RedirectToAction("ResetPasswordConfirmation", "User"); // } // AddErrors(result); // return View(); //} //[AllowAnonymous] //public ActionResult ResetPasswordConfirmation() //{ // return View(); //} private void AddErrors(IdentityResult result) { foreach (var error in result.Errors) { ModelState.AddModelError("", error); } } //private ActionResult RedirectToLocal(string returnUrl) //{ // if (Url.IsLocalUrl(returnUrl)) // { // return Redirect(returnUrl); // } // return RedirectToAction("Index", "Home"); //} } }
有点无语的是 public SignInManager SignInManager 老出错,以我的水平实在不知道错在哪,后来,把命名空间乱搞一下,成功了
using jsdhh2; namespace Yidosoft.Identity.Controllers
在控制器的方法中,就可以使用 UserManager 属性进行用户管理了。
6. 添加客户端验证(这步我也没安装自带的就有)
此步骤的操作也可以在空的 MVC 5 项目中第一次添加视图(也就是后面讲解的7.1.3 节)之后再操作。这里作为独立的一个章节拿出来讲解。在 ASP.NET MVC 5 空项目中,在第一次添加视图时,Visual Studio 2015 会自动添加一些与 UI 相关的文件,包括 Bootstrap、Jquery、modernizr 等。但是就没有添加与客户端验证相关的 JS 文件。默认情况下,ASP.NET MVC 5 是支持客户端和服务器端双重数据验证的。服务器端数据验证默认就支持。现在我们添加与客户端相关的 JS 文件。
ASP.NET MVC 5 的客户端数据验证是需要 jquery.validate.js 和jquery.validate.unobtrusive.js 这两个 JS 文件支持的。“Microsoft.jQuery.Unobtrusive.Validation”,这是由微软在 jQuery 的基础上编写的在 ASP.NET MVC 5 下使用的验证 JS 文件。可以通过 NuGet 包管理器安装这 2 个验证文件。如图 6-1 所示:
圈住的两个包安装。安装完成后如图 6-2 所示:
安装成功后,会在项目的“Scripts”文件夹中添加图 6-2 红线标注的 5 个文件,其
中还包括了 min.js 类型的文件。
打开“_Layout.cshtml”视图,将:
<script src="~/Scripts/jquery.validate.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.js"></script>
放在原 JS 文件的底部。如图 6-3 所示:
由于 jquery.validate.js 是依赖于 jquery.js 文件的,所以要放在 jquery.js 文件后面
才会生效
_Layout布局上加上:
@Scripts.Render("~/bundles/jquery") @Scripts.Render("~/bundles/bootstrap") @RenderSection("scripts", required: false) <script src="~/Scripts/jquery.validate.js"></script> <script src="~/Scripts/jquery.validate.unobtrusive.js"></script>