一步一步Asp.Net MVC系列_權限管理數據庫與ViewModel篇


在上一篇中我們講了大致怎么搭建一個項目,以及一個項目的基本構架,這一次,我們講解基本的權限管理思路.

說道權限管理,相信大家都不太陌生,這個東西幾乎什么系統都會涉及到,因此,抽出時間去思考,去研究復用的模塊,架構,就是一個非常好的提升水平的方式,特別是對於我們這些學生來說,沒有太多經驗,更需要去研究這些東西來更多的掌握實戰方面的技巧.數據庫設計是一個幾乎人人都要面對的一個話題,我就來講,我的數據庫設計.當然只是個人見解,博客園的很多人都設計的各種各樣的權限,各種各樣的思路,但是可能大多數設計的比較復雜,我只是從一個菜鳥的角度講解,我能夠理解的權限管理.

首先是數據庫設計:

在此期間,我們貼上powerdesinger設計圖

image

大致就是7張表:

tbModule模塊表

tbModulePermission模塊權限表

tbPermission權限表

tbRole角色表

tbRolePermission角色權限表

tbUserRole用戶角色表

tbUser用戶表

首先要注意的就是命名:

可以看到,我的數據庫命名風格是非常統一的,呵呵,這個可能是習慣,tb代表table,如果是視圖我就在前面加上v,后面全部是Pascal風格的命名,字母大寫的方式.

一個清晰的命名可以讓我們很容易看清細節,起一個好名字非常重要.

這也告訴我們,我們平時注意細節,命名,代碼規范統一,讓我們節省大量的時間.

在博客園看到很多權限設計思路,

我覺得上面這個可能是比較清晰的,呵呵,參考了好多人的設計,

權限設計要設計那些呢?

首先,一個模塊對應多種權限,增刪改查,導入導出等等等等,這里權限在mvc里面可以控制到每一個請求,每一個ajax響應,在mvc里面有一個很好的對應關系,一個模塊對應就是一個控制器,一個action就是一個權限(當然這種可能太細了,但是我們可以先這么看),這樣我們就需要在tbModule和tbPermission中間加一個tbModulePermission表了,因為不同角色的同一模塊的權限可能都是不一樣的,所以這里要加入一個tbModulePermission表.

其次,角色的設計,角色擁有模塊權限,每個用戶對應多種角色,這個用戶可能即是普通管理員,又是其他后台編輯,等等,權限當然是不一樣的.

這樣一個思路就很明顯了,角色直接擁有各種模塊的功能權限,而用戶可以扮演各種角色.

至於字段的設計,我看了很多人的設計,很多細節的字段,比如IsDeleted,CreateUserID,CreateDate,ModifyUserID,ModifyDate,Description這些幾乎大部分表都是需要的,多加一些表,更是讓設計更靈活.

 

數據庫的設計ID用GUID的習慣,我還沒有養成,可能現在沒遇到麻煩吧,也是沒有真正的工作經驗,呵呵,別人都說應該主鍵用GUID,呵呵!

 

 

我看到很多人直接設計Menu和Button來表示tbModule和tbPermission,其實感覺從命名來講,頁面任何一個ajax請求都可以算是一種功能權限,可能不是Button,如果用Permission表示可能更好一點,至於Menu,我認為用Module來命名可能更好,在tbModule里面有一個IsMenu字段來表示是否是菜單,里面有一個Controller字段,其實大致對應一個Controller控制器,每一個模塊在mvc中以控制器的方式展示,我們在設計UI菜單的時候也可以通過反射的方式來獲取Controller,對於菜單的設計更友好,不需要自己去配置URL,更自由.當然,在Permission表里面有PermissionAction和PermissionController字段表示,每一個動作請求和對應的控制器.

image

對於菜單的添加我們可以做到非常輕松,點點點就能夠配置好,以前需要在數據庫輸入的一些預定義的數據!

數據設計好了,這種數據庫可能我感覺比較清晰.呵呵!

接下來,我們繼續談關於設計的問題,上次基本上已經講完關於數據訪問層的設計,這次講解,關於ViewModel的設計和公共類庫的設計,

ViewModel呵呵,我用它來干什么,我用它來做UI層和業務邏輯層交互的實體對象.

可能大家覺得EF已經幫我們設計了數據庫的實體對象,為什么還要另外一個單獨的層來定義交互的實體對象?

image

我把這個ViewModel就是和界面交互的實體對象,在EF基本上是根據數據庫直接生成的實體,所以命名字段都是跟數據庫緊密結合在一起的,往往我們給UI層展示的時候,要做大量的處理,而UI交互,經常性我們用的后台框架都要求提供指定格式的json數據,我們以前甚至這么干過,從數據訪問層直接select DeptID as id,name as title from deptment……………這種代碼簡直太坑爹了,而幾乎數據庫的東西和界面都關聯到一起了,如果我們修改個字段,界面改點東西,兩頭都要動,所以,這種模式太痛苦了.

因此,我專門獨立了一層,用來做UI和業務邏輯層的交互使用,這樣ViewModel與UI更友好,用例子說說吧!

數據庫的Permission在界面中大量顯示為Button,我們就可以直接定義一個ViewModelButton類,來做UI和業務邏輯的交互,

如果對於普通字段的添加,我相信在這個代碼中,你的改動非常少,幾乎只是EF模型重新生成,ViewModel,和html界面傳參的修改,我們的目標是,讓修改更簡單,業務的變動,我們仍然能夠清晰的把握細節.

   1:  /*  作者:       tianzh
   2:  *  創建時間:   2012/7/26 15:52:19
   3:  *
   4:  */
   5:  using System;
   6:  using System.Collections.Generic;
   7:  using System.Linq;
   8:  using System.Text;
   9:  using System.Web;
  10:  using TZHSWEET.Entity;
  11:   
  12:  namespace TZHSWEET.ViewModel
  13:  {
  14:      public class ViewModelButton
  15:      {
  16:          #region - 屬性 -
  17:   
  18:          public int ID { get; set; }
  19:   
  20:          public string Action { get; set; }
  21:   
  22:          public string Name { get; set; }
  23:   
  24:          public bool IsVisible { get; set; }
  25:   
  26:          public string Script { get; set; }
  27:   
  28:          public string Icon { get; set; }
  29:   
  30:          public string Controller { get; set; }
  31:   
  32:          public string Description { get; set; }
  33:   
  34:          #endregion
  35:   
  36:          #region - 構造函數 -
  37:   
  38:          public ViewModelButton()
  39:          {
  40:   
  41:          }
  42:   
  43:          public ViewModelButton(HttpContextBase context)
  44:          {
  45:              ID = Convert.ToInt32(context.Request["ID"]);
  46:              Action = context.Request["Action"];
  47:              Name = context.Request["Name"];
  48:              IsVisible = context.Request["IsVisible"] == "1";
  49:              Script = context.Request["Script"];
  50:              Icon = context.Request["Icon"];
  51:              this.Controller = context.Request["Controller"];
  52:   
  53:   
  54:          }
  55:   
  56:          #endregion
  57:   
  58:          #region - 方法 -
  59:          #region ToEntity
  60:          public static tbPermission ToEntity(ViewModelButton permission)
  61:          {
  62:              tbPermission item = new tbPermission();
  63:              item.PermissionID = permission.ID;
  64:              item.PermissionName = permission.Name;
  65:              item.PermissionAction = permission.Action;
  66:              item.Script = permission.Script;
  67:              item.Icon = permission.Icon;
  68:              item.IsVisible = permission.IsVisible;
  69:              item.PermissionController = permission.Controller;
  70:              item.Description = permission.Description;
  71:              return item;
  72:          }
  73:   
  74:          #endregion
  75:          #region ToViewModel
  76:          /// <summary>
  77:          /// 轉化為ViewModel
  78:          /// </summary>
  79:          /// <param name="permission"></param>
  80:          /// <returns></returns>
  81:          public static ViewModelButton ToViewModel(tbPermission permission)
  82:          {
  83:              ViewModelButton item = new ViewModelButton();
  84:              item.ID = permission.PermissionID;
  85:              item.Name = permission.PermissionName;
  86:              item.Action = permission.PermissionAction;
  87:              item.IsVisible = permission.IsVisible.Value;
  88:              item.Icon = permission.Icon;
  89:              item.Script = permission.Script;
  90:              item.Controller = permission.PermissionController;
  91:              item.Description = permission.Description;
  92:              return item;
  93:          }
  94:   
  95:          /// <summary>
  96:          /// 轉化為List集合
  97:          /// </summary>
  98:          /// <param name="list"></param>
  99:          /// <returns></returns>
 100:          public static IEnumerable<ViewModelButton> ToListViewModel(IEnumerable<tbPermission> list)
 101:          {
 102:              var listModel = new List<ViewModelButton>();
 103:              foreach (tbPermission item in list)
 104:              {
 105:                  listModel.Add(ViewModelButton.ToViewModel(item));
 106:              }
 107:              return listModel;
 108:          } 
 109:          #endregion
 110:   
 111:          #endregion
 112:   
 113:   
 114:      }
 115:  }

一般情況下,我們只需要定義實體,和一個構造函數,這個構造函數,大家可能覺得為什么要傳遞HttpContext?

原因就是,封裝Controller中的請求,把Controller中表單的數據請求單獨用ViewModel處理,看看關於Controller的一個函數處理

image

我們把關於請求的數據從Controller中分離處理啊,Controller就相當簡潔了,很清晰的看到具體的思路,而我們的業務邏輯層,就可以相當簡潔的調用

image

而這個T是什么呢?T就是tbPermission實體這個東西了,是不是發現頁面非常清晰,簡潔?

而我們經常通用的幾種UI格式,LigerUI Grid格式請求,Select格式請求,GridTree格式請求,Tree格式請求,都是ViewModel中的封裝,

image

GridRequest請求的ViewModel:

   1:   /*  作者:       tianzh
   2:   *  創建時間:   2012/7/19 18:16:08
   3:   *
   4:   */
   5:   /*  作者:       tianzh
   6:   *  創建時間:   2012/7/16 15:31:37
   7:   *
   8:   */
   9:   
  10:  using System;
  11:  using System.Collections.Generic;
  12:  using System.Linq;
  13:  using System.Text;
  14:  using System.Web;
  15:  namespace TZHSWEET.ViewModel
  16:  {
  17:      public class LigerUIGridRequest
  18:      {
  19:   
  20:          private string sortOrder;
  21:   
  22:          private int pageSize;
  23:          /// <summary>
  24:          /// 字段查看視圖(暫時沒做到)
  25:          /// </summary>
  26:          public string View
  27:          {
  28:              get;
  29:              set;
  30:          }
  31:          /// <summary>
  32:          /// 排序字段名稱
  33:          /// </summary>
  34:          public string SortName { get; set; }
  35:          /// <summary>
  36:          /// 排序規則
  37:          /// </summary>
  38:          public string SortOrder
  39:          {
  40:              get
  41:              {
  42:                  if (this.sortOrder == "desc")
  43:                      return this.sortOrder;
  44:                  else
  45:                      return "asc";
  46:              }
  47:   
  48:              set
  49:              {
  50:                  this.sortOrder = value;
  51:              }
  52:          }
  53:          private int pageNumber;
  54:          /// <summary>
  55:          /// 頁號
  56:          /// </summary>
  57:          public int PageNumber
  58:          {
  59:              get
  60:              {
  61:                  if (this.pageNumber <= 0)
  62:                      return 1;
  63:                  else
  64:                      return pageNumber;
  65:              }
  66:              set
  67:              {
  68:                  if (value <= 0)
  69:                      pageNumber = 1;
  70:                  else
  71:                      pageNumber = value;
  72:              }
  73:          }
  74:          /// <summary>
  75:          /// 每頁的多少條數據
  76:          /// </summary>
  77:          public int PageSize 
  78:          {
  79:              get
  80:              {
  81:                  return this.pageSize;
  82:              }
  83:              set
  84:              { 
  85:                 this.pageSize= (value==0?10:value);
  86:              }
  87:          }
  88:          /// <summary>
  89:          /// 查詢條件
  90:          /// </summary>
  91:          public string Where { get; set; }
  92:          /// <summary>
  93:          /// 初始化讀取信息
  94:          /// </summary>
  95:          /// <param name="context"></param>
  96:          public LigerUIGridRequest(HttpContextBase context)
  97:          {
  98:              this.View = context.Request["view"];
  99:              this.SortName= context.Request["sortname"];
 100:             this.SortOrder = context.Request["sortorder"]=="desc"?"desc":"asc";
 101:             this.PageNumber =Convert.ToInt32(context.Request["page"]);
 102:             this.PageSize =Convert.ToInt32(context.Request["pagesize"]);
 103:              this.Where = context.Request["where"];
 104:   
 105:          }
 106:      }
 107:  }

在這里我們可以做數據的校驗工作,這樣我們的控制器就非常非常的簡潔了,

在業務邏輯層,我們可以通過Request的ViewModel模型,進行數據處理,

image

可以看到我們僅僅通過數據的封裝就讓我們的UI和業務邏輯更簡潔,更豐富,

當然這個業務邏輯層寫的很有問題..............就是業務邏輯層竟然直接返回json格式,這個大大的不好,因為我們的業務邏輯層盡可能的跟數據的展示格式沒關系,應該直接返回數據,集合就直接返回IEnumerable<T>接口,而數據如何展示應該在控制器中控制.

一個好一點的方法設計就是這樣子的,

image

可以看到我們的工作大部分就是讓不同的類減壓,盡量更簡單,單一指責,如果一個類的職責過多就更難維護,特別是控制器,很容易出現大量的代碼,

就像我們的這個

image

讓代碼更精簡,這樣我們才能更清晰,本來我們返回的Grid數據需要json化,我寫了一個擴展方法,返回json,而且格式針對UI更友好,配合前台的封裝的ajax,讓消息的提示更友好,

image

image

前台利用LigerUI中的封裝,

image

image

 

 

 

 

總結:可以看到我們每一部細節的改進,都能讓我們代碼更簡潔,而且讓我們的思路更清晰,清晰,才是我們的追求,清晰的代碼是我們的追求!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM