酷权限控制系统 第二部分 -- asp.net MVC 与 WCF





5.00/5 (8投票s)
基于MVC和WCF的权限控制系统。
酷权限控制系统 第二部分 -- asp.net MVC 与 WCF
引言
酷权限控制是一个独立的访问权限系统。它解决了许多复杂的访问权限问题。例如,用户拥有不同的访问级别的问题,无限的功能层次结构以及易于添加功能类型。它还包含许多通用机制,例如支持多语言环境,支持三种日志类型(错误日志,跟踪日志,审计日志)。它能帮助您轻松快速地创建具有强大权限的asp.net mvc演示项目。有关详细信息,请参阅我上一篇文章。在这里,我想花一些时间感谢那些投票5星和提问的人。再次感谢。今天我想更深入地探讨系统扩展。这是什么以及如何实现?请跟随我的思路。
背景
WCF,全称是Windows Communication Foundation。它是构建面向服务应用程序的框架。我知道一些聪明人已经猜到了。是的,你猜对了!在本文中,我将把酷权限控制系统分成两个不同的部分。一个是负责UI维护的MVC项目(发送请求到服务/接收来自服务的响应),另一个是负责执行操作的WCF项目(托管在IIS中)(接收并执行来自调用者的请求/将响应发送给调用者)。让我们继续,我将介绍如何以及为何使用WCF来实现系统扩展。
设计模式
首先,我想描述一下mvc设计模式。是的,MVC设计模式。我知道很多人比我拥有更多的想法和经验。我不想欺骗那些比我更深入的人。但我认为先进性来自于原始设计模式的缺点和新需求。
在Google搜索引擎中有许多MVC的图片。这帮我节省了很多绘制图表的时间。感谢Google。
MVC由Controller(控制器)、View(视图)和Model(模型)组成。MVC设计模式不仅可以用于Web应用程序,还可以用于客户端应用程序(PS:我更倾向于在客户端应用程序开发中使用MVVM设计模式,我指的是WPF应用程序)。但酷权限控制是一个Web应用程序,所以我将专注于Web应用程序的MVC。Web请求被发送到应用程序服务器。URL路由器接收请求并选择合适的控制器来处理请求(PS:我忽略了URL路由器接收请求之前的许多步骤,例如HttpHandler。因为我的主题不是覆盖MVC设计模式)。Controller是模型和视图之间的中间层。它处理请求,调用模型方法,绑定数据并返回视图。Model包含数据库操作和业务逻辑。View是表示层,展示带有数据的布局。
MVC的优缺点
优点: |
|
缺点: |
|
注意:以上优缺点是我的个人观点。如有任何错误,请随时与我联系。
如上图所示,在Controller接收到URL Router的请求后,Controller将调用wcf服务而不是调用Model的修改方法。然后WCF服务接收请求并调用Model的修改方法。Model执行操作并将View Model返回给调用者。Controller接收返回的View Model并将其绑定到View。
带WCF的MVC的优缺点
优点: |
|
缺点: |
|
在前面的章节之后,以你现在的聪明才智,我猜你可能想使用WCF来扩展你的产品。下面的序列图说明了ASP.NET MVC中“获取HR信息”的流程,我希望这个图能在你设计复杂的酷权限控制系统时给你一些启发。
描述
1. | 客户端用户发送请求,从HR模块获取员工基本信息。 |
1.1 | HR模块将浏览器重定向到登录页面。 |
1.1.1 | 向客户端返回登录页面。 |
2. | 客户端用户输入登录名和密码,点击提交按钮。 |
2.1 | MVC控制器调用WCF的认证方法。 |
2.1.1 | WCF执行验证方法。 |
2.1.2 | WCF服务向调用者发出令牌或返回验证错误。 |
2.1.3 | 客户端缓存令牌以供后续处理。 |
3. | 客户端用户发送带令牌的请求以获取HR信息。 |
3.1 | HR模块调用WCF服务,并附带功能键和令牌,以检查访问权限。 |
3.1.1 | WCF执行验证方法。 |
3.1.2 | 如果成功,则返回成功给HR模块。 |
3.1.2.1 | HR模块将HR信息返回给客户端用户。 |
3.1.3 | 如果失败,则返回失败给HR模块。 |
3.1.3.1 | HR模块将失败消息返回给客户端用户。 |
毫无疑问,你可以用不同的技术替换UI。我只是给你一个例子,你可以通过调用WCF服务来自己控制流程。
设置
对于MSSQL/MYSQL数据库用户,我发布了两个版本以满足您的需求。如果您默认使用MSSQL,请下载“CoolPrivilegeControl.Community.WCF.MSSQL.zip”,否则,请下载“CoolPrivilegeControl.Community.WCF.MYSQL.zip”。我列出了我的开发环境供您参考。
1. Microsoft Visual Studio 2013
2. .NET Framework 4.5.1
3. MSSQL Server 2012 或 MYSQL 5.6.26
4. MVC 5.2.3
5. Entity Framwork 6.0
强制步骤:打开解决方案后,请右键单击解决方案并选择“启用NuGet程序包还原”。
强制步骤:请右键单击解决方案并单击“属性”,将项目“CoolPrivilegeControl”和“CoolPrivilegeServiceHost”设置为启动项目。
4.1 编辑WEB.CONFIG文件
4.1.1 对于MYSQL用户
请注意appSettings节点,根据mysql服务器设置更改DBSource/DBName/DBPort/LoginName/LoginPWD的值。
属性 | 描述 |
DBSource | 服务器主机的IP地址 |
DBName | 数据库名称 |
DBPort | 服务器TCP/IP端口 |
LoginName | DB用户名 |
LoginPWD | 数据库用户密码 |
IsDebug | 如果设置为true,将显示更多详细的跟踪器信息。 |
属性 | 描述 |
IsDebug | 如果设置为true,任何异常都会显示在页面上,反之亦然。 |
4.1.2 对于MSSQL用户
与mysql用户一样,根据mssql服务器设置更改DBSource/DBName/LoginName/LoginPWD的值,但DBPort除外。在mssql服务器中,端口号可以在服务器名称或服务器IP地址后用逗号分隔。例如:
WELLSCHEUNG\MSSQLSERVER2012,49287 49287是端口。
属性 | 描述 |
DBSource | 服务器主机的IP地址 带端口格式:服务器名称或IP地址,端口 |
DBName | 数据库名称 |
LoginName | DB用户名 |
LoginPWD | 数据库用户密码 |
IsDebug | 如果设置为true,将显示更多详细的跟踪器信息。 |
对于UI web.config设置,请参阅上面第4.1.1.2节。
4.1.3 启用或禁用数据库初始化程序
这是Entity Framework Code First设计模式的特性。也是我听说过的最有用的功能。当您执行项目时,如果数据库实例不存在于服务器上并且该标志已启用,Entity Framework机制将帮助您进行初始化。有关更多信息,请参阅MSDN(https://msdn.microsoft.com/en-us/data/ee712907)。如果您不希望Entity Framework初始化数据库并覆盖您的数据库,可以将上下文元素中名为“disableDatabaseInitialization”的属性设置为true。
或者,您可以通过sql脚本或数据库备份来初始化数据库。我为mysql用户准备了sql脚本来执行,并为mssql用户准备了数据库备份进行恢复。
4.2 编辑Log4Net.config文件
我们使用log4net来帮助我们记录跟踪信息和错误信息。关于如何使用log4net,我认为你们大多数人都比我更有经验,所以我不想花太多时间重复。我只说明系统中的所有函数默认都启用了跟踪日志(即,包含输入和输出信息)。即使我们无法在现场支持时运行Visual Studio调试,这也是一种轻松跟踪错误的方法。在WCF版本中,我们可以配置两个log4net.config文件。一个用于asp.net mvc,另一个用于wcf服务。
可以轻松地关闭或更改您想捕获的另一种类型的信息。log4net中预设了七个级别类型。
以下级别按优先级递增的顺序定义
- ALL
- DEBUG
- INFO
- WARN
- ERROR
- 致命错误
- 关
您可以将log4net.config中名为“level”的属性值更改为您想要的。
SysLog: 记录系统的所有信息。 |
ErrorLog: 只记录系统的异常。 |
在mvc项目中,之前的设置是全局设置。如果您想自动化地禁用一个或任何函数来记录信息。您可以将您想要的函数标记为“[UnTracerAction]”函数。这样做之后,所有信息都不会被记录。
[HttpPost]
[ValidateAntiForgeryToken]
[UnTracerAction]
public ActionResult Create(FunctionVM functionVM)
{
//Message Box Title -- When Error occured, Message Box would be showed.
string str_MsgBoxTitle = MultilingualHelper.GetStringFromResource(languageKey, "FManage_Create");
如果您只想记录主线程是否进入函数,而不记录输入的详细信息或输出信息。您可以将您想要的函数标记为“[TracerActionWithDetails(EnableTracer=false)]”函数。
[HttpPost]
[ValidateAntiForgeryToken]
[TracerActionWithDetails(EnableTracer=false)]
public ActionResult Create(FunctionVM functionVM)
{
//Message Box Title -- When Error occured, Message Box would be showed.
string str_MsgBoxTitle = MultilingualHelper.GetStringFromResource(languageKey, "FManage_Create");
完成上述设置后,系统即可供您使用。在Visual Studio中按“F5”并仔细检查是否有任何编译错误。如果出现任何错误,您可以将错误发送给我进行检查,或者自己搜索Google解决方案。
使用代码
在接下来的部分,我想说明如何在酷权限控制中调用wcf服务。
UI操作
这是FTManageController中Index方法的完整视图,当用户输入选择条件并点击Function Type Management中的搜索按钮时,它将被调用。
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Index(FunctionTypeVM selectionCriteria)
{
//Message Box Title -- When Error occured, Message Box would be showed.
string str_MsgBoxTitle = MultilingualHelper.GetStringFromResource(languageKey, "FTManage");
//Declare output variable(recordCount && entityList_Result)
int recordCount = 0;
List<FunctionTypeVM> entityList_Result = new List<FunctionTypeVM>();
//Declare wcf output object;
FTSerListResult entity_FTSerListResult = null;
//Instantiate WebCommonHelper in order to call wcf service
WebCommonHelper webCommonHelper = new WebCommonHelper();
webCommonHelper.CallWCFHelper<IFunTypeMgtSer>(this, this.HttpContext, postOffice.FunTypeMgtSerPath, (entity_IFunTypeMgtSer, entity_WCFSessionVM) =>
{
entity_FTSerListResult = entity_IFunTypeMgtSer.GetListWithPaging(entity_WCFSessionVM, selectionCriteria, 1, PageSize, null, null, CustomFilter(selectionCriteria));
});
//Assign data to local variable
if (entity_FTSerListResult != null)
{
recordCount = entity_FTSerListResult.Int_TotalRecordCount;
entityList_Result = entity_FTSerListResult.EntityList_FunctionTypeVM;
}
//Set paging bar info (Total Record Count and Page Index)
StorePageInfo(recordCount, 1);
//Cache selection criteria
StoreSelectionCriteria<FunctionTypeVM>(selectionCriteria);
//Pass Error To UI
string strError = "";
if (entity_FTSerListResult.StrList_Error.Count() > 0)
strError = string.Join("<br/>", entity_FTSerListResult.StrList_Error.ToArray());
//Fail
if (entity_FTSerListResult.StrList_Error.Count > 0)
{
MsgInfo errorMsgInfo = new MsgInfo();
errorMsgInfo.MsgTitle = str_MsgBoxTitle;
errorMsgInfo.MsgDesc = strError;
errorMsgInfo.MsgType = MessageType.ValidationError;
ViewBag.ActionMessage = errorMsgInfo;
}
//Success
return View(entityList_Result);
}
分步代码说明
- 设置消息框标题。
//Message Box Title -- When Error occured, Message Box would be showed. string str_MsgBoxTitle = MultilingualHelper.GetStringFromResource(languageKey, "FTManage");
- 声明变量recordCount和entityList_Result,用于接收服务调用后的总记录数和Function Type列表。
//Declare output variable(recordCount && entityList_Result) int recordCount = 0; List<FunctionTypeVM> entityList_Result = new List<FunctionTypeVM>();
- 声明WCF输出对象
//Declare wcf output object; FTSerListResult entity_FTSerListResult = null;
- 实例化WebCommonHelper并调用服务。
//Instantiate WebCommonHelper in order to call wcf service WebCommonHelper webCommonHelper = new WebCommonHelper(); webCommonHelper.CallWCFHelper<IFunTypeMgtSer>(this, this.HttpContext, postOffice.FunTypeMgtSerPath, (entity_IFunTypeMgtSer, entity_WCFSessionVM) => { entity_FTSerListResult = entity_IFunTypeMgtSer.GetListWithPaging(entity_WCFSessionVM, selectionCriteria, 1, PageSize, null, null, CustomFilter(selectionCriteria)); });
- 将数据分配给局部变量
//Assign data to local variable if (entity_FTSerListResult != null) { recordCount = entity_FTSerListResult.Int_TotalRecordCount; entityList_Result = entity_FTSerListResult.EntityList_FunctionTypeVM; }
- 缓存分页信息以设置分页条。
//Set paging bar info (Total Record Count and Page Index) StorePageInfo(recordCount, 1);
- 缓存选择条件。
//Cache selection criteria StoreSelectionCriteria<FunctionTypeVM>(selectionCriteria);
- 如果发生错误,则将错误信息返回给UI,否则将实体列表绑定到视图。
//Pass Error To UI string strError = ""; if (entity_FTSerListResult.StrList_Error.Count() > 0) strError = string.Join("<br/>", entity_FTSerListResult.StrList_Error.ToArray()); //Fail if (entity_FTSerListResult.StrList_Error.Count > 0) { MsgInfo errorMsgInfo = new MsgInfo(); errorMsgInfo.MsgTitle = str_MsgBoxTitle; errorMsgInfo.MsgDesc = strError; errorMsgInfo.MsgType = MessageType.ValidationError; ViewBag.ActionMessage = errorMsgInfo; } //Success return View(entityList_Result);
服务操作
在上面的代码片段中,我说明了UI控制器的操作。在下面的代码片段中,我将向您展示wcf接收到调用后的过程。
这是由上述控制器调用的wcf方法的完整视图
public FTSerListResult GetListWithPaging(WCFSessionVM entity_WCFSessionVM, FunctionTypeVM entity_SearchCriteria, int int_CurrentPage, int int_PageSize, string str_SortColumn, string str_SortDir, List<string> str_CustomFilter)
{
try
{
//Restore Server Session by token
RetrieveServerSideSession(entity_WCFSessionVM);
//Flag Success or Fail
bool ret = false;
//Define error list
List<string> strList_Error = new List<string>();
//Instantiate FTSerListResult
FTSerListResult returnResult = new FTSerListResult();
CoolPrivilegeControlContext dbContext = CoolPrivilegeControlContext.CreateContext();
FunctionTypeRespository entityRepos_FT = new FunctionTypeRespository(dbContext, entity_ServerSideSession.ID);
#region [ Check Privilege ]
ret = CheckAccPrivilege(entity_ServerSideSession.ID, entity_WCFSessionVM.RequestFunKey, entity_WCFSessionVM.RequestFunTypeKey, ref strList_Error);
#endregion
//Initialize FTSerListResult instance
returnResult.StrList_Error = strList_Error;
returnResult.Int_TotalRecordCount = 0;
returnResult.EntityList_FunctionTypeVM = new List<FunctionTypeVM>();
//Success
if (ret)
{
int recordCount = 0;
List<FunctionTypeVM> vmList = entityRepos_FT.GetEntityListByPage(entity_SearchCriteria, int_CurrentPage, int_PageSize, str_SortColumn, str_SortDir, out recordCount, str_CustomFilter);
//Assign data to FTSerListResult instance
returnResult.EntityList_FunctionTypeVM = vmList;
returnResult.Int_TotalRecordCount = recordCount;
}
return returnResult;
}
catch (Exception ex)
{
throw new FaultException<WCFErrorContract>(new WCFErrorContract(ex), ex.Message);
}
}
分步代码说明
- 通过令牌恢复wcf会话
//Restore Server Session by token RetrieveServerSideSession(entity_WCFSessionVM);
- 定义标志和错误列表
//Flag Success or Fail bool ret = false; //Define error list List<string> strList_Error = new List<string>();
- 实例化FTSerListResult对象,用于回复请求。
//Instantiate FTSerListResult FTSerListResult returnResult = new FTSerListResult();
- 实例化dbcontext对象和function type respository,它包含数据访问逻辑并用于获取数据。
CoolPrivilegeControlContext dbContext = CoolPrivilegeControlContext.CreateContext(); FunctionTypeRespository entityRepos_FT = new FunctionTypeRespository(dbContext, entity_ServerSideSession.ID);
- 根据Function Key、Function Type Key和当前用户ID检查权限。
#region [ Check Privilege ] ret = CheckAccPrivilege(entity_ServerSideSession.ID, entity_WCFSessionVM.RequestFunKey, entity_WCFSessionVM.RequestFunTypeKey, ref strList_Error); #endregion
- 初始化FTSerListResult实例
//Initialize FTSerListResult instance returnResult.StrList_Error = strList_Error; returnResult.Int_TotalRecordCount = 0; returnResult.EntityList_FunctionTypeVM = new List<FunctionTypeVM>();
- 从服务获取数据并将其设置为输出对象
//Success if (ret) { int recordCount = 0; List<FunctionTypeVM> vmList = entityRepos_FT.GetEntityListByPage(entity_SearchCriteria, int_CurrentPage, int_PageSize, str_SortColumn, str_SortDir, out recordCount, str_CustomFilter); //Assign data to FTSerListResult instance returnResult.EntityList_FunctionTypeVM = vmList; returnResult.Int_TotalRecordCount = recordCount; } return returnResult;
完成以上步骤后,假设您已具备酷权限控制的基础知识。要描述系统中的所有功能对我来说很困难。您可以通过设置调试断点来跟踪程序。如果您对项目有任何疑问或发现任何bug,请随时与我联系。我希望你们都能从我的项目中获得启发,让我们的世界变得越来越美好。
测试驱动开发
酷权限控制系统采用测试驱动开发(TDD)方法。对许多公司来说,TDD是重复开发周期中的一项强制性方法。酷权限控制系统在所有控制器中包含40个测试用例,您可以通过单击“测试资源管理器”中的“全部运行”来轻松测试所有功能。当然,您可以根据您的需求添加新的测试用例或在原始测试用例中添加新条件。酷权限控制系统使用Xunit和Mock。Xunit是一个注入Visual Studio的测试框架,与MSTest和Nunit类似。更多信息,您可以访问官方网站。
历史
2016-02-21 首次发布
2016-03-01 添加测试项目