zoukankan      html  css  js  c++  java
  • .NET MVC权限设计思考之切入点

       在WebForm下我们一般会设计个PageBase继承Page,在OnInit方法中实现对基本权限的验证业务,然后所有的页面在继承PageBase直接继承这项基本权验证业务。而在.NET MVC下我们如何再实现这个业务呢? 其实无非也是要设计一个ExtController基类来实现这个业务,而这个ExtController基类的权限验证业务切入点选在哪里合适呢? 这个答案还要从前面的 了解.net MVC的实现原理Controller/Action 章节寻找。(标签属性IActionFilter, IAuthorizationFilter暂且不涉及)

    一、寻找合适时机的切入点 

         简单的回顾一下这个过程,首先Controller中的Action要被执行,那Controller就要被实例化,接着才能根据请求的URL,调用对应的Action。Controller是被MvcHandler的ProcessRequest或BeginProcessRequest方法的这段代码创建的。

    复制代码
      private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory) {
    // If request validation has already been enabled, make it lazy. This allows attributes like [HttpPost] (which looks
    // at Request.Form) to work correctly without triggering full validation.
    bool? isRequestValidationEnabled = ValidationUtility.IsValidationEnabled(HttpContext.Current);
    if (isRequestValidationEnabled == true) {
    ValidationUtility.EnableDynamicValidation(HttpContext.Current);
    }

    AddVersionHeader(httpContext);
    RemoveOptionalRoutingParameters();

    // Get the controller type
    string controllerName = RequestContext.RouteData.GetRequiredString("controller");

    // Instantiate the controller and call Execute
    factory = ControllerBuilder.GetControllerFactory();
    controller = factory.CreateController(RequestContext, controllerName);
    if (controller == null) {
    throw new InvalidOperationException(
    String.Format(
    CultureInfo.CurrentCulture,
    MvcResources.ControllerBuilder_FactoryReturnedNull,
    factory.GetType(),
    controllerName));
    }
    }
    复制代码

         相信很多人都通IOC容器重新实现过ControllerBuilder的DefaultControllerFactory

                ControllerBuilder.Current.SetControllerFactory(new UnityControllerFactory(ContainerFactory.GetContainer()));

          通过上面代码我们可以知道Controller是在何时被创建的,我们第一次有机会拦截Controller对其做一定处理的时机,就是重新实现ControllerBuilder的DefaultControllerFactory中的这个方法GetControllerInstance。我们再继续寻找其它的切入点,当MvcHandler通过ControllerFactory创建了请求对应的Controller的实例后,会继续调用自己的这个方法

    View Code

         执行IController接口的Execute方法,我们来看一下最终是执行的Controller.Execute->ControllerBase.Execute的方法

    复制代码
      protected virtual void Execute(RequestContext requestContext) {
    if (requestContext == null) {
    throw new ArgumentNullException("requestContext");
    }
    if (requestContext.HttpContext == null) {
    throw new ArgumentException(MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, "requestContext");
    }

    VerifyExecuteCalledOnce();
    Initialize(requestContext);

    using (ScopeStorage.CreateTransientScope()) {
    ExecuteCore();
    }
    }
    复制代码

        注意ControllerBase中Execute本身这个方法,以及其中的Initialize方法,ExecuteCore方法均是 Action被执行前的实现基本权限验证业务的切入点。

    另外在Controller中ExecuteCore 方法被重载实现

    View Code

         其中ActionInvoker.InvokeAction方法是反射执行对应URL请求的Action,在这个方法,我们可以清楚看到当在Action被执行前,又分别先执行了Controller中的这个两个方法

    复制代码
     protected override void OnActionExecuting(System.Web.Mvc.ActionExecutingContext filterContext)
    {
    base.OnActionExecuting(filterContext);
    }

    protected override void OnAuthorization(System.Web.Mvc.AuthorizationContext filterContext)
    {
    base.OnAuthorization(filterContext);
    }
    复制代码

    因此Controller的OnActionExecuting,OnAuthorization也可以做为Action 被执行前的权限验证切入点。好了,通过源码找到了这么多的切入点,那我们就分别来实现一下试试。

    二、权限验证切入点的尝试

    首先设计个继承Controller的基类BaseController,详细设计请参照注释。大部分思考内容都写在代码注释部分,文章中就不重复写了。

    复制代码
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    namespace Demo.Mvc.Core
    {
    ///<summary>
    /// 登录成功的时候被放入SESSION中
    ///</summary>
    public class UserState
    {
    public string PassCode { set; get; }
    public string UserId { set; get; }
    public string UserName { set; get; }

    //public List<Role> RoleCollection { set; get; } //角色集
    //public List<Auth> AuthCollection { set; get; } //权限集
    }

    public class BaseController:System.Web.Mvc.Controller
    {
    ///<summary>
    /// 用户信息
    ///</summary>
    public UserState UserState { set; get; }

    ///<summary>
    /// 微软设计这个无参的构造的Controller 有利于使用IOC容器提高对象的创建效率
    /// 如果设计了System.Web.Routing.RequestContext参数,由于每次来的RequestContext都不相同
    /// 则Controller 就要不停的动态创建
    ///</summary>
    public BaseController()
    {
    //无参的构造
    }

    ///<summary>
    /// 改造一个构造函数切入点
    /// 这种方式虽然使得切入机会早,并且可以较早的构造中对业务层注入一些用户信息。
    /// 但是缺点就是每次都要动态反射(因为每次来的HttpContext请求都不相同)
    ///</summary>
    ///<param name="requestContext"></param>
    public BaseController(System.Web.Routing.RequestContext requestContext)
    {
    this.OnInit(requestContext); //这样可以在构造的时候就切入了
    }

    ///<summary>
    /// 比较早的切入点 在ControllerFactory被创建的时候顺便就实现权限验证
    ///</summary>
    ///<param name="requestContext"></param>
    public virtual void OnInit(System.Web.Routing.RequestContext requestContext)
    {
    //这里实现用户信息的相关验证业务
    if (requestContext.HttpContext.Session["UserState"] != null)
    {
    UserState userState = requestContext.HttpContext.Session["UserState"] as UserState;
    string passCode = requestContext.HttpContext.Request.Cookies["UserState"].Value.Trim();

    string controllerName = requestContext.RouteData.Values["controller"].ToString()+"Controller";
    string actionName = requestContext.RouteData.Values["action"].ToString();

    //判断有没有Action操作权限
    //userState.AuthCollection.Contains(controllerName + "/" + acitonName);
    }
    else
    {
    //非登录用户跳转
    //requestContext.HttpContext.Response.Redirect("/html/complex.html");
    }
    }

    ///<summary>
    /// 比较晚的切入点 IController在执行Execute之后,Action被执行之前使用的
    ///</summary>
    public virtual void OnInit()
    {
    //这里实现用户信息的相关验证业务
    if (this.HttpContext.Session["UserState"] != null)
    {
    UserState userState = this.HttpContext.Session["UserState"] as UserState;
    string passCode = this.HttpContext.Request.Cookies["UserState"].Value.Trim();

    string controllerName = this.RouteData.Values["controller"].ToString() + "Controller";
    string actionName = this.RouteData.Values["action"].ToString();

    //实现Action操作权限验证业务
    //userState.AuthCollection.Contains(controllerName + "/" + acitonName);
    }
    else
    {
    //非登录用户跳转
    //requestContext.HttpContext.Response.Redirect("/html/complex.html");
    }
    }


    protected override void Execute(System.Web.Routing.RequestContext requestContext)
    {
    base.Execute(requestContext);
    //this.OnInit();//---------------------------------------------切入点
    }

    protected override void ExecuteCore()
    {
    base.ExecuteCore();
    //this.OnInit();//---------------------------------------------切入点
    }

    protected override void Initialize(System.Web.Routing.RequestContext requestContext)
    {
    base.Initialize(requestContext);
    this.OnInit(); //---------------------------------------------切入点
    }

    //除上述的方式以下方式
    //我们还可以使用IActionFilter, IAuthorizationFilter标签属性的方式实现权限验证 (这个不在本次讨研究范围内)
    protected override void OnActionExecuting(System.Web.Mvc.ActionExecutingContext filterContext)
    {
    base.OnActionExecuting(filterContext);
    //this.OnInit();//---------------------------------------------切入点
    }

    protected override void OnAuthorization(System.Web.Mvc.AuthorizationContext filterContext)
    {
    base.OnAuthorization(filterContext);
    //this.OnInit();//---------------------------------------------切入点
    }

    }
    }
    复制代码

          里面列出了实现基类的中的多个切入点,其中第一个OnInit 方法的设计 是由于Controller在构造实例时 并没有合适的切入的点,所以通过RequestContext的注入可以使我们可以将切入点提到ControllerFactory中。 第二个OnInit的切入比较晚,都是在IController的 Execute被执行后,对应的Action被执行前。当然第二种情况已经完全满足了我们的业务需要,为什么还要第一种OnInit的设计?我们先来重构一下ControllerFactory,改变默认的Controller的创建方式。

    复制代码
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Web.Mvc;
    using System.Web.Routing;
    using Microsoft.Practices.Unity;

    namespace Demo.Mvc
    {
    public class UnityControllerFactory : DefaultControllerFactory
    {
    private readonly IUnityContainer container;

    public UnityControllerFactory(IUnityContainer container)
    {
    //要做异常处理
    this.container = container;
    }

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
    if (controllerType.IsSubclassOf(typeof(Core.BaseController)))
    {
    //早期切入点 方式1,使用默认的无构造方式
    //目的必免直接反射Controller,提高效率
    Core.BaseController controller = container.Resolve(controllerType) as Core.BaseController;
    controller.OnInit(requestContext); //切入点

    //假如这个位置使用构造注入那OnInit就会构造函数执行时就实现相关验证业务包括UserState已经创建
    //这样做导致Controller每次都要被动态反射创建,但好处是可以使用权限切入点更早
    //这样权限业务可以更早的注入业务层中(如UserState传递至业务层)
    //代码省略

    return controller;
    }
    else
    return container.Resolve(controllerType) as IController;
    }

    public override IController CreateController(RequestContext requestContext, string controllerName)
    {
    return base.CreateController(requestContext, controllerName);
    }

    }


    }
    复制代码

    设计Controller构造切入点,主要是因为子类MenuController的构造一般会注入如Public MenuController(FileService service)构造,如果我们可以BaseController构造时就完成权限对象的创建,那就完全有机会在子类构造将权限对象传递到Service中,甚至利用IOC容器一样可以实现。 一般大家都会在MenuController中构造一个全局的FileService对象,如果基类还未获得权限对象,那只能等到对应的Action要被执行前时这些权限验证对象才会被创建,也就是说FileService获得验证模型时间就被推迟到Action中,亦或者MenuController重载BaseController的一系列切入点。这个不是Filter机制能带来的效果。

    下面是实现的MenuController ,详细参照注释

    复制代码
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Web;
    using System.Web.Mvc;
    using System.ComponentModel;
    using Demo.Model;
    using Demo.Service.IService;
    using Demo.Mvc.ViewObject;

    namespace Demo.Mvc.Controller
    {


    [HandleError]
    public class MenuController:Core.BaseController
    {
    private IMenuService service;

    //如果我们ControllerFactory中使用构造生成Controller,那OnInit在构造的同时也成功获得了UserState
    //并且可以在这个时候将UserState注入到业务层中
    public MenuController(IMenuService service)
    {

    this.service = service;
    //this.service.UserState = this.UserState;
    //这样做的优点就是不用再重载基类的Initialize方法,前提ControllerFactory中使用构造生成Controller,这样可以过早的拿到Session.
    }

    //由于在ControllerFactory无构造生成Controller,就会导OnInit的切入时机比较晚,这个时候UserState还是NULL(即还没有拿到HttpContext中的Session)
    //所以我们只能通过在重载一次基类的方式,才能获得已经创建的UserState信息
    protected override void Initialize(System.Web.Routing.RequestContext requestContext)
    {
    base.Initialize(requestContext);
    //this.service.UserState = this.UserState;
    }


    public MenuController()
    { }

    public ActionResult ShowContent(object model)
    {
    //service.Update(model,this.UserState) //用户信息传至业务层 通过业务层方法参数注入
    return View();
    }

    public ActionResult Index(UserVO userVo)
    {
    if (ModelState.IsValid)
    {
    ViewData["Key1"] = "TEST1";
    TempData["Key2"] = "TEST2";
    }
    else
    {

    }
    return View(); //默认封装ViewResult返回
    }

    public ActionResult DownLoadFile(string fileName)
    {
    return File(Server.MapPath(@"/Images/view.jpg"), @"image/gif");
    }

    public ActionResult ToOther(string fileName)
    {
    return Redirect(@"http://localhost:1847/Menu/ShowContent");
    }

    }

    }
    复制代码

    以上就是对.NET MVC 基本权限验证实现的分析和模拟实现。如果.net mvc 的controller不用来实现业务,还要另外提取实现业务层的情况下,那本身对Controller的Action拦截是否还有意义呢? 因为对具体业务方法拦截才是最终目的,这导致类似AOP的拦截框架是与业务层绑定的,因此只要针对业务层实现即可了,并且还可以重用。而此时.net mvc 在提供Controller对Action 的拦截机制似乎就变得多余了。我没必要对Action拦截一次,还要对业务层具体业务再拦截一次。大家对这个问题怎么看?希望分享一下自己的经验。 

  • 相关阅读:
    December 23rd 2016 Week 52nd Friday
    December 22nd 2016 Week 52nd Thursday
    December 21st 2016 Week 52nd Wednesday
    December 20th 2016 Week 52nd Tuesday
    December 19th 2016 Week 52nd Sunday
    December 18th 2016 Week 52nd Sunday
    uva294(唯一分解定理)
    uva11624Fire!(bfs)
    fzu2150Fire Game(双起点bfs)
    poj3276Face The Right Way
  • 原文地址:https://www.cnblogs.com/wangyt/p/3684255.html
Copyright © 2011-2022 走看看