返回总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期css
用户实体表明应用的一个用户,它派生自AbpUser类,以下所示:html
public class User : AbpUser<Tenant, User> { //add your own user properties here }
这个类是在安装模块零时建立的。用户存储在数据库的AbpUsers表中。您能够将自定义属性添加到User类(并为更改建立数据库迁移)。前端
AbpUser类定义了一些基本属性。一些属性是:git
还有一些属性,如角色, 权限,租户,设置, IsEmailConfirmed等。检查AbpUser类以获取更多信息。github
AbpUser类是从FullAuditedEntity继承的。这意味着它具备建立,修改和删除审计属性。这也是 软删除。因此,当咱们删除一个用户时,它不会从数据库中删除,只是标记为已删除。ajax
AbpUser类实现了 IMayHaveTenant 过滤器,以便在多租户应用程序中正常工做。数据库
最后,用户的Id被定义为long。bootstrap
UserManager是 为用户执行域逻辑的服务:缓存
public class UserManager:AbpUserManager <Tenant,Role,User> { // ... }
您能够注入并使用UserManager建立,删除,更新用户,授予权限,更改用户角色等等。你能够在这里添加你本身的方法。此外,您能够 根据本身的须要重写AbpUserManager基类的任何方法。async
UserManager被设计为一次为单个租户工做。它适用于当前租户做为默认。咱们来看看UserManager的一些用法:
public class MyTestAppService : ApplicationService { private readonly UserManager _userManager; public MyTestAppService(UserManager userManager) { _userManager = userManager; } public void TestMethod_1() { //Find a user by email for current tenant var user = _userManager.FindByEmail("sampleuser@aspnetboilerplate.com"); } public void TestMethod_2() { //Switch to tenant 42 CurrentUnitOfWork.SetFilterParameter(AbpDataFilters.MayHaveTenant, AbpDataFilters.Parameters.TenantId, 42); //Find a user by email for the tenant 42 var user = _userManager.FindByEmail("sampleuser@aspnetboilerplate.com"); } public void TestMethod_3() { //Disabling MayHaveTenant filter, so we can reach to all users using (CurrentUnitOfWork.DisableFilter(AbpDataFilters.MayHaveTenant)) { //Now, we can search for a user name in all tenants var users = _userManager.Users.Where(u => u.UserName == "sampleuser").ToList(); //Or we can add TenantId filter if we want to search for a specific tenant var user = _userManager.Users.FirstOrDefault(u => u.TenantId == 42 && u.UserName == "sampleuser"); } } }
模块零定义了LoginManager,它具备 用于登陆到应用程序的LoginAsync方法。它检查全部登陆逻辑并返回登陆结果。LoginAsync方法也会自动保存到数据库的全部登陆尝试(即便是失败的尝试)。您可使用UserLoginAttempt实体来查询它。
UserManager的某些方法返回IdentityResult,而不是在某些状况下抛出异常。这是ASP.NET Identity Framework的本质。模块零也跟着它。因此,咱们应该检查这个返回的结果对象,以肯定操做是否成功。
Module-zero定义了CheckErrors扩展方法,该方法自动检查错误并在必要时抛出异常(一个本地化的 UserFriendlyException)。用法示例:
(await UserManager.CreateAsync(user)).CheckErrors();
为了得到一个本地化的异常,咱们应该提供一个ILocalizationManager实例:
(await UserManager.CreateAsync(user)).CheckErrors(LocalizationManager);
module-zero的登陆方法从数据库中的AbpUsers表中对用户进行身份验证。某些应用程序可能须要从某些外部来源(如活动目录,来自另外一个数据库的表或甚至远程服务)对用户进行身份验证。
对于这种状况,UserManager定义了一个名为“外部认证源”的扩展点。咱们能够建立一个派生自 IExternalAuthenticationSource的类并注册到配置中。有DefaultExternalAuthenticationSource类来简化IExternalAuthenticationSource的实现。咱们来看一个例子:
public class MyExternalAuthSource : DefaultExternalAuthenticationSource<Tenant, User> { public override string Name { get { return "MyCustomSource"; } } public override Task<bool> TryAuthenticateAsync(string userNameOrEmailAddress, string plainPassword, Tenant tenant) { //TODO: authenticate user and return true or false } }
在TryAuthenticateAsync方法中,咱们能够从某个源检查用户名和密码,若是给定用户经过此源验证,则返回true。此外,咱们能够覆盖CreateUser和UpdateUser方法来控制用户建立和更新此源。
当用户经过外部源进行身份验证时,module-zero会检查数据库中是否存在此用户(AbpUsers表)。若是不是,则调用CreateUser建立用户,不然调用UpdateUser以容许认证源更新现有的用户信息。
咱们能够在一个应用程序中定义多个外部认证源。AbpUser实体具备AuthenticationSource属性,该属性显示哪一个源验证了此用户。
要注册咱们的认证源,咱们能够在咱们的模块的PreInitialize中使用这样的代码 :
Configuration.Modules.Zero()。UserManagement.ExternalAuthenticationSources.Add < MyExternalAuthSource >();
LdapAuthenticationSource是外部认证的实现,使用户可使用其LDAP(活动目录)用户名和密码登陆。
若是咱们想使用LDAP认证,咱们首先添加 Abp.Zero.Ldap nuget包到咱们的项目(通常是核心(域)项目)。那么咱们应该为咱们的应用程序扩展LdapAuthenticationSource,以下所示:
public class MyLdapAuthenticationSource:LdapAuthenticationSource <Tenant,User> { public MyLdapAuthenticationSource(ILdapSettings settings,IAbpZeroLdapModuleConfig ldapModuleConfig) :base(settings,ldapModuleConfig) { } }
最后,咱们应该将模块依赖关系设置为AbpZeroLdapModule, 并使用上面建立的认证来启用 LDAP:
[DependsOn(typeof(AbpZeroLdapModule))] public class MyApplicationCoreModule:AbpModule { public override void PreInitialize() { Configuration.Modules.ZeroLdap()。Enable(typeof(MyLdapAuthenticationSource)); } ... }
完成这些步骤以后,将为您的应用程序启用LDAP模块。但LDAP验证默认状况下不启用。咱们可使用设置启用它。
LdapSettingNames类定义了用于设置名称的常量。您能够在更改设置(或获取设置)时使用这些常量名称。LDAP设置是每一个租户(对于多租户应用程序)。所以,不一样的租户有不一样的设置(请参阅 github上的设置定义 )。
正如您在MyLdapAuthenticationSource 构造函数中所看到的,LdapAuthenticationSource指望 ILdapSettings做为构造函数参数。此接口用于获取LDAP设置,如域,用户名和密码以链接到Active Directory。默认实现(LdapSettings类)从设置管理器获取这些设置。
若是你使用设置管理器,那么没有问题。您可使用设置管理器API更改LDAP设置。若是须要,能够将初始/种子数据添加到数据库,以默认启用LDAP身份验证。
注意:若是您没有定义域,用户名和密码,那么若是您的应用程序在具备相应权限的域中运行,那么LDAP身份验证适用于当前域。
若是你想定义另外一个设置源,你能够实现一个自定义的ILdapSettings类,以下所示:
public class MyLdapSettings : ILdapSettings { public async Task<bool> GetIsEnabled(int? tenantId) { return true; } public async Task<ContextType> GetContextType(int? tenantId) { return ContextType.Domain; } public async Task<string> GetContainer(int? tenantId) { return null; } public async Task<string> GetDomain(int? tenantId) { return null; } public async Task<string> GetUserName(int? tenantId) { return null; } public async Task<string> GetPassword(int? tenantId) { return null; } }
而后在模块中的PreInitialize方法里将它注册到IOC中:
[DependsOn(typeof(AbpZeroLdapModule))] public class MyApplicationCoreModule : AbpModule { public override void PreInitialize() { IocManager.Register<ILdapSettings, MyLdapSettings>(); //change default setting source Configuration.Modules.ZeroLdap().Enable(typeof (MyLdapAuthenticationSource)); } ... }
这里开始bootstrap table,引入项目有两种方法,一种是直接去官网下载
地址:http://bootstrap-table.wenzhixin.net.cn/
另外一种是Nuget引入.
而后就是把js引用到项目中来,其实Bootstrap js 还有jQuery咱们在模板页已经引进了,这里只须要引入bootstrap table相关的js以及中文包就能够了
<link href="~/Scripts/Content/bootstrap-table/bootstrap-table.css" rel="stylesheet" /> <script src="~/Scripts/Content/bootstrap-table/bootstrap-table.js"></script> <script src="~/Scripts/Content/bootstrap-table/locale/bootstrap-table-zh-CN.js"></script>
前提是建立控制器userinfo,添加index视图里面处理,建立视图的时候自动选择_Layout做为模板页.引入须要的文件以后,咱们最重要的就是定义一个空的table,如上的 <table id="tb_departments"></table> 。固然Bootstrap table还提供了一种简介的用法,直接在table标签里面定义相似“data-...”等相关属性,就不用再js里面注册了,但我以为这种用法虽然简单,但不太灵活,遇到父子表等这些高级用法的时候就不太好处理了,因此我们仍是统一使用在js里面初始化的方式来使用table组件。
$(function () { //1.初始化Table var oTable = new TableInit(); oTable.Init(); //2.初始化Button的点击事件 var oButtonInit = new ButtonInit(); oButtonInit.Init(); }); var Url = "@Url.Action("GetUsersList")"; var TableInit = function () { var oTableInit = new Object(); //初始化Table oTableInit.Init = function () { $('#tb_departments').bootstrapTable({ // url: '../User/GetUsersList', url: Url, //请求后台的URL(*) method: 'get', //请求方式(*) toolbar: '#toolbar', //工具按钮用哪一个容器 striped: true, //是否显示行间隔色 cache: false, //是否使用缓存,默认为true,因此通常状况下须要设置一下这个属性(*) pagination: true, //是否显示分页(*) sortable: false, //是否启用排序 sortOrder: "asc", //排序方式 queryParams: oTableInit.queryParams,//传递参数(*) sidePagination: "server", //分页方式:client客户端分页,server服务端分页(*) pageNumber: 1, //初始化加载第一页,默认第一页 pageSize: 2, //每页的记录行数(*) pageList: [10, 25, 50, 100], //可供选择的每页的行数(*) search: true, //是否显示表格搜索,此搜索是客户端搜索,不会进服务端,因此,我的感受意义不大 strictSearch: true, showColumns: true, //是否显示全部的列 showRefresh: true, //是否显示刷新按钮 minimumCountColumns: 2, //最少容许的列数 clickToSelect: true, //是否启用点击选中行 height: 500, //行高,若是没有设置height属性,表格自动根据记录条数以为表格高度 uniqueId: "ID", //每一行的惟一标识,通常为主键列 showToggle: true, //是否显示详细视图和列表视图的切换按钮 cardView: false, //是否显示详细视图 detailView: false, //是否显示父子表 columns: [{ checkbox: true }, { field: 'UserName', title: '姓名' }, { field: 'Email', title: '邮箱' }, { field: 'Phone', title: '手机' }, { field: 'Address', title: '地址' }, ] }); }; //获得查询的参数 oTableInit.queryParams = function (params) { var temp = { //这里的键的名字和控制器的变量名必须一直,这边改动,控制器也须要改为同样的 limit: params.limit, //页面大小 offset: params.offset, //页码 departmentname: $("#txt_search_departmentname").val(), statu: $("#txt_search_statu").val() }; return temp; }; return oTableInit; };
表格的初始化也很简单,定义相关的参数便可。上面一些博主以为重要的参数都加了注释,而且初始化Table必须的几个参数也用(*)作了标记,若是你的表格也有太多的页面需求,直接用必须的参数就能解决。一样,在columns参数里面其实也有不少的参数须要设置,好比列的排序,对齐,宽度等等。这些比较简单,不会涉及表格的功能,看看API就能搞定。
这里须要注意的是@Url.Action,var Url = "@Url.Action("GetUsersList")";/ UserInfo/ GetUsersList,直接指定后台的控制器里面的方法.
public class UserInfoController : Controller { private readonly IUserService _iUsersService; public UserInfoController(IUserService iUsersService) { _iUsersService = iUsersService; } // GET: Admin/UserInfo public ActionResult Index() { return View(); } [DisableAbpAntiForgeryTokenValidation] [HttpGet] [DontWrapResult] //不须要AbpJsonResult public JsonResult GetUsersList() { string pageNumber = string.IsNullOrWhiteSpace(Request["pageNumber"]) ? "0" : Request["pageNumber"]; string pageSize = string.IsNullOrWhiteSpace(Request["pageSize"]) ? "20" : Request["pageSize"]; List<UserInfoDto> Userlist = new List<UserInfoDto>(); Userlist = _iUsersService.GetAllList().ToList(); int totaldata = Userlist.Count(); Userlist = Userlist.Skip(int.Parse(pageNumber) * int.Parse(pageSize)).Take(int.Parse(pageSize)).ToList(); var result = new { total = totaldata, rows = Userlist }; return Json(result, JsonRequestBehavior.AllowGet); } }
这里有一点须要注意:若是是服务端分页,返回的结果必须包含total、rows两个参数。漏写或错写都会致使表格没法显示数据。相反,若是是客户端分页,这里要返回一个集合对象到前端。固然我这里为了快捷,我没有去服务里面处理分页,直接在这里分页,这种作法其实很low,按照以前的作法会专门封装一个分页DTO,而后添加自定义排序字段.我这里就不去处理了,有兴趣的本身去弄一下,这些会在我下一个项目里面详细讲.这里我再网上找一张一图片来看一下具体代码的应用.
其实这些信息在API里面应该都有,本身看一下就能够了.
我这里分页和菜单是本身写的,crud的功能都是有的.
var ButtonInit = function () { var oInit = new Object(); var postdata = {}; oInit.Init = function () { //初始化页面上面的按钮事件 //查询角色 $("#btn_query").click(function () { var actionUrl = "@Url.Action("GetUsersList")"; m_pagerow = 0; $("#tb_departments").bootstrapTable('refresh', { url: actionUrl }); }); //新增角色 $("#btn_add").click(function () { $("#id").val(""); $("#txt_Surname").val(""); $("#txt_Name").val(""); $("#txt_UserName").val(""); $("#txt_isDeleted").val(""); $("#myModalLabel").text("新增"); $('#myModal').modal(); }); //新增角色 $("#btn_submit").click(function () { var actionUrl = "@Url.Action("Create")"; var UserName = $("#txt_Surname").val(); var Email = $("#txt_Name").val(); var Phone = $("#txt_UserName").val(); var isnull = $("#txt_isDeleted").val(); var isDeleted = true; if (isnull=="") { isDeleted = false; } var Id = $("#id").val() == "" ? 0 : $("#id").val(); debugger; $.ajax({ type: 'post', dataType: "Json", url: actionUrl, data: { Id: Id, UserName: UserName, Email: Email, Phone: Phone, isDeleted: isDeleted }, beforeSend: function (XMLHttpRequest) { }, success: function (data, textStatus) { //请求成功后处理函数。 toastr.warning('操做成功!'); var actionUrl = "@Url.Action("GetUsersList")"; m_pagerow = 0; $("#tb_departments").bootstrapTable('refresh', { url: actionUrl }); }, error: function (XMLHttpRequest, textStatus, errorThrown) { } }); }); //编辑角色 $("#btn_edit").click(function () { debugger; var arrselections = $("#tb_departments").bootstrapTable('getSelections'); if (arrselections.length > 1) { toastr.warning('只能选择一行进行编辑'); return; } if (arrselections.length <= 0) { toastr.warning('请选择有效数据'); return; } $("#id").val(arrselections[0].Id); $("#txt_Surname").val(arrselections[0].UserName); $("#txt_Name").val(arrselections[0].Email); $("#txt_UserName").val(arrselections[0].Phone); $("#txt_isDeleted").val(arrselections[0].Id); $("#myModalLabel").text("修改"); $('#myModal').modal(); //ShowModal(actionUrl, param, "编辑角色"); }); //删除角色 $("#btn_delete").click(function () { var actionUrl = "@Url.Action("DelUserById")"; var arrselections = $("#tb_departments").bootstrapTable('getSelections'); if (arrselections.length > 1) { toastr.warning('只能选择一行进行编辑'); return; } if (arrselections.length <= 0) { toastr.warning('请选择有效数据'); return; } var Id = arrselections[0].Id; debugger; $.ajax({ type: 'post', dataType: "Json", url: actionUrl, data: { Id:Id}, beforeSend: function (XMLHttpRequest) { }, success: function (data, textStatus) { //请求成功后处理函数。 toastr.warning('操做成功!'); var actionUrl = "@Url.Action("GetUsersList")"; m_pagerow = 0; $("#tb_departments").bootstrapTable('refresh', { url: actionUrl }); }, error: function (XMLHttpRequest, textStatus, errorThrown) { } }); }); //权限受权 $("#btn_authorize").click(function () { var arrselections = $("#tb_departments").bootstrapTable('getSelections'); if (arrselections.length > 1) { toastr.warning('只能选择一个角色进行受权'); return; } if (arrselections.length <= 0) { toastr.warning('请选择有效数据'); return; } var actionUrl = "@Url.Action("AuthorizePermission")"; var param = { id: arrselections[0].Id }; ShowModal_Authorize(actionUrl, param, "权限受权"); }); //模态框中“权限受权”保存 var $modal = $("#authorizeModal"); $("#btnSave", $modal).click(function () { var actionUrl = "@Url.Action("AuthorizePermission")"; SaveModal_Authorize(actionUrl); }); //模态框中“新增编辑角色”保存 var $formmodal = $("#modal-form"); $("#btnSave", $formmodal).click(function () { var $tb = $("#tb_departments"); SaveModal($tb); }); /*******弹出表单*********/ function ShowModal(actionUrl, param, title) { debugger; var $modal = $("#modal-form"); //表单初始化 $(".modal-title", $modal).html(title); $("#modal-content", $modal).attr("action", actionUrl); $.ajax({ type: "GET", url: actionUrl, data: param, beforeSend: function () { }, success: function (result) { debugger; $("#modal-content").html(result); $('#modal-form').modal('show'); }, error: function () { }, complete: function () { } }); } }; return oInit; };
<section class="content-header"> <h1> 用户明细 <small>advanced cxdmles</small> </h1> <ol class="breadcrumb"> <li><a href="#"><i class="fa fa-dashboard"></i> 主页</a></li> <li><a href="#">用户管理</a></li> <li class="active">用户列表</li> </ol> </section> <section class="content"> <div class="panel-body" style="padding-bottom:0px;"> <div class="panel panel-default"> <div class="panel-heading">查询条件</div> <div class="panel-body"> <form id="formSearch" class="form-horizontal"> <div class="form-group" style="margin-top:15px"> <label class="control-label col-sm-1" for="txt_search_departmentname">姓名</label> <div class="col-sm-3"> <input type="text" class="form-control" id="txt_search_departmentname"> </div> <label class="control-label col-sm-1" for="txt_search_statu">昵称</label> <div class="col-sm-3"> <input type="text" class="form-control" id="txt_search_statu"> </div> <div class="col-sm-4" style="text-align:left;"> <button type="button" style="margin-left:50px" id="btn_query" class="btn btn-primary">查询</button> </div> </div> </form> </div> </div> <div id="toolbar" class="btn-group"> <button id="btn_add" type="button" class="btn btn-success"> <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>新增 </button> <button id="btn_edit" type="button" class="btn btn-warning"> <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>修改 </button> <button id="btn_delete" type="button" class="btn btn-danger"> <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>删除 </button> <button id="btn_authorize" type="button" class="btn btn-info "> <span class="glyphicon glyphicon-lock" aria-hidden="true"></span>受权 </button> </div> <table id="tb_departments"></table> </div> <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title" id="myModalLabel"></h4> </div> <div class="modal-body"> <div class="form-group"> <label for="txt_departmentname">姓名</label> <input type="text" name="id" class="form-control" id="id" placeholder="id" style="display:none"> <input type="text" name="txt_departmentname" class="form-control" id="txt_Surname" placeholder="真实姓名"> </div> <div class="form-group"> <label for="txt_parentdepartment">邮箱</label> <input type="text" name="txt_parentdepartment" class="form-control" id="txt_Name" placeholder="姓名"> </div> <div class="form-group"> <label for="txt_departmentlevel">手机</label> <input type="text" name="txt_departmentlevel" class="form-control" id="txt_UserName" placeholder="部门级别"> </div> <div class="form-group"> <label for="txt_departmentlevel">是否启用</label> <div class="checkbox"> <label> <input type="checkbox" disabled="disabled"> </label> </div> </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span>关闭</button> <button type="button" id="btn_submit" class="btn btn-primary" data-dismiss="modal"><span class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span>保存</button> </div> </div> </div> </div> </section>
这里的菜单其实也是bootstrap 样式.表单也是.看下控制器方法.都很简单.
public ActionResult Create() { var model = new UserInfoDto(); return PartialView(model); } [HttpPost] public ActionResult Create(UserInfoDto roleVm) { var result = _iUsersService.AddUserList(roleVm); return Json(result); } [DisableAbpAntiForgeryTokenValidation] [HttpPost] [DontWrapResult] public ActionResult DelUserById(string Id) { var result = _iUsersService.DelUsers(Id); return Json(result); }
Service方法.无需细说,一看就懂,注意递归
public class UserService : IUserService { private readonly IRepository<Users, int> _userRepository; public ILogger Logger { get; set; } public UserService(IRepository<Users, int> userRepository) { Logger = NullLogger.Instance; _userRepository = userRepository; } public async Task AddUserList(UserInfoDto model) { var user = model.MapTo<Users>(); await _userRepository.InsertAsync(user); } public async Task DelUsers(string id) { try { Users user = _userRepository.Get(Int32.Parse(id)); await _userRepository.DeleteAsync(user); } catch (Exception ex) { throw; } } public async Task<ListResultDto<UserInfoDto>> GetUsers() { var users = await _userRepository.GetAllListAsync(); return new ListResultDto<UserInfoDto>( users.MapTo<List<UserInfoDto>>() ); } public List<UserInfoDto> GetAllList() { var users = _userRepository.GetAllListAsync(); return new List<UserInfoDto>( users.MapTo<List<UserInfoDto>>() ); } }
自此,用户列表.