65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (8投票s)

2016年2月24日

Ms-PL

12分钟阅读

viewsIcon

31386

downloadIcon

1074

基于MVC和WCF的权限控制系统。

酷权限控制系统 第一部分 -- asp.net MVC

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

引言

酷权限控制是一个独立的访问权限系统。它解决了许多复杂的访问权限问题。例如,用户拥有不同的访问级别的问题,无限的功能层次结构以及易于添加功能类型。它还包含许多通用机制,例如支持多语言环境,支持三种日志类型(错误日志,跟踪日志,审计日志)。它能帮助您轻松快速地创建具有强大权限的asp.net mvc演示项目。有关详细信息,请参阅我上一篇文章。在这里,我想花一些时间感谢那些投票5星和提问的人。再次感谢。今天我想更深入地探讨系统扩展。这是什么以及如何实现?请跟随我的思路。

背景

WCF,全称是Windows Communication Foundation。它是构建面向服务应用程序的框架。我知道一些聪明人已经猜到了。是的,你猜对了!在本文中,我将把酷权限控制系统分成两个不同的部分。一个是负责UI维护的MVC项目(发送请求到服务/接收来自服务的响应),另一个是负责执行操作的WCF项目(托管在IIS中)(接收并执行来自调用者的请求/将响应发送给调用者)。让我们继续,我将介绍如何以及为何使用WCF来实现系统扩展。

设计模式

首先,我想描述一下mvc设计模式。是的,MVC设计模式。我知道很多人比我拥有更多的想法和经验。我不想欺骗那些比我更深入的人。但我认为先进性来自于原始设计模式的缺点和新需求。

图 3.1 MVC-Diagram.gif

在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的优缺点

  优点:  
  1. 易于维护。设计师专注于设计UI,开发人员专注于业务逻辑。也就是说,设计师不再关心业务逻辑和数据库操作,而开发人员则无需关心UI层。(PS:你知道,在实际情况中,有时我们都是一体的)。
  2. 易于为业务逻辑编写单元测试。
  3. 最大的优点是消除了Web窗体可怕的回发逻辑,其中包含巨大的ViewState。
  缺点:  
  1. 无法同时将业务逻辑共享到不同的UI层。例如WPF、AngularJS网站和其他用不同语言编写的应用程序。这就是SO应用程序(面向服务应用程序)的优点。
  2. 没有WS/WCF,在Asp.net mvc项目中,所有编译后的dll都必须存储在一个服务器上。如果黑客攻破了服务器,他们将很容易访问数据库并进行任何你能想到的可怕的事情。
  3. 没有WS/WCF,如果你设计asp.net mvc应用程序,你将使用Entity Framework来执行数据库操作,或者使用Enterprise Library来执行数据库操作。MVC中的Model会感知数据库模式。我认为黑客知道这些是很危险的。

注意:以上优缺点是我的个人观点。如有任何错误,请随时与我联系。

图 3.2 不带WCF和带WCF的MVC服务流程

如上图所示,在Controller接收到URL Router的请求后,Controller将调用wcf服务而不是调用Model的修改方法。然后WCF服务接收请求并调用Model的修改方法。Model执行操作并将View Model返回给调用者。Controller接收返回的View Model并将其绑定到View

带WCF的MVC的优缺点

 

  优点:  
  1. 更安全。您可以在服务服务器前设置至少一个防火墙并限制调用者。(例如,指定一个或两个服务器可以调用服务服务器)
  2. View和Controller都不知道Model。它们只知道View Model。即使黑客攻破了可以从外部访问的Web服务器,他们也不知道数据库模式和业务逻辑。
  3. 支持多UI层。
    图 3.3 支持多UI
  缺点:  
  1. 由于Web应用程序和WCF应用程序没有被托管在同一台服务器上。Web应用程序和WCF应用程序之间的连接可能是通过内部网络或互联网。这会带来网络问题。带宽决定了服务的响应时间。然而,通过多年的开发经验,这个问题并没有产生任何影响。可能有一到两秒的延迟。(PS:目前,许多新的硬件技术(如SSD、5G等)降低了风险)。此外,您还可以使用异步方法来防止主线程挂起并继续执行剩余过程,从而帮助您减少等待时间。
  2. 很容易理解,如果你正在开发一个小型项目,开发和维护过多的层级会增加项目的复杂性和维护时间。另一方面,如果你正在开发一个包含许多模块的大型系统,就像我遇到的那样,它将帮助你认识到系统的整体视图,并将不同的层/部分分配给相应的同事。

在前面的章节之后,以你现在的聪明才智,我猜你可能想使用WCF来扩展你的产品。下面的序列图说明了ASP.NET MVC中“获取HR信息”的流程,我希望这个图能在你设计复杂的酷权限控制系统时给你一些启发。

图 3.4 带WCF的酷权限控制序列图

描述

 

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程序包还原”。

图 4.0 启用NuGet程序包还原

强制步骤:请右键单击解决方案并单击“属性”,将项目“CoolPrivilegeControl”和“CoolPrivilegeServiceHost”设置为启动项目。

4.1 编辑WEB.CONFIG文件

4.1.1 对于MYSQL用户

图 4.1.1.1 托管服务 web.config(MySQL与WCF版本)

请注意appSettings节点,根据mysql服务器设置更改DBSource/DBName/DBPort/LoginName/LoginPWD的值。

属性 描述
DBSource 服务器主机的IP地址
DBName 数据库名称
DBPort 服务器TCP/IP端口
LoginName DB用户名
LoginPWD 数据库用户密码
IsDebug 如果设置为true,将显示更多详细的跟踪器信息。
图 4.1.1.2 Asp.net MVC web.config(MySQL与WCF版本)

 

属性 描述
IsDebug 如果设置为true,任何异常都会显示在页面上,反之亦然。

4.1.2 对于MSSQL用户

图 4.1.2.1 托管服务 web.config(MSSql与WCF版本)

与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。

图 4.1.3 disableDatabaseInitialization 标志

或者,您可以通过sql脚本或数据库备份来初始化数据库。我为mysql用户准备了sql脚本来执行,并为mssql用户准备了数据库备份进行恢复。

MySql脚本

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”的属性值更改为您想要的。

图 4.2.1 Log4Net.config

SysLog: 记录系统的所有信息。
ErrorLog: 只记录系统的异常。

在mvc项目中,之前的设置是全局设置。如果您想自动化地禁用一个或任何函数来记录信息。您可以将您想要的函数标记为“[UnTracerAction]”函数。这样做之后,所有信息都不会被记录。

图 4.2.2 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");
图 4.2.4 不带详细信息的跟踪器动作日志

图 4.2.5 带详细信息的跟踪器动作日志

完成上述设置后,系统即可供您使用。在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);
}

分步代码说明

  1. 设置消息框标题。
        //Message Box Title -- When Error occured, Message Box would be showed.
        string str_MsgBoxTitle = MultilingualHelper.GetStringFromResource(languageKey, "FTManage");
        
  2. 声明变量recordCount和entityList_Result,用于接收服务调用后的总记录数和Function Type列表。
        //Declare output variable(recordCount && entityList_Result)
        int recordCount = 0;
        List<FunctionTypeVM> entityList_Result = new List<FunctionTypeVM>();
        
  3. 声明WCF输出对象
        //Declare wcf output object;
        FTSerListResult entity_FTSerListResult = null;     
  4. 实例化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));
    });
    
  5. 将数据分配给局部变量
        //Assign data to local variable
        if (entity_FTSerListResult != null)
        {
            recordCount = entity_FTSerListResult.Int_TotalRecordCount;
            entityList_Result = entity_FTSerListResult.EntityList_FunctionTypeVM;
        }
    
  6. 缓存分页信息以设置分页条。
        //Set paging bar info (Total Record Count and Page Index)
        StorePageInfo(recordCount, 1);
        
  7. 缓存选择条件。
        //Cache selection criteria
        StoreSelectionCriteria<FunctionTypeVM>(selectionCriteria);
  8. 如果发生错误,则将错误信息返回给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);
        }
    }

分步代码说明

  1. 通过令牌恢复wcf会话
            //Restore Server Session by token
            RetrieveServerSideSession(entity_WCFSessionVM);
            
  2. 定义标志和错误列表
            //Flag Success or Fail 
            bool ret = false;
            //Define error list
            List<string> strList_Error = new List<string>();
            
  3. 实例化FTSerListResult对象,用于回复请求。
            //Instantiate  FTSerListResult
            FTSerListResult returnResult = new FTSerListResult();
            
  4. 实例化dbcontext对象和function type respository,它包含数据访问逻辑并用于获取数据。
            CoolPrivilegeControlContext dbContext = CoolPrivilegeControlContext.CreateContext();
    
            FunctionTypeRespository entityRepos_FT = new FunctionTypeRespository(dbContext, entity_ServerSideSession.ID);
            
  5. 根据Function Key、Function Type Key和当前用户ID检查权限。
            #region [ Check Privilege ]
            ret = CheckAccPrivilege(entity_ServerSideSession.ID, entity_WCFSessionVM.RequestFunKey, entity_WCFSessionVM.RequestFunTypeKey, ref strList_Error);
            #endregion
            
  6. 初始化FTSerListResult实例
            //Initialize FTSerListResult instance 
            returnResult.StrList_Error = strList_Error;
            returnResult.Int_TotalRecordCount = 0;
            returnResult.EntityList_FunctionTypeVM = new List<FunctionTypeVM>();
            
  7. 从服务获取数据并将其设置为输出对象
            //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类似。更多信息,您可以访问官方网站。

图 6.0.1 测试资源管理器

历史

2016-02-21 首次发布

2016-03-01 添加测试项目

© . All rights reserved.