返回總目錄:ABP+AdminLTE+Bootstrap Table權限管理系統一期
用戶實體
用戶實體代表應用的一個用戶,它派生自AbpUser類,如下所示:
public class User : AbpUser<Tenant, User> { //add your own user properties here }
這個類是在安裝模塊零時創建的。用戶存儲在數據庫的AbpUsers表中。您可以將自定義屬性添加到User類(並為更改創建數據庫遷移)。
AbpUser類定義了一些基本屬性。一些屬性是:
- UserName:用戶的登錄名,對於一個租戶來說應該是唯一的。
- EmailAddress:用戶的郵箱地址。對於租戶來說應該是唯一的。
- Password:用戶的哈希密碼。
- IsActive:如果用戶可以登錄到該應用,那么此值為true。
- Name和Surname:用戶的名和姓。
還有一些屬性,如角色, 權限,租戶,設置, IsEmailConfirmed等。檢查AbpUser類以獲取更多信息。
AbpUser類是從FullAuditedEntity繼承的。這意味着它具有創建,修改和刪除審計屬性。這也是 軟刪除。所以,當我們刪除一個用戶時,它不會從數據庫中刪除,只是標記為已刪除。
AbpUser類實現了 IMayHaveTenant 過濾器,以便在多租戶應用程序中正常工作。
最后,用戶的Id被定義為long。
用戶管理器
UserManager是 為用戶執行域邏輯的服務:
public class UserManager:AbpUserManager <Tenant,Role,User> { // ... }
您可以注入並使用UserManager創建,刪除,更新用戶,授予權限,更改用戶角色等等。你可以在這里添加你自己的方法。此外,您可以 根據自己的需要重寫AbpUserManager基類的任何方法。
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實體來查詢它。
關於IdentityResults
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 >();
LDAP / Active Directory
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引入
這里開始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,然后添加自定義排序字段.我這里就不去處理了,有興趣的自己去弄一下,這些會在我下一個項目里面詳細講.這里我再網上找一張一圖片來看一下具體代碼的應用.
crud功能
其實這些信息在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方法
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>>() ); } }
效果
自此,用戶列表.