ASP.net MVC 基于角色的权限控制系统的实现 - 夜の魔王 - 博客园

mikel阅读(700)

来源: ASP.net MVC 基于角色的权限控制系统的实现 – 夜の魔王 – 博客园

一、引言

我们都知道ASP.NET mvc权限控制都是实现AuthorizeAttribute类的OnAuthorization方法

下面是最常见的实现方式:

复制代码
 public class CustomAuthorizeAttribute : AuthorizeAttribute
    {
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
           if (!filterContext.RequestContext.HttpContext.Request.IsAuthenticated)
            {
                filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "account", action = "login", returnUrl = filterContext.HttpContext.Request.Url, returnMessage = "您无权查看." }));
                return;
            }
          base.OnAuthorization(filterContext);
        }
}
复制代码

然后在需要验证的Action上打上[CustomAuthorize]标签就可以了。

这种方式是比较粗粒度的解决方案,由于是已经将定义好(约定好的)权限hard code带对应的Action上,所以无法实现用户自定义权限控制。

看一下代码:

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace Deepleo.Role.Controllers
{
    public class UserController : Controller
    {
        [UserAuthorize]
        public ActionResult Index()
        {
            return View();
        }
       [AdminAuthorize]
        public ActionResult Admin()
        {
            return View();
        }
        [UserAuthorize]
        public ActionResult Detail()
        {
            return View();
        }
    }
}
复制代码

我们有一个UserController,他有3个Action:Index,Admin,Detail.其中Admin需要系统管理员权限,其他两个值需要User权限。这样就需要建立AdminAuthorizeAttributeUserAuthorizeAttribute.这样做就无法实现用户自定义权限。

二、基于角色的权限控制系统

基于角色的权限控制系统RBAC(Role Based Access Control)是目前最流行,也是最通用的权限控制系统。

对于ASP.NET MVC来说,这套系统很容易实现:Controller下的每一个Action可以看作是一个权限,角色就相当于多个权限的组合。

然后我们新建一个RoleAuthorizeAttribute,即对角色的属性描述。

2.1 如何鉴权

这个RoleAuthorizeAttribute的关键在于如何拿到ControllerName和ActionName,查阅msdn其实很容易就实现了,不多说,直接上代码

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.Mvc;
using System.Web.Routing;
using Deepleo.Role.Services;

namespace Deepleo.Role.Attributes
{
    public class RoleAuthorizeAttribute : AuthorizeAttribute
    {
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            var isAuth = false;
            if (!filterContext.RequestContext.HttpContext.Request.IsAuthenticated)
            {
                isAuth = false;
            }
            else
            {
                if (filterContext.RequestContext.HttpContext.User.Identity != null)
                {
                    var roleService = new RoleService();
                    var actionDescriptor = filterContext.ActionDescriptor;
                    var controllerDescriptor = actionDescriptor.ControllerDescriptor;
                    var controller = controllerDescriptor.ControllerName;
                    var action = actionDescriptor.ActionName;
                    var ticket = (filterContext.RequestContext.HttpContext.User.Identity as FormsIdentity).Ticket;
                    var role = roleService.GetById(ticket.Version);
                    if (role != null)
                    {
                        isAuth = role.Permissions.Any(x => x.Permission.Controller.ToLower() == controller.ToLower() && x.Permission.Action.ToLower() == action.ToLower());
                    }
                }
            }
            if (!isAuth)
            {
                filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "account", action = "login", returnUrl = filterContext.HttpContext.Request.Url, returnMessage = "您无权查看." }));
                return;
            }
            else
            {
                base.OnAuthorization(filterContext);
            }
        }
    }
}
复制代码

注意:这里用Ticket的Version存储RoleId(最好不要这样,原因看我的另一篇博文:http://www.cnblogs.com/deepleo/p/iso_cookies_formsAuthenticationTicket_version.html)。你也可以用其他方式。

主要是用到了 filterContext.ActionDescriptor和filterContext.ActionDescriptor。

2.2 如何生成权限控制列表

前面的role.Permissions的集合已经是定义好的权限列表。

Permissions类的定义如下:

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

namespace Deepleo.Role.Entities
{
    public class PermissionDefinition
    {
        public virtual int Id
        {
            set;
            get;
        }
        public virtual int ActionNo
        {
            set;
            get;
        }

        public virtual int ControllerNo
        {
            set;
            get;
        }
        public virtual string Name
        {
            set;
            get;
        }

        public virtual string ControllerName
        {
            set;
            get;
        }
        public virtual string Controller
        {
            set;
            get;
        }
        public virtual string Action
        {
            set;
            get;
        }
        public virtual DateTime AddDate
        {
            set;
            get;
        }
    }
}
复制代码

属性Controller和Action记录的是权限,ControllerName和ActionName用于显示UI,ControllerNo和ActionNo用于显示顺序控制。

这里你可以手工将所有Action录入数据库中,然后实现RolService即可。但是显然这种方法实在是太笨了,我们其实可以用反射+Attribute机制实现自动化载入权限控制表。原理很简单,我就直接上关键代码了。

以下是反射的代码:

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Deepleo.Role.Services;
using Deepleo.Role.Attributes;
using Deepleo.Role.Entities;

namespace Deepleo.Role.Controllers
{
    public class InstallController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Index()
        {
            try
            {
                var roleService = new RoleService();
                #region init permission
                createPermission(new UserController());
                #endregion

                var allDefinedPermissions = roleService.GetDefinedPermissions();
                #region 超级管理员角色初始化
                var adminPermissions = new List<RolePermissionInfo>();
                foreach (var d in allDefinedPermissions)
                {
                    adminPermissions.Add(new RolePermissionInfo { AddDate = DateTime.Now, Permission = d, });
                }
                int adminRoleId = roleService.AddRole(new Entities.RoleInfo
                {
                    AddDate = DateTime.Now,
                    Description = "",
                    Name = "超级管理员",
                    Permissions = adminPermissions
                });
                #endregion
                return RedirectToAction("Admin", "User");
            }
            catch (Exception ex)
            {
                ModelState.AddModelError("", ex.Message);
                return View();
            }
        }
        private void createPermission(Controller customController)
        {
            var roleService = new RoleService();

            var controllerName = "";
            var controller = ""; var controllerNo = 0;
            var actionName = ""; var action = ""; var actionNo = 0;
            var controllerDesc = new KeyValuePair<string, int>();

            var controllerType = customController.GetType();
            controller = controllerType.Name.Replace("Controller", "");//remobe controller posfix
            controllerDesc = getdesc(controllerType);
            if (!string.IsNullOrEmpty(controllerDesc.Key))
            {
                controllerName = controllerDesc.Key;
                controllerNo = controllerDesc.Value;
                foreach (var m in controllerType.GetMethods())
                {
                    var mDesc = getPropertyDesc(m);
                    if (string.IsNullOrEmpty(mDesc.Key)) continue;
                    action = m.Name;
                    actionName = mDesc.Key;
                    actionNo = mDesc.Value;
                    roleService.CreatePermissions(actionNo, controllerNo, actionName, controllerName, controller, action);
                }
            }
        }
        private KeyValuePair<string, int> getdesc(Type type)
        {
            var descriptionAttribute = (DescriptionAttribute)(type.GetCustomAttributes(false).FirstOrDefault(x => x is DescriptionAttribute));
            if (descriptionAttribute == null) return new KeyValuePair<string, int>();
            return new KeyValuePair<string, int>(descriptionAttribute.Name, descriptionAttribute.No);
        }
        private KeyValuePair<string, int> getPropertyDesc(System.Reflection.MethodInfo type)
        {
            var descriptionAttribute = (DescriptionAttribute)(type.GetCustomAttributes(false).FirstOrDefault(x => x is DescriptionAttribute));
            if (descriptionAttribute == null) return new KeyValuePair<string, int>();
            return new KeyValuePair<string, int>(descriptionAttribute.Name, descriptionAttribute.No);
        }
    }
}
复制代码

以下是DescriptionAttribute的代码:

 

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

namespace Deepleo.Role.Attributes
{
    public class DescriptionAttribute : Attribute
    {
        public string Name
        {
            set;
            get;
        }
        public int No
        {
            set;
            get;
        }
    }
}
复制代码

然后在UserController打上DescriptionAttribute标签就可以了,如下所示:

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Deepleo.Role.Attributes;

namespace Deepleo.Role.Controllers
{
    [Description(No = 1, Name = "用户")]
    public class UserController : Controller
    {
        [RoleAuthorize]
        [Description(No = 1, Name = "用户首页")]
        public ActionResult Index()
        {
            return View();
        }
        [RoleAuthorize]
        [Description(No = 1, Name = "用户管理")]
        public ActionResult Admin()
        {
            return View();
        }
        [RoleAuthorize]
        [Description(No = 1, Name = "用户详情")]
        public ActionResult Detail()
        {
            return View();
        }
    }
}
复制代码

 

这样在网站安装的时候直接执行Install就可以完全自动化创建权限。

这样就可以精确到每个Action的用户自定义权限控制了。

看看我的劳动成果:

写在最后:对于同名的Action的HttpGET和HttpPOST分成两个权限还没有实现。比如说:New[HttpGet],和New[HttpPOST]。主要是觉得这样没有太大的意义,当然如果你的业务需求必须这样,我觉得应该很容易就能扩展。

完整代码下载:http://files.cnblogs.com/deepleo/RoleSolution.rar

PS:代码只有关键代码,没有实现RoleService方法,请自行根据自己的实际情况实现。

长期从事微信公众平台开发以及相关项目,开发速度快、代码质量高。详细请参见我的github开源项目:https://github.com/night-king/weixinSDK,联系QQ:2586662969

[转载]ASP.NET路由系统实现原理:HttpHandler的动态映射 - Artech - 博客园

mikel阅读(745)

来源: [转载]ASP.NET路由系统实现原理:HttpHandler的动态映射 – Artech – 博客园

我们知道一个请求最终通过一个具体的HttpHandler进行处理,而我们熟悉的用于 表示一个Web页面的Page对象就是一个HttpHandler,被用于处理基于某个.aspx文件的请求。我们可以通过HttpHandler的动态 映射来实现请求地址与物理文件路径之间的分离。实际上ASP.NET路由系统就是采用了这样的实现原理。如下图所示,ASP.NET路由系统通过一个注册 到当前应用的自定义HttpModule对所有的请求进行拦截,并通过对请求的分析为之动态匹配一个用于处理它的HttpHandler。 HttpHandler对请求进行处理后将相应的结果写入HTTP回复以实现对请求的相应。

clip_image002

目录
一、UrlRoutingModule
一、UrlRoutingModule
二、PageRouteHandler V.S. MvcRouteHandler
三、ASP.NET路由系统扩展
实例演示:通过自定义Route对ASP.NET路由系统进行扩展

上图所示的作为请求拦截器的HttpModule类型为UrlRoutingModule。如下面的代码片断所示,UrlRoutingModule对请求的拦截是通过注册表示当前应用的HttpApplication的PostResolveRequestCache事件实现的。

   1: public class UrlRoutingModule : IHttpModule
   2: {
   3:     //其他成员
   4:     public RouteCollection RouteCollection { get; set; }
   5:     public void Init(HttpApplication context)
   6:     {
   7:         context.PostResolveRequestCache += new EventHandler(this.OnApplicationPostResolveRequestCache);
   8:
   9:     }
  10:     private void OnApplicationPostResolveRequestCache(object sender,  EventArgs e);
  11: }

UrlRoutingModule具有一个类型为RouteCollection的RouteCollection属性,在默认的情况下引用这通过RouteTable的静态属性Routes表示的全局路由表。 针对请求的HttpHandler的动态映射就实现在OnApplicationPostResolveRequestCache方法中,具体的实现逻辑 非常简单:通过HttpApplication获得但前的HTTP上下文,并将其作为参数调用RouteCollection的GetRouteData 方法得到一个RouteData对象。

通过RouteData的RouteHandler属性可以得到一个实现了IRouteHandler的路由处理器对象,而调用后者的 GetHttpHandler方法直接可以获取对应的HttpHandler对象,而我们需要映射到当前请求的就是这么一个 HttpHandler。下面的代码片断基本上体现了定义在UrlRoutingModule的 OnApplicationPostResolveRequestCache方法中的动态HttpHandler映射逻辑。

   1: public class UrlRoutingModule : IHttpModule
   2: {
   3:     //其他成员
   4:     private void OnApplicationPostResolveRequestCache(object sender, EventArgs e)
   5:     {
   6:         HttpContext context = ((HttpApplication)sender).Context;
   7:         HttpContextBase contextWrapper = new HttpContextWrapper(context);
   8:         RouteData routeData = this.RouteCollection.GetRouteData(contextWrapper);
   9:         RequestContext requestContext = new RequestContext(contextWrapper, routeData);
  10:         IHttpHandler handler = routeData.RouteHandler.GetHttpHandler(requestContext);
  11:         context.RemapHandler(handler);
  12:     }
  13: }

二、 PageRouteHandler V.S. MvcRouteHandler

通过前面的介绍我们知道对于调用RouteCollection的GetRouteData获得的RouteData对象,其 RouteHandler来源于匹配的Route对象。对于通过调用RouteCollection的MapPageRoute方法注册的Route来 说,它的RouteHandler是一个类型为PageRouteHandler对象。

由于调用MapPageRoute方法的目的在于实现请求地址与某个.aspx页面文件之间的映射,所以我们最终还是要创建的Page对象还处理相 应的请求,所以PageRouteHandler的GetHttpHandler方法最终返回的就是针对映射页面文件路径的Page对象。此 外,MapPageRoute方法中还可以控制是否对物理文件地址实施授权,而授权在返回Page对象之前进行。

定义在PageRouteHandler中的HttpHandler获取逻辑基本上体现在如下的代码片断中,两个属性VirtualPath和 CheckPhysicalUrlAccess表示页面文件的地址和是否需要对物理文件地址实施URL授权,它们在构造函数中被初始化,而最终来源于调用 RouteCollection的MapPageRoute方法传入的参数。

   1: public class PageRouteHandler : IRouteHandler
   2: {
   3:     public bool CheckPhysicalUrlAccess { get; private set; }
   4:     public string VirtualPath { get; private set; }
   5:     public PageRouteHandler(string virtualPath, bool checkPhysicalUrlAccess)
   6:     {
   7:         this.VirtualPath = virtualPath;
   8:         this.CheckPhysicalUrlAccess = checkPhysicalUrlAccess;
   9:     }
  10:     public IHttpHandler GetHttpHandler(RequestContext requestContext)
  11:     {
  12:         if (this.CheckPhysicalUrlAccess)
  13:         {
  14:             //Check Physical Url Access
  15:         }
  16:         return (IHttpHandler)BuildManager.CreateInstanceFromVirtualPath(this.VirtualPath, typeof(Page))
  17:     }
  18: }

ASP.NET MVC的Route对象是通过调用RouteCollection的扩展方法MapRoute方法进行注册的,它对应的RouteHandler是一个类型为MvcRouteHandler的对象。如下面的代码片断所示,MvcRouteHandler用于获取处理当前请求的HttpHandler是一个MvcHandler对象。MvcHandler实现对Controller的激活、Action方法的执行以及对请求的相应,毫不夸张地说,整个MVC框架实现在MvcHandler之中。

   1: public class MvcRouteHandler : IRouteHandler
   2: {
   3:     //其他成员
   4:     public IHttpHandler GetHttpHandler(RequestContext requestContext)
   5:     {
   6:         return new MvcHandler(requestContext)
   7:     }
   8: }

三、 ASP.NET路由系统扩展

到此为止我们已经对ASP.NET的路由系统的实现进行了详细介绍,总的来说,整个路由系统是通过对HttpHandler的动态注册的方式来实现 的。具体来说,UrlRoutingModule通过对代表Web应用的HttpApplication的 PostResolveRequestCache事件的注册实现了对请求的拦截。对于被拦截的请求,UrlRoutingModule利用注册的路由表对 其进行匹配和解析,进而得到一个包含所有路由信息的RouteData对象。最终借助该对象的RouteHandler创建出相应的 HttpHandler映射到当前请求。从可扩展性的角度来讲,我们可以通过如下三种方式来实现我们需要的路由方式。

  • 通过集成抽象类RouteBase创建自定义Route定制路由逻辑。
  • 通过实现接口IRouteHandler创建自定义RouteHandler定制HttpHandler提供机制。
  • 通过实现IHttpHandler创建自定义HttpHandler来对请求处理。

实例演示:通过自定义Route对ASP.NET路由系统进行扩展

定义在ASP.NET路由系统中默认的路由类型Route建立了定义成文本模板的URL模式与某个物理文件之间的映射,如果我们对WCF REST有一定的了解,应该知道其中也有类似的实现。具体来说,WCF REST借助于System.UriTemplate这个对象实现了同样定义成某个文本模板的URI模式与目标操作之间的映射。篇幅所限,我们不能对 WCF REST的UriTemplate作详细的介绍,有兴趣的读者可以参考《UriTemplate、UriTemplateTable与WebHttpDispatchOperationSelector》。[源代码从这里下载]

我们创建一个新的ASP.NET Web应用,并且添加针对程序集System.ServiceModel.dll的引用(UriTemplate定义在该程序集中),然后创建如下一个针对UriTemplate的路由类型UriTemplateRoute。

   1: public class UriTemplateRoute:RouteBase
   2: {
   3:     public UriTemplate   UriTemplate { get; private set; }
   4:     public IRouteHandler     RouteHandler { get; private set; }
   5:     public RouteValueDictionary     DataTokens { get; private set; }
   6:
   7:     public UriTemplateRoute(string template, string physicalPath, object dataTokens = null)
   8:     {
   9:         this.UriTemplate = new UriTemplate(template);
  10:         this.RouteHandler = new PageRouteHandler(physicalPath);
  11:         if (null != dataTokens)
  12:         {
  13:             this.DataTokens = new RouteValueDictionary(dataTokens);
  14:         }
  15:         else
  16:         {
  17:             this.DataTokens = new RouteValueDictionary();
  18:         }
  19:     }
  20:     public override RouteData GetRouteData(HttpContextBase httpContext)
  21:     {
  22:         Uri uri = httpContext.Request.Url;
  23:         Uri baseAddress = new Uri(string.Format("{0}://{1}", uri.Scheme, uri.Authority));
  24:         UriTemplateMatch match = this.UriTemplate.Match(baseAddress, uri);
  25:         if (null == match)
  26:         {
  27:             return null;
  28:         }
  29:         RouteData routeData = new RouteData();
  30:         routeData.RouteHandler = this.RouteHandler;
  31:         routeData.Route = this;
  32:         foreach (string name in match.BoundVariables.Keys)
  33:         {
  34:             routeData.Values.Add(name,match.BoundVariables[name]);
  35:         }
  36:         foreach (var token in this.DataTokens)
  37:         {
  38:             routeData.DataTokens.Add(token.Key, token.Value);
  39:         }
  40:         return routeData;
  41:     }
  42:     public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
  43:     {
  44:         Uri uri = requestContext.HttpContext.Request.Url;
  45:         Uri baseAddress = new Uri(string.Format("{0}://{1}", uri.Scheme, uri.Authority));
  46:         Dictionary<string, string> variables = new Dictionary<string, string>();
  47:         foreach(var item in values)
  48:         {
  49:             variables.Add(item.Key, item.Value.ToString());
  50:         }
  51:
  52:         //确定段变量是否被提供
  53:         foreach (var name in this.UriTemplate.PathSegmentVariableNames)
  54:         {
  55:             if(!this.UriTemplate.Defaults.Keys.Any(key=> string.Compare(name,key,true) == 0) &&
  56:                 !values.Keys.Any(key=> string.Compare(name,key,true) == 0))
  57:             {
  58:                 return null;
  59:             }
  60:         }
  61:         //确定查询变量是否被提供
  62:         foreach (var name in this.UriTemplate.QueryValueVariableNames)
  63:         {
  64:             if(!this.UriTemplate.Defaults.Keys.Any(key=> string.Compare(name,key,true) == 0) &&
  65:                 !values.Keys.Any(key=> string.Compare(name,key,true) == 0))
  66:             {
  67:                 return null;
  68:             }
  69:         }
  70:
  71:         Uri virtualPath = this.UriTemplate.BindByName(baseAddress, variables);
  72:         string strVirtualPath = virtualPath.ToString().ToLower().Replace(baseAddress.ToString().ToLower(),"");
  73:         VirtualPathData virtualPathData =  new VirtualPathData(this, strVirtualPath);
  74:         foreach (var token in this.DataTokens)
  75:         {
  76:             virtualPathData.DataTokens.Add(token.Key, token.Value);
  77:         }
  78:         return virtualPathData;
  79:     }
  80: }

如上面的代码片断所示,UriTemplateRoute具有UriTemplate、DataTokens和RouteHandler三个只读属性,前两个通过构造函数的参数进行初始化,后者则是在构造函数中创建的PageRouteHandler对象。

用于对入栈请求进行匹配判断的GetRouteData方法中,我们解析出基于应用的基地址并量连同请求地址作为参数调用UriTemplate的 Match方法,如果返回的UriTemplateMatch对象不为Null,则意味着URL模板的模式与请求地址匹配。在匹配的情况下我们创建并返回 相应的RouteData对象,否则直接返回Null。

在用于生成出栈URL的GetVirtualPath方法中,我们通过定义在URL模板中的模板(包括变量名包含在属性 PathSegmentVariableNames的路径段变量和包含在QueryValueVariableNames属性的查询变量)是否在提供的 RouteValueDictionary字段或者默认变量列表(通过属性Defaults表示)从判断URL模板是否与提供的变量列表匹配。在匹配的情 况下通过调用UriTemplate的BindByName方法得到一个完整的Uri。由于该方法返回的是相对路径,所以我们需要将应用基地址剔除并最终 创建并返回一个VirtualPathData对象。如果不匹配,则直接返回Null。

在创建的Global.asax文件中我们采用如下的代码对我们自定义的UriTemplateRoute进行注册,选用的场景还是我们上面采用的 天气预报的例子。我个人具有基于UriTemplate的URI模板比针对Route的URL模板更好用,其中一点就是它在定义默认值方法更为直接。如下 面的代码片断所示,我们直接将默认值定义在模板中((“{areacode=010}/{days=2})。

   1: public class Global : System.Web.HttpApplication
   2: {
   3:     protected void Application_Start(object sender, EventArgs e)
   4:     {
   5:         UriTemplateRoute route = new UriTemplateRoute("{areacode=010}/{days=2}",
   6:             "~/Weather.aspx", new { defualtCity = "BeiJing", defaultDays = 2});
   7:         RouteTable.Routes.Add("default", route);
   8:     }
   9: }

在注册的路由对应的目标页面Weather.aspx的后台代码中,我们定义了如下一个GenerateUrl根据指定的区号(areacode) 和预报天数(days)创建一个Url,而Url的生成直接通过调用RouteTable的Routes属性的GetVirtualPathData方法 完成。生成的URL连同当前页面的RouteData的属性通过如下所示的HTML输出来。

   1: <body>
   2:     <form id="form1" runat="server">
   3:     <div>
   4:         <table>
   5:             <tr>
   6:                 <td>Router:</td>
   7:                 <td><%=RouteData.Route != null? RouteData.Route.GetType().FullName:"" %></td>
   8:             </tr>
   9:             <tr>
  10:                 <td>RouteHandler:</td>
  11:                 <td><%=RouteData.RouteHandler != null? RouteData.RouteHandler.GetType().FullName:"" %></td>
  12:             </tr>
  13:             <tr>
  14:                 <td>Values:</td>
  15:                 <td>
  16:                     <ul>
  17:                         <%foreach (var variable in RouteData.Values)
  18:                           {%>
  19:                         <li><%=variable.Key%>=<%=variable.Value%></li>
  20:                         <% }%>
  21:                     </ul>
  22:                 </td>
  23:             </tr>
  24:             <tr>
  25:                 <td>DataTokens:</td>
  26:                 <td>
  27:                     <ul>
  28:                         <%foreach (var variable in RouteData.DataTokens)
  29:                           {%>
  30:                         <li><%=variable.Key%>=<%=variable.Value%></li>
  31:                         <% }%>
  32:                     </ul>
  33:                 </td>
  34:             </tr>
  35:              <tr>
  36:                 <td>Generated Url:</td>
  37:                 <td>
  38:                     <%=GenerateUrl("0512",3)%>
  39:                 </td>
  40:             </tr>
  41:         </table>
  42:     </div>
  43:     </form>
  44: </body>

由于注册的URL模板所包含的段均由具有默认值的变量构成,所以当我们请求根地址时,会自动路由到Weather.aspx。下图是我们在浏览器访 问应用根目录的截图,上面显示了我们注册的UriTemplateRoute生成的RouteData的信息和生成URL(/0512/3)。

clip_image004

[转载]ASP.NET MVC路由扩展:路由映射 - Artech - 博客园

mikel阅读(1135)

来源: [转载]ASP.NET MVC路由扩展:路由映射 – Artech – 博客园

上周我写了三篇文章() 详细地介绍了ASP.NET的路由系统。ASP.NET的路由系统旨在通过注册URL模板与物理文件之间的映射进而实现请求地址与文件路径之间的分离,但 是对于ASP.NET MVC应用来说,请求的目标不再是一个具体的物理文件,而是定义在某个Controller类型中的Action方法。出于自身路由特点的需 要,ASP.NET对ASP.NET的路由系统进行了相应的扩展。

目录
一、基本路由映射
二、实例演示:注册路由映射与查看路由信息
三、基于Area的路由映射
1、AreaRegistration与AreaRegistrationContext
2、AreaRegistration的缓存
3、实例演示:查看基于Area路由信息

一、基本路由映射

通 过前面的介绍我们知道基于某个物理文件的路由映射通过调用代表全局路由表的RouteTable的静态属性Routes(一个 RouteCollection对象)的MapPageRoute方法来完成,为了实现针对目标Controller和Action的路 由,ASP.NET MVC针对RouteCollection类型定义了一系列的扩展方法以实现文件路径无关的路由映射,这些扩展方法定义在RouteCollectionExtensions类 型中。如下面的代码片断所示,RouteCollectionExtensions定义了两组方法,方法IgnoreRoute用于注册不需要进行路由的 URL模板,对应于RouteCollectionExtensions的Ignore方法;仿佛MapRoute用于进行基于URL模板的路由注册,对 应于RouteCollectionExtensions的MapPageRoute方法。

   1: public static class RouteCollectionExtensions
   2: {
   3:     //其他成员
   4:     public static void IgnoreRoute(this RouteCollection routes, string url);
   5:     public static void IgnoreRoute(this RouteCollection routes, string url, object constraints);
   6:
   7:     public static Route MapRoute(this RouteCollection routes, string name, string url);
   8:     public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults);
   9:     public static Route MapRoute(this RouteCollection routes, string name, string url, string[] namespaces);
  10:     public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints);
  11:     public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, string[] namespaces);
  12:     public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces);
  13: }

由于ASP.NET MVC的路由注册与具体的物理文件无关,所以MapRoute方法中并没有一个表示文件路径的physicalFile参数。与直接定义在RouteCollectionExtensions中的Ignore和MapPageRoute方法不同的是,表示默认变量的参数defaults和基于正则表达式的变量约束的参数constraints都不再是一个RouteValueDictionary对象,而是一个普通的object。这主要是为了编程上的便利,使得我们可以通过匿名类型的方式来指定这两个参数值。该方法在内部会通过反射的方式得到指定对象所有属性值,并转换为RouteValueDictionary对象,其属性名和属性值作为字典元素的Key和Value。

对于ASP.NET MVC来说,最终需要通过在请求地址中指定的Controller名称来创建具体的Controller实例。由于Controller名称 仅仅对应着类型的名称,Controller的成功实例化的前提是我们能够正确地解析出它的具体类型,所以我们需要使用了命名空间。在调用 MapRoute方法的时候我们可以通过字符串数组类型的参数namespaces来指定一个命名空间的列表。对于注册的命名空间,可以指定一个代表完整 命名空间的字符串,也可以使用“*”作为通配符。

添加的命名控件列表最终是被存储于Route对象的DataTokens属性中,对应的Key为“Namespaces”。MapRoute方法没有为初始化Route对象的DataTokens属性提供相应的参数,如果没有指定命名空间列表,所有通过该方法添加的Route对象的DataTokens属性总是一个空的RouteValueDictionary对象。

对于针对定义在某个Controller中的某个Action的请求,如果注册的路由表与之匹配,具体匹配的某个路由对象的 GetRouteData被调用并返回一个具体的RouteData对象。根据对请求地址进行解析得到的目标Controller和Action的名称必 须包含在该RouteData的Values属性对应的RouteValueDictionary对象中,其对应的Key分别为controlleraction

二、 实例演示:注册路由映射与查看路由信息

ASP.NET MVC通过定义在RouteCollectionExtensions中的扩展方法MapRoute进行路由映射,为了让读者对此有一个深刻的认识,我们 来进行一个简单的实例演示。我们依然沿用之前关于获取天气信息的场景,看看通过这种方式进行注册的Route对象针对匹配的HTTP请求返回怎样的 RouteData对象。[源代码从这里下载]

我们在创建的ASP.NET Web应用(不是ASP.NET MVC应用)添加一个Web页面(Default.aspx),并按照之前的方式以内联代码的方式直接将RouteData的相关属性显示出来,页面主体 部分的HTML如下所示。需要注意的是我们显示的RouteData是从定义的方法GetRouteData方法获取的,而不是对应于当前页面的 RouteData属性。

   1: <body>
   2:     <form id="form1" runat="server">
   3:     <div>
   4:         <table>
   5:             <tr>
   6:                 <td>Route:</td>
   7:                 <td><%=GetRouteData().Route != null? GetRouteData().Route.GetType().FullName:"" %></td>
   8:             </tr>
   9:             <tr>
  10:                 <td>RouteHandler:</td>
  11:                 <td><%=GetRouteData().RouteHandler != null? GetRouteData().RouteHandler.GetType().FullName:"" %></td>
  12:             </tr>
  13:             <tr>
  14:                 <td>Values:</td>
  15:                 <td>
  16:                     <ul>
  17:                         <%foreach (var variable in GetRouteData().Values)
  18:                           {%>
  19:                         <li><%=variable.Key%>=<%=variable.Value%></li>
  20:                         <% }%>
  21:                     </ul>
  22:                 </td>
  23:             </tr>
  24:             <tr>
  25:                 <td>DataTokens:</td>
  26:                 <td>
  27:                     <ul>
  28:                         <%foreach (var variable in GetRouteData().DataTokens)
  29:                           {%>
  30:                         <li><%=variable.Key%>=<%=variable.Value%></li>
  31:                         <% }%>
  32:                     </ul>
  33:                 </td>
  34:             </tr>
  35:         </table>
  36:     </div>
  37:     </form>
  38: </body>

我们将GetRouteData方法定义在当前页面的后台代码中。如下面的代码片断所示,我们手工创建了一个HttpRequest和HttpResponse对象,HttpRequest的请求的地址为“http://localhost:3721/0512/3” (3721是本Web应用对应的端口号)。根据这两个对象创建了HttpContext对象,并以此创建一个HttpContextWrapper对象。 最终我们将其作为参数调用RouteTable的Routes属性的GetRouteData方法并返回。这个方法实际上就是模拟注册的路由表针对相对地 址为“/0512/3”的HTTP请求的路由处理。

   1: public partial class Default : System.Web.UI.Page
   2: {
   3:     private RouteData routeData;
   4:     public RouteData GetRouteData()
   5:     {
   6:         if (null != routeData)
   7:         {
   8:             return routeData;
   9:         }
  10:         HttpRequest request = new HttpRequest("default.aspx", "http://localhost:3721/0512/3", null);
  11:         HttpResponse response = new HttpResponse(new StringWriter());
  12:         HttpContext context = new HttpContext(request, response);
  13:         HttpContextBase contextWrapper = new HttpContextWrapper(context);
  14:         return routeData = RouteTable.Routes.GetRouteData(contextWrapper);
  15:     }
  16: }

具体的路由映射依然定义在添加的Global.asax文件中。如下面的代码片断所示,我们通过调用RouteTable的Routes属性的MapRoute方法注册了一个采用“{areacode}/{days}”作为URL模板的路由对象,并指定了默认变量、约束和命名空间列表。

   1: public class Global : System.Web.HttpApplication
   2: {
   3:     protected void Application_Start(object sender, EventArgs e)
   4:     {
   5:         object defaults = new { areacode = "010", days = 2, defaultCity="BeiJing", defaultDays=2};
   6:         object constraints = new { areacode = @"0\d{2,3}", days = @"[1-3]{1}"};
   7:         string[] namespaces = new string[] { "Artech.Web.Mvc", "Artech.Web.Mvc.Html" };
   8:         RouteTable.Routes.MapRoute("default", "{areacode}/{days}", defaults, constraints, namespaces);
   9:     }
  10: }

如果我们现在在浏览器中访问Default.aspx页面,会得到下图所示的结果,从中我们可以得到一些有用的信息:

  • 与调用RouteCollection的MapPateRoute方法进行路由映射不同的是,这个得到的RouteData对象的RouteHandler属性是一个System.Web.Mvc.MvcRouteHandler对象。
  • 在MapRoute方法中通过defaults参数指定的两个与URL匹配无关的变量 (defaultCity=BeiJing;defaultDays=2)体现在RouteData的Values属性中。这意味着如果我们没有在URL 模板中为Controller和Action的名称定义相应的变量({controller}和{action}),也可以将它们定义成默认变量。
  • DataTokens属性中包含一个Key值为Namespaces值为字符数组的元素,我们不难猜出它对应着我们指定的命名空间列表。

clip_image002

三、基于Area的路由映射

对于一个较大规模的Web应用,我们可以从功能上通过Area将其划分为较小的单元。每个Area相当于一个独立的子系统,具有一套包含 Models、Views和Controller在内的目录结构和配置文件。一般来说,每个Area具有各自的路由规则(URL模版上一般会体现Area 的名称),而基于Area的路由映射通过AreaRegistration进行注册。

AreaRegistration与AreaRegistrationContext

基于Area的路由映射通过AreaRegistration进行注册。如下面的代码片断所示,AreaRegistration是一个抽象类,抽 象只读属性AreaName返回当前Area的名称,而抽象方法RegisterArea用于实现基于当前Area的路由注册。

   1: public abstract class AreaRegistration
   2: {
   3:     public static void RegisterAllAreas();
   4:     public static void RegisterAllAreas(object state);
   5:
   6:     public abstract void RegisterArea(AreaRegistrationContext context);
   7:     public abstract string AreaName { get; }
   8: }

AreaRegistration定义了两个抽象的静态RegisterAllAreas方法重载,参数state用于传递给具体AreaRegistration的数据。当RegisterAllArea方法执行的时候,它先遍历通过BuildManager的静态方法GetReferencedAssemblies方法得到的编译Web应用所使用的程序集,通过反射得到所有实现了接口IController的类型,并通过反射创建相应的AreaRegistration对象。对于每个AreaRegistration对象,一个AreaRegistrationContext对象被创建出来并作为参数调用它们的RegisterArea方法。

如下面的代码片断所示,AreaRegistrationContext的只读属性AreaName表示Area的名称,属性Routes是一个代 表路由表的RouteCollection对象,而State是一个用户自定义对象,它们均通过构造函数进行初始化。具体来说,对于最初通过调用 AreaRegistration的静态方法RegisterAllAreas创建的AreaRegistrationContext对 象,AreaName来源于当前AreaRegistration对象的同名属性,Routes则对应着RouteTable的静态属性Routes表示 的全局路由表,而在调用RegisterAllAreas方法指定的参数(state)作为AreaRegistrationContext对象的 State参数。

   1: public class AreaRegistrationContext
   2: {
   3:     public AreaRegistrationContext(string areaName, RouteCollection routes);
   4:     public AreaRegistrationContext(string areaName, RouteCollection routes, object state);
   5:
   6:     public Route MapRoute(string name, string url);
   7:     public Route MapRoute(string name, string url, object defaults);
   8:     public Route MapRoute(string name, string url, string[] namespaces);
   9:     public Route MapRoute(string name, string url, object defaults, object constraints);
  10:     public Route MapRoute(string name, string url, object defaults, string[] namespaces);
  11:     public Route MapRoute(string name, string url, object defaults, object constraints, string[] namespaces);
  12:
  13:     public string AreaName { get; }
  14:     public RouteCollection Routes { get; }
  15:     public object State { get; }
  16:     public ICollection<string> Namespaces { get; }
  17: }

AreaRegistrationContext的只读属性Namespaces表示一组优先匹配的命名空间(当多个同名的Controller类 型定义在不同的命名空间中)。当针对某个具体AreaRegistration的AreaRegistrationContext被创建的时候,如果AreaRegistration类型具有命名空间,那么会在这个命名空间基础上添加“.*”后缀并添加到Namespaces集合中。换言之,对于多个定义在不同命名空间中的同名Controller类型,会优先选择包含在当前AreaRegistration命名空间下的Controller

AreaRegistrationContext定义了一系列的MapRoute用于进行路由映射注册,方法的使用以及参数的含义与定义在RouteCollectionExtensions类型中的同名扩展方法一致。在这里需要特别指出的是,如果MapRoute方法没有指定命名空间,则通过属性Namespaces表示的命名空间列表会被使用;反之,该属性中包含的命名空间被直接忽略

当我们通过Visual Studio的ASP.NET MVC项目模版创建一个Web应用的时候,在的Global.asax文件中会生成如下的代码通过调用AreaRegistration的静态方法 RegisterAllAreas实现对所有Area的注册,也就是说针对所有Area的注册发生在应用启动的时候。

   1: public class MvcApplication : System.Web.HttpApplication
   2: {
   3:     protected void Application_Start()
   4:     {
   5:         AreaRegistration.RegisterAllAreas();
   6:     }
   7: }

AreaRegistration的缓存

Area的注册(主要是基于Area的路由映射注册)通过具体的AreaRegistration来实现。在应用启动的时候,为了实现对所有Area的注册,需要遍历通过调用BuildManager的 静态方法GetReferencedAssemblies方法得到的程序集列表,并通过从中找到所有AreaRegistration类型。如果一个应用 涉及到太多的程序集,这个过程可能会耗费很多时间,为了提供性能,基于AreaRegistration类型列表的缓存被采用。

注:BuildManager的静态方法 GetReferencedAssemblies返回所有页编译都必须引用的程序集引用的列表,这包括包含 Web.config 文件的<system.web>/<compilation>/<assemblies>配置节中指定的用于编译 Web应用所使用的程序集和从 App_Code 目录中的自定义代码生成的程序集以及其他顶级文件夹中的程序集。

ASP.NET MVC对AreaRegistration类型列表的缓存是基于文件的。具体来说,当通过程序集加载和反射得到了所有的AreaRegistration 类型列表后,会将其进行序列化并被保存为一个XML物理文件,这个名为MVC-AreaRegistrationTypeCache.xml的XML文件 被存放在ASP.NET的临时目录下,具体的路径如下。其中第一个针对寄宿于IIS中的Web应用,后者针对直接通过Visual Studio Developer Server作为宿主的应用。

  • %Windir%\Microsoft.NET\Framework\v{version}\Temporary ASP.NET Files\{appname}\…\…\UserCache\
  • %Windir%\Microsoft.NET\Framework\v{version}\Temporary ASP.NET Files\root\…\…\UserCache\

下面的XML片断体现了这个作为所有AreaRegistration类型缓存的XML文件的结构,从中我们可以看到所有的 AreaRegistration类型名称,连同它所在的托管模块和程序集名称都被保存了下来。当调用AreaRegistration的静态方法 RegisterAllAreas被调用之后,系统会试图加载该文件,如果该文件存在并且具有期望的结构,那么将不在通过程序集加载和反射来解析 AreaRegistration的类型,而是直接对文件内容进行反序列化从而得到所有AreaRegistration类型的列表。

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <!--This file is automatically generated. Please do not modify the contents of this file.-->
   3: <typeCache lastModified="3/22/2012 2:58:47 PM" mvcVersionId="80365b23-7a1d-42b2-9e7d-cc6f5694c6d1">
   4:   <assembly name="Artech.Admin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
   5:     <module versionId="07be22a1-781d-4ade-bd22-34b0850445ef">
   6:       <type>Artech.Admin.AdminAreaRegistration</type>
   7:     </module>
   8:   </assembly>
   9:   <assembly name="Artech.Portal, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
  10:     <module versionId="7b0490d4-427e-43cb-8cb5-ac1292bd4976">
  11:       <type>Artech.Portal.PortalAreaRegistration</type>
  12:     </module>
  13:   </assembly>
  14: </typeCache>

实例演示:查看基于Area路由信息

通过AreaRegistration实现的针对Area的路由注册具有一些特殊的细节差异,我们通过实例演示的方式来说明。我们直接使用前面创建 的演示实例,并在项目中创建一个自定义的WeatherAreaRegistration。如下面的代码片断所 示,WeatherAreaRegistration继承自抽象基类AreaRegistration,表示Area名称的AreaName属性返回 “Weahter”。在实现路由注册的RegisterArea方法中我们调用AreaRegistrationContext对象的MapRoute方 法注册了一个URL模版为“weather/{areacode}/{days}“的路由对象。默认变量值、约束也被相应地提供。[源代码从这里下载]

   1: public class WeatherAreaRegistration : AreaRegistration
   2: {
   3:     public override string AreaName
   4:     {
   5:         get { return "Weather"; }
   6:     }
   7:     public override void RegisterArea(AreaRegistrationContext context)
   8:     {
   9:         object defaults = new { areacode = "010", days = 2, defaultCity = "BeiJing", defaultDays = 2 };
  10:         object constraints = new { areacode = @"0\d{2,3}", days = @"[1-3]{1}" };
  11:         context.MapRoute("weatherDefault", "weather/{areacode}/{days}", defaults, constraints);
  12:     }
  13: }

我们在Global.asax的Application_Start方法中按照如下的方式调用AreaRegistration的静态方法 RegisterAllAreas实现对所有Area的注册。按照我们在上面介绍的Area注册原理,对于第一次RegisterAllAreas方法的 调用,会自动加载所有引用的程序集来获取所有的AreaRegistration(当然就包括我们上面定义的 WeatherAreaRegistration),最后通过反射创建相应的对象并调用RegisterArea方法。

   1: public class Global : System.Web.HttpApplication
   2: {
   3:     protected void Application_Start(object sender, EventArgs e)
   4:     {
   5:         AreaRegistration.RegisterAllAreas();
   6:     }
   7: }

对于定义在Default.aspx页面后台代码中用于进行路由匹配和获取路由信息的GetRouteData方法中,我们对创建的HttpRequest对象略加修改,使请求地址符合通过WeatherAreaRegistration注册的路由规则(/weather/0512/3)。

   1: public partial class Default : System.Web.UI.Page
   2: {
   3:     private RouteData routeData;
   4:     public RouteData GetRouteData()
   5:     {
   6:         if (null != routeData)
   7:         {
   8:             return routeData;
   9:         }
  10:         HttpRequest request = new HttpRequest("default.aspx", "http://localhost:3721/weather/0512/3", null);
  11:         HttpResponse response = new HttpResponse(new StringWriter());
  12:         HttpContext context = new HttpContext(request, response);
  13:         HttpContextBase contextWrapper = new HttpContextWrapper(context);
  14:         return routeData = RouteTable.Routes.GetRouteData(contextWrapper);
  15:     }
  16: }

在浏览器中访问Default.aspx页面,我们会得到如图2-10所示的结果。通过AreaRegistration注册的路由对象得到的 RouteData的不同之处主要反映在其DataTokens属性上。如下图所示,除了表示命名空间列表的元素,DataTokens属性表示的 RouteValueDictionary还具有两个额外的元素,其中一个Key为“area”的元素代表Area的名称,另一个Key为“UseNamespaceFallback”的元素具有一个布尔类型的值表示是否需要使用后备的命名空间来解析Controller的类型。

clip_image002[4]

如果调用AreaRegistrationContext的MapRoute方法是显式指定了命名空间,或者说对应的 AreaRegistration定义在某个命名空间下,这个名称为“UseNamespaceFallback”的DataToken元素的值为 False;反之为True。进一步来说,如果在调用MapRoute方法时指定了命名空间列表,那么AreaRegistration类型所示在命名空间会被忽略。也就是说,后者是前者的一个后备,前者具有更高的优先级。

AreaRegistration类型所示在命名空间也不说直接作为最终RouteData的DataTokens中的命名空间,而是在此基础上加上“.*”后缀。如果对本实例得到得到包含RouteData的DataTokens集合中的命名空间,你会发现其值为“WebApp.*”(WebApp是定义WeatherAreaRegistration的命名空间)。

ASP.NET MVC路由展:路由映射
ASP.NET MVC路由扩展:链接和URL的生成

[转载]ASP.NET MVC Controller激活系统详解:IoC的应用[下篇] - Artech - 博客园

mikel阅读(1094)

来源: [转载]ASP.NET MVC Controller激活系统详解:IoC的应用[下篇] – Artech – 博客园

[上篇] 除了通过自定义ControllerFactory的方式引入IoC之外,在使用默认DefaultControllerFactory情况下也可以通过 一些扩展使基于IoC的Controller激活成为可能。主要的方式就是自定义ControllerActivator和 DependencyResolver。

四、ControllerActivator V.S. DependencyResolver

如下面的代码片断所示,DefaultControllerFactory具有两个构造函数重载,其中一个具有一个类型为IControllerActivator接口的参数,我们将实现了该接口得类型统称为ControllerActivator。

   1: public class DefaultControllerFactory : IControllerFactory
   2: {
   3:     //其他成员
   4:     public DefaultControllerFactory();
   5:     public DefaultControllerFactory(IControllerActivator controllerActivator);
   6: }

顾名思义,ControllerActivator就是Controller的“激活器”,Controller的激活实现在唯一的Create方 法中。如下面的代码所示,该方法具有两个参数(requestContext和controllerType),分别代表当前请求上下文和解析出来的目标 Controller的类型。

   1: public interface IControllerActivator
   2: {
   3:     IController Create(RequestContext requestContext, Type controllerType);
   4: }

在默认的情况下(调用DefaultControllerFactory默认构造函数或者指定的参数为Null),Controller激活系统 会默认使用一个类型为DefaultControllerActivator的对象。如下面的代码片断所 示,DefaultControllerActivator是一个实现了IControllerActivator私有类型而已,我们不能直接通过编程的 方式使用它。

   1: private class DefaultControllerActivator : IControllerActivator
   2: {
   3:     public DefaultControllerActivator();
   4:     public DefaultControllerActivator(IDependencyResolver resolver);
   5:      public IController Create(RequestContext requestContext,  Type controllerType);
   6: }

DefaultControllerActivator的构造函数具有一个类型为IDependencyResolver的 参数,这是一个重要的接口,我们将实现了该接口的类型统称为DependencyResolver。。如下面的代码片断所 示,IDependencyResolver接口具有两个方法GetService和GetServices,用于根据指定的类型获取单个或者多个实例。 实际上DefaultControllerActivator就是通过调用GetService方法获取具体的Controller对象的

   1: public interface IDependencyResolver
   2: {
   3:     object GetService(Type serviceType);
   4:     IEnumerable<object> GetServices(Type serviceType);
   5: }

如果在构造DefaultControllerActivator对象的时候传入的参数为Null,那么Controller激活系统会使用通过DependencyResolver的静态只读属性Current表示DependencyResolver。需要提醒的是,DependencyResolver类型没有实现IDependencyResolver接口,而是对一个实现了IDependencyResolver接口类型对象的封装。

   1: public class DependencyResolver
   2: {
   3: private static DependencyResolver _instance;
   4:
   5:     public void InnerSetResolver(object commonServiceLocator);
   6:     public void InnerSetResolver(IDependencyResolver resolver);
   7:     public void InnerSetResolver(Func<Type, object> getService, Func<Type, IEnumerable<object>> getServices);
   8:
   9:     public static void SetResolver(object commonServiceLocator);
  10:     public static void SetResolver(IDependencyResolver resolver);
  11:     public static void SetResolver(Func<Type, object> getService, Func<Type, IEnumerable<object>> getServices);
  12:
  13:     public static IDependencyResolver Current { get; }
  14:     public IDependencyResolver InnerCurrent { get; }
  15: }

这个被封装的DependencyResolver(指实现了接口IDependencyResolver的某个类型的类型,不是指 DependencyResolver类型的对象,对于后者我会采用“DependencyResolver类型对象”的说法)通过只读属性 InnerCurrent表示,而三个InnerSetResolver方法重载用于初始化改属性。静态字段_instance表示当前的 DependencyResolver类型对象,静态只读属性Current则表示该对象内部封装的DependencyResolver对象,而它通过 三个静态的SetResolver进行初始化。

如果我们不曾通过调用DependencyResolver的静态方法SetResolver通过Current属性表示的当前 DependencyResolver进行显示设置,该属性默认返回一个DefaultDependencyResolver对象。如下面的代码片断所 示,DefaultDependencyResolver是一个实现了IDependencyResolver接口的私有类型,在实现的 GetService方法中,它直接通过根据指定的类型以反射的形式创建相应的对象并返回,所以前面我们说 DefaultControllerFactory根据解析出来的Controller类型以反射的形式创建对应的实例在这里得到了印证。至于 GetServices方法则返回一个空对象集合。

   1: private class DefaultDependencyResolver : IDependencyResolver
   2: {
   3:     public object GetService(Type serviceType)
   4:     {
   5:         if (serviceType.IsInterface || serviceType.IsAbstract)
   6:         {
   7:             return null;
   8:         }
   9:         try
  10:         {
  11:             return Activator.CreateInstance(serviceType);
  12:         }
  13:         catch
  14:         {
  15:             return null;
  16:         }
  17:     }
  18:
  19:     public IEnumerable<object> GetServices(Type serviceType)
  20:     {
  21:         return Enumerable.Empty<object>();
  22:     }
  23: }

上面介绍的类型DefaultControllerFactory、IControllerActivator、 DefaultControllerActivator、IDependencyResolver、DefaultDependencyResolver 和DependencyResolver之前的关系基本上可以通过如下图所示的类图来体现。

image

五、通过自定义ControllerActivator实现IoC

如果我们基于一个ControllerActivator对象来创建一个DefaultControllerFactory,它会最终被用于 Controller对象的激活,那么我们可以自定义ControllerActivator的方式将IoC引入Controller激活系统。我们接下 来自定义的ControllerActivtor基于另一个IoC框架Ninject,较之Unity,Ninject是一个更加轻量级也更适合 ASP.NET MVC的IoC框架。我们将自定义的ControllerActivator起名为NinjectControllerActivator,全部定义如 下。[源代码从这里下载]

   1: public class NinjectControllerActivator: IControllerActivator
   2: {
   3:     public IKernel Kernel { get; private set; }
   4:     public NinjectControllerActivator()
   5:     {
   6:         this.Kernel = new StandardKernel();
   7:         AddBindings();
   8:     }
   9:     public IController Create(RequestContext requestContext, Type controllerType)
  10:     {
  11:         return (IController)this.Kernel.TryGet(controllerType) as IController;
  12:     }
  13:     private void AddBindings()
  14:     {
  15:         this.Kernel.Bind<IEmployeeRepository>().To<EmployeeRepository>();
  16:     }
  17: }

我们使用的还是上面演示的关于员工管理的例子。NinjectControllerActivator的只读属性Kernel在这里用于类型注册和 基于类型的实例提供,具体来说它是在构造函数中初始化的StandardKernel对象。同样在构造函数中,我们通过该Kernel实现了作为 Model接口的IEmployeeRepository类型和Model实现的EmployeeRepository类型之间的映射。在Create方 法中,我们通过Kernel的TryGet方法根据指定的类型获取相应的Controller对象。

现在我们无须再使用自定义的ControllerFactory,只需要注册一个基于我们自定义的 NinjectControllerActivator的DefaultControllerFactory即可。定义在Global.asax中与 ControllerFactory注册相关的代码如下所示。

   1: public class MvcApplication : System.Web.HttpApplication
   2: {
   3:     //其他成员
   4:     protected void Application_Start()
   5:     {
   6:         //其他操作
   7:         NinjectControllerActivator controllerActivator =  new NinjectControllerActivator();
   8:         DefaultControllerFactory controllerFactory  = new DefaultControllerFactory(controllerActivator);
   9:         ControllerBuilder.Current.SetControllerFactory(controllerFactory);
  10:     }
  11: }

六、通过自定义DependencyResoolver实现IoC

通过前面的介绍我们知道,当我们调用构造函数创建一个DefaultControllerFactory的时候,如果调用的时候默认无参构造函数, 后者将作为参数的ControllerActivator对象设置为Null,那么默认请求用于激活Controller实例的是通过 DependencyResoolver类型的静态属性Current表示的DependencyResoolver对象。换言之,我们可以通过自定义 DependencyResoolver的方式来实现基于IoC的Controller激活。

同样是采用Ninject,我们定义了一个具有如下定义的NinjectDependencyResolver。与上面定义的 NinjectControllerActivator类似,NinjectDependencyResolver具有一个IKernel类型的只读属性 Kernel,该属性在构造函数中被初始化。同样是在构造函数中,我们通过该Kernel完成了IEmployeeRepository接口和 EmployeeRepository类型的注册。对于实现的GetService和GetServices方法,我们直接调用Kernel的 TryGet和GetAll返回指定类型的实例和实例列表。[源代码从这里下载]

   1: public class NinjectDependencyResolver : IDependencyResolver
   2: {
   3:     public IKernel Kernel { get; private set; }
   4:     public NinjectDependencyResolver()
   5:     {
   6:         this.Kernel = new StandardKernel();
   7:         AddBindings();
   8:     }
   9:     private void AddBindings()
  10:     {
  11:         this.Kernel.Bind<IEmployeeRepository>().To<EmployeeRepository>();
  12:     }
  13:
  14:     public object GetService(Type serviceType)
  15:     {
  16:         return this.Kernel.TryGet(serviceType);
  17:     }
  18:
  19:     public IEnumerable<object> GetServices(Type serviceType)
  20:     {
  21:         return this.Kernel.GetAll(serviceType);
  22:     }
  23: }

由于默认情况下通过无参构造函数创建的DefaultConrtollerFactory会被使用,所以我们无须进行 ControllerFactory的注册。我们只需要创建一个自定义的NinjectDependencyResolver对象并将其作为当前的 DependencyResolver即可,定义在Global.asax设置当前DependencyResolver的代码如下所示。

   1: public class MvcApplication : System.Web.HttpApplication
   2: {
   3:     //其他成员
   4:     protected void Application_Start()
   5:     {
   6:         //其他操作
   7:         DependencyResolver.SetResolver( new NinjectDependencyResolver());
   8:     }
   9: }
ASP.NET MVC Controller激活系统详解:总体设计
ASP.NET MVC Controller激活系统详解:默认实现
ASP.NET MVC Controller激活系统详解:IoC的应用[上篇]
ASP.NET MVC Controller激活系统详解:IoC的应用[下篇]

[转载]ASP.NET MVC Controller激活系统详解:IoC的应用[上篇] - Artech - 博客园

mikel阅读(1005)

来源: [转载]ASP.NET MVC Controller激活系统详解:IoC的应用[上篇] – Artech – 博客园

所谓控制反转(IoC: Inversion Of Control)简单地说就是应用本身不负责依赖对象的创建和维护,而交给一个外部容器来负责。这样控制权就由应用转移到了外部IoC容器,控制权就实现 了所谓的反转。比如在类型A中需要使用类型B的实例,而B实例的创建并不由A来负责,而是通过外部容器来创建。通过IoC的方式是实现针对目标 Controller的激活具有重要的意义。

目录
一、从Unity来认识IoC
二、Controller与Model的分离
三、 创建基于IoC的自定义ControllerFactory
实例演示:自定义一个基于Unity的ControllerFactory
四、ControllerActivator V.S. DependencyResoolver
五、通过自定义ControllerActivator实现IoC
六、通过自定义DependencyResoolver实现IoC

一、从Unity来认识IoC

有 时我们又将IoC称为依赖注入(DI: Dependency Injection)。所谓依赖注入,就是由外部容器在运行时动态地将依赖的对象注入到组件之中。Martin Fowler在那篇著名的文章《Inversion of Control Containers and the Dependency Injection pattern》中将具体依赖注入划分为三种形式,即构造器注入、属性(设置)注入和接口注入,而我个人习惯将其划分为一种(类型)匹配和三种注入:

  • 类型匹配(Type Matching):虽然我们通过接口(或者抽象类)来进行服务调用,但是服务本身还是实现在某个具体的服务类型中,这就需要某个类型注册机制来解决服务接口和服务类型之间的匹配关系;
  • 构造器注入(Constructor Injection):IoC容器会智能地选择选择和调用适合的构造函数以创建依赖的对象。如果被选择的构造函数具有相应的参数,IoC容器在调用构造函数之前解析注册的依赖关系并自行获得相应参数对象;
  • 属性注入(Property Injection):如果需要使用到被依赖对象的某个属性,在被依赖对象被创建之后,IoC容器会自动初始化该属性;
  • 方法注入(Method Injection):如果被依赖对象需要调用某个方法进行相应的初始化,在该对象创建之后,IoC容器会自动调用该方法。

开 源社区具有很有流行的IoC框架,比如Castle Windsor、Unity、Spring.NET、StructureMap和Ninject等。Unity是微软Patterns & Practices部门开发的一个轻量级的IoC框架。该项目在Codeplex上的地址为http://unity.codeplex.com/, 你可以下载相应的安装包和开发文档。Unity的最新版本为2.1。出于篇幅的限制,我不可能对Unity进行前面的介绍,但是为了让读者了解IoC在 Unity中的实现,我写了一个简单的程序。

我们创建一个控制台程序,定义如下几个接口(IA、IB、IC和ID)和它们各自的实现类 (A、B、C、D)。在类型A中定义了3个属性B、C和D,其类型分别为接口IB、IC和ID。其中属性B在构在函数中被初始化,以为着它会以构造器注入 的方式被初始化;属性C上应用了DependencyAttribute特性,意味着这是一个需要以属性注入方式被初始化的依赖属性;属性D则通过方法Initialize初始化,该方法上应用了特性InjectionMethodAttribute,意味着这是一个注入方法在A对象被IoC容器创建的时候会被自动调用。

   1: namespace UnityDemo
   2: {
   3:     public interface IA { }
   4:     public interface IB { }
   5:     public interface IC { }
   6:     public interface ID {}
   7:
   8:     public class A : IA
   9:     {
  10:         public IB B { get; set; }
  11:         [Dependency]
  12:         public IC C { get; set; }
  13:         public ID D { get; set; }
  14:
  15:         public A(IB b)
  16:         {
  17:             this.B = b;
  18:         }
  19:         [InjectionMethod]
  20:         public void Initialize(ID d)
  21:         {
  22:             this.D = d;
  23:         }
  24:     }
  25:     public class B: IB{}
  26:     public class C: IC{}
  27:     public class D: ID{}
  28: }

然后我们为该应用添加一个配置文件,并定义如下一段关于Unity的配置。这段配置定义了一个名称为defaultContainer的Unity容器,并在其中完成了上面定义的接口和对应实现类之间映射的类型匹配。

   1: <configuration>
   2:   <configSections>
   3:     <section name="unity"
   4:          type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,
   5:                Microsoft.Practices.Unity.Configuration"/>
   6:   </configSections>
   7:   <unity>
   8:     <containers>
   9:       <container name="defaultContainer">
  10:         <register type="UnityDemo.IA, UnityDemo" mapTo="UnityDemo.A, UnityDemo"/>
  11:         <register type="UnityDemo.IB, UnityDemo" mapTo="UnityDemo.B, UnityDemo"/>
  12:         <register type="UnityDemo.IC, UnityDemo" mapTo="UnityDemo.C, UnityDemo"/>
  13:         <register type="UnityDemo.ID, UnityDemo" mapTo="UnityDemo.D, UnityDemo"/>
  14:       </container>
  15:     </containers>
  16:   </unity>
  17: </configuration>

最后在Main方法中创建一个代表IoC容器的UnityContainer对象,并加载配置信息对其进行初始化。然后调用它的泛型的Resolve方法创建一个实现了泛型接口IA的对象。最后将返回对象转变成类型A,并检验其B、C和D属性是否是空。

   1: static void Main(string[] args)
   2: {
   3:     IUnityContainer container = new UnityContainer();
   4:     UnityConfigurationSection configuration = ConfigurationManager.GetSection(UnityConfigurationSection.SectionName)
   5:         as UnityConfigurationSection;
   6:     configuration.Configure(container, "defaultContainer");
   7:     A a = container.Resolve<IA>() as A;
   8:     if (null != a)
   9:     {
  10:         Console.WriteLine("a.B == null ? {0}", a.B == null ? "Yes" : "No");
  11:         Console.WriteLine("a.C == null ? {0}", a.C == null ? "Yes" : "No");
  12:         Console.WriteLine("a.D == null ? {0}", a.D == null ? "Yes" : "No");
  13:     }
  14: }

从如下给出的执行结果我们可以得到这样的结论:通过Resolve<IA>方法返回的是一个类型为A的对象,该对象的三个属性被进行了 有效的初始化。这个简单的程序分别体现了接口注入(通过相应的接口根据配置解析出相应的实现类型)、构造器注入(属性B)、属性注入(属性C)和方法注入 (属性D)。[源代码从这里下载]

   1: a.B == null ? No
   2: a.C == null ? No
   3: a.D == null ? No

二、Controller与Model的分离

在《MVC、MVP以及Model2[下篇]》中我们谈到ASP.NET MVC是基于MVC的变体Model2设计的。ASP.NET MVC所谓的Model仅仅表示绑定到View上的数据,我们一般称之为View Model。而真正的Model一般意义上指维护应用状态和提供业务功能操作的领域模型,或者是针对业务层的入口或者业务服务的代理。真正的MVC在ASP.NET MVC中的体现如下图所示。

image

对于一个ASP.NET MVC应用来说,用户交互请求直接发送给Controller。如果涉及到针对某个个业务功能的调用,Controller会直接调用Model;如果呈 现业务数据,Controller会通过Model获取相应业务数据并转换成View Model,最终通过View呈现出来。这样的交互协议方式反映了Controller针对Model的直接依赖。

如果我们在Controller激活系统中引入IoC,并采用IoC的方式提供用于处理请求的Controller对象,那么Controller 和Model之间的依赖程度在很大程度上降低。我们甚至可以像下图所示的一样,以接口的方式都Model进行抽象,让Controller依赖于这个抽象 化的Model接口,而不是具体的Model实现。

image

三、 创建基于IoC的自定义ControllerFactory

ASP.NET MVC的Controller激活系统最终通过ControllerFactory来创建目标Controller对象,要将IoC引入ASP.NET MVC并通过对应的IoC容器实现对目标Controller的激活,我们很自然地会想到自定义一个基于IoC的ControllerFactory。

对于IoC的ControllerFactory的创建,我们可以直接实现IControllerFactory接口创建一个全新的 ControllerFactory类型,这需要实现包括Controller类型的解析、Controller实例的创建与释放以及会话状态行为选项的 获取在内的所有功能。一般来说,Controller实例的创建与释放才收IoC容器的控制,为了避免重新实现其他的功能,我们可以直接继承 DefaultControllerFactory,重写Controller实例创建于释放的逻辑。

实例演示:自定义一个基于Unity的ControllerFactory

现在我们通过一个简单的实例演示如何通过自定义ControllerFactory利用Unity进行Controller的激活与释放。为了避免 针对Controller类型解析和会话状态行为选项的获取逻辑的重复定义,我们直接继承DefaultControllerFactory。我们将该自 定义ControllerFactory命名为UnityControllerFactory,整个定义如下面的的代码片断所示。[源代码从这里下载]

   1: public class UnityControllerFactory : DefaultControllerFactory
   2: {
   3:     static object syncHelper = new object();
   4:   static Dictionary<string, IUnityContainer> containers = new Dictionary<string,IUnityContainer>();
   5:     public IUnityContainer UnityContainer { get; private set; }
   6:     public UnityControllerFactory(string containerName = "")
   7:     {
   8:         if (containers.ContainsKey(containerName))
   9:         {
  10:             this.UnityContainer = containers[containerName];
  11:             return;
  12:         }
  13:         lock (syncHelper)
  14:         {
  15:             if (containers.ContainsKey(containerName))
  16:             {
  17:                 this.UnityContainer = containers[containerName];
  18:                 return;
  19:             }
  20:             IUnityContainer container = new UnityContainer();
  21:             //配置UnityContainer
  22:             UnityConfigurationSection configSection = ConfigurationManager.GetSection(UnityConfigurationSection.SectionName)
  23:                 as UnityConfigurationSection;
  24:             if (null == configSection && !string.IsNullOrEmpty(containerName))
  25:             {
  26:                 throw new ConfigurationErrorsException("The <unity> configuration section does not exist.");
  27:             }
  28:             if (null != configSection )
  29:             {
  30:                 if(string.IsNullOrEmpty(containerName))
  31:                 {
  32:                     configSection.Configure(container);
  33:                 }
  34:                 else
  35:                 {
  36:                     configSection.Configure(container, containerName);
  37:                 }
  38:             }
  39:             containers.Add(containerName, container);
  40:             this.UnityContainer = containers[containerName];
  41:         }
  42:     }
  43:   protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
  44:     {
  45:         if (null == controllerType)
  46:         {
  47:             return null;
  48:         }
  49:         return (IController)this.UnityContainer.Resolve(controllerType);
  50:     }
  51:     public override void ReleaseController(IController controller)
  52:     {
  53:         this.UnityContainer.Teardown(controller);
  54:     }
  55: }

UnityControllerFactory的UnityConainer属性表示的实现自 Microsoft.Practices.Unity.IUnityContainer接口的对象表示定义在Unity中的IoC容器。为了避免 UnityConainer对象的频繁创建,我们创建的UnityConainer对象保存在一个通过静态字段(containers)表示的字典对象 中,其Key为UnityConainer的配置名称。构造函数中的参数containnerName表示使用的UnityConainer的配置名称, 如果静态字典中存在着与之匹配的UnityConainer对象,则直接获取出来作为UnityConainer属性的值;否则创建一个新的 UnityConainer对象并加载对应的配置对其进行相关设置,最后将其赋值给UnityConainer属性并添加到静态字典之中。

我们重写了定义在基类DefaultControllerFactory的虚方法GetControllerInstance,在解析出来的 Controller类型(controllerType参数)不为Null的情况下,直接调用UnityConainer的Resolve方法激活对应 的Controller实例。在用于释放Controller对象的ReleaseController方法中,我们直接将Controller对象作为 参数调用UnityConainer的Teardown方法。

整个自定义的UnityControllerFactory就这么简单,为了演示IoC在它身上的体现,我们在一个简单的ASP.MVC实例中来使用我们刚刚定义的UnityControllerFactory。我们沿用在《ASP.NET的路由系统:URL与物理文件的分离》中使用过的关于“员工管理”的场景,如下图所示,本实例由两个页面(对应着两个View)组成,一个用于显示员工列表,另一个用于显示基于某个员工的详细信息。

image

我们通过Visual Studio的ASP.NET MVC项目模板创建一个空的Web应用,并添加针对Unity的两个程序集(Microsoft.Practices.Unity.dll和 Microsoft.Practices.Unity.Configuration.dll)引用。然后我们再Models目录下定义如下一个表示员工信 息的Employee类型。

   1: public class Employee
   2: {
   3:     [Display(Name="ID")]
   4:     public string Id { get; private set; }
   5:     [Display(Name = "姓名")]
   6:     public string Name { get; private set; }
   7:     [Display(Name = "性别")]
   8:     public string Gender { get; private set; }
   9:     [Display(Name = "出生日期")]
  10:     [DataType(DataType.Date)]
  11:     public DateTime BirthDate { get; private set; }
  12:     [Display(Name = "部门")]
  13:     public string Department { get; private set; }
  14:
  15:     public Employee(string id, string name, string gender, DateTime birthDate, string department)
  16:     {
  17:         this.Id = id;
  18:         this.Name = name;
  19:         this.Gender = gender;
  20:         this.BirthDate = birthDate;
  21:         this.Department = department;
  22:     }
  23: }

我们创建一个独立的组件来模拟用于维护应用状态提供业务操作功能的Model(在这里我们将ASP.NET MVC中的Model视为View Model),为了降低Controller和Model之间耦合度,我们为这个Model定义了接口。如下所示的 IEmployeeRepository就代表了这个接口,唯一的方法GetEmployees用于获取所有员工列表(id参数值为空)或者基于指定ID 的某个员工信息。

   1: public interface IEmployeeRepository
   2: {
   3:     IEnumerable<Employee> GetEmployees(string id = "");
   4: }

EmployeeRepository类型实现了IEmployeeRepository接口,具体的定义如下所示。简单起见,我们直接通过一个类型为List<Employee>得静态字段来表示所有员工信息的存储。

   1: public class EmployeeRepository: IEmployeeRepository
   2: {
   3:     private static IList<Employee> employees;
   4:     static EmployeeRepository()
   5:     {
   6:         employees = new List<Employee>();
   7:         employees.Add(new Employee(Guid.NewGuid().ToString(), "张三", "男", new DateTime(1981, 8, 24), "销售部"));
   8:         employees.Add(new Employee(Guid.NewGuid().ToString(), "李四", "女", new DateTime(1982, 7, 10), "人事部"));
   9:         employees.Add(new Employee(Guid.NewGuid().ToString(), "王五", "男", new DateTime(1981, 9, 21), "人事部"));
  10:     }
  11:     public IEnumerable<Employee> GetEmployees(string id = "")
  12:     {
  13:         return employees.Where(e => e.Id == id || string.IsNullOrEmpty(id));
  14:     }
  15: }

现在我们来创建我们的Controller,在这里我们将其起名为EmployeeController。如下面的代码片断所示,EmployeeController具有一个类型为IEmployeeRepository的属性Repository,应用在上面的DependencyAttribute特性我们知道这是一个“依赖属性”,如果采用UnityContainer来激活EmployeeController对象的时候,会根据注册的类型映射来实例化一个实现了IEmployeeRepository的类型的实例来初始化该属性。

   1: public class EmployeeController : Controller
   2: {
   3:     [Dependency]
   4:     public IEmployeeRepository Repository { get; set; }
   5:     public ActionResult Index()
   6:     {
   7:         var employees = this.Repository.GetEmployees();
   8:         return View(employees);
   9:     }
  10:     public ActionResult Detail(string id)
  11:     {
  12:         Employee employee = this.Repository.GetEmployees(id).FirstOrDefault();
  13:         if (null == employee)
  14:         {
  15:             throw new HttpException(404, string.Format("ID为{0}的员工不存在", id));
  16:         }
  17:         return View(employee);
  18:     }
  19: }

默认的Index操作方法中,我们通过Repository属性获取表示所有员工的列表,并将其作为Model显现在对应的View中。至于用于显 示指定员工ID详细信息的Detail操作,我们同样通过Repository属性根据指定的ID获取表示相应员工信息的Employee对象,如果该对 象为Null,直接返回一个状态为404的HttpException;否则作为将其作为Model显示在相应的View中。

如下所示的名为Index的View的定义,它的Model类型为IEnumerable<Employee>,在这里View中,我 们通过一个表格来显示表示为Model的员工列表。值得一提的是,我们通过调用HtmlHelper的ActionLink方法将员工的名称显示为一个执 行Detail操作的连接,作为路由变量参数集合中同时包含当前员工的ID和姓名。根据我们即将注册的路由规则,这个链接地址的格式为/Employee /Detail/{Name}/{Id}。

   1: @model IEnumerable<Employee>
   2: @{
   3:     ViewBag.Title = "Index";
   4: }
   5: <table id="employees" rules="all" border="1">
   6:     <tr>
   7:         <th>姓名</th>
   8:         <th>性别</th>
   9:         <th>出生日期</th>
  10:         <th>部门</th>
  11:     </tr>
  12:     @{
  13:         foreach(Employee employee in Model)
  14:         {
  15:             <tr>
  16:                 <td>@Html.ActionLink(employee.Name, "Detail",
  17:                     new { name = employee.Name, id = employee.Id })</td>
  18:                 <td>@employee.Gender</td>
  19:                 <td>@employee.BirthDate.ToString("dd/MM/yyyy")</td>
  20:                 <td>@employee.Department</td>
  21:             </tr>
  22:         }
  23:     }
  24: </table>

用于显示具有某个员工信息的名为Detail的View定义如下,这是一个Model类型为Employee的强类型的View,我们通过通过表格的形式将员工的详细信息显示出来。

   1: @model Employee
   2: @{
   3:     ViewBag.Title = Model.Name;
   4: }
   5: <table id="employee" rules="all" border="1">
   6:     <tr><td>@Html.LabelFor(m=>m.Id)</td><td>@Model.Id</td></tr>
   7:     <tr><td>@Html.LabelFor(m=>m.Name)</td><td>@Model.Name</td></tr>
   8:     <tr><td>@Html.LabelFor(m=>m.Gender)</td><td>@Model.Gender</td></tr>
   9:     <tr><td>@Html.LabelFor(m=>m.BirthDate)</td><td>@Model.BirthDate.ToString("dd/MM/yyyy")</td></tr>
  10:     <tr><td>@Html.LabelFor(m=>m.Department)</td><td>@Model.Department</td></tr>
  11: </table>

我需要在Global.asax中完成两件事情,即针对自定义UnityControllerFactory和路由的注册。如下的代码片断所示,在 Application_Start方法中我们通过当前ControllerBuilder注册了一个UnityControllerFactory实 例。 在RegisterRoutes方法中我们注册两个路由,前者针对Detail操作(URL模版包含员工的ID和姓名),后者针对Index操作。

   1: public class MvcApplication : System.Web.HttpApplication
   2: {
   3:     //其他成员
   4:     public static void RegisterRoutes(RouteCollection routes)
   5:     {
   6:         routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
   7:         routes.MapRoute(
   8:             name: "Detail",
   9:             url: "{controller}/{action}/{name}/{id}",
  10:             defaults: new { controller = "Employee" }
  11:         );
  12:         routes.MapRoute(
  13:             name: "Default",
  14:             url: "{controller}/{action}",
  15:             defaults: new { controller = "Employee", action = "Index"}
  16:         );
  17:     }
  18:
  19:     protected void Application_Start()
  20:     {
  21:         //其他操作
  22:         RegisterRoutes(RouteTable.Routes);
  23:         ControllerBuilder.Current.SetControllerFactory(new UnityControllerFactory());
  24:     }
  25: }

EmployeeController仅仅依赖于IEmployeeRepository接口,它通过基于该类型的依赖属性Repository返 回员工信息,我们需要通过注册为之设置一个具体的匹配类型,而这个类型自然就是前面我们定义的EmployeeRepository。如下所示的正是 Unity相关的类型注册配置。到此为止,整个实例的编程和配置工作既已完成(忽略了针对样式的设置),运行该程序就可以得到如上图所示的效果。

   1: <configuration>
   2:   <configSections>
   3:     <section name="unity"
   4:          type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,
   5:                Microsoft.Practices.Unity.Configuration"/>
   6:      </configSections>
   7:   <unity>
   8:     <containers>
   9:       <container>
  10:         <register type="Artech.Mvc.IEmployeeRepository, Artech.Mvc.MvcApp"
  11:                   mapTo="Artech.Mvc.EmployeeRepository, Artech.Mvc.MvcApp"/>
  12:       </container>
  13:     </containers>
  14:   </unity>
  15: </configuration>

 

ASP.NET MVC Controller激活系统详解:总体设计
ASP.NET MVC Controller激活系统详解:默认实现
ASP.NET MVC Controller激活系统详解:IoC的应用[上篇]
ASP.NET MVC Controller激活系统详解:IoC的应用[下篇]

[转载]ASP.NET MVC Controller激活系统详解:默认实现 - Artech - 博客园

mikel阅读(1015)

来源: [转载]ASP.NET MVC Controller激活系统详解:默认实现 – Artech – 博客园

Controller激活系统最终通过注册的ControllerFactory创建相 应的Conroller对象,如果没有对ControllerFactory类型或者类型进行显式注册(通过调用当前ControllerBuilder 的SetControllerFactory方法),默认使用的是一个DefaultControllerFactory对象,我们现在就来讨论实现在DefaultControllerFactory类型中的默认Controller激活机制。

目录
一、Controller类型的解析
实例演示:创建一个自定义ControllerFactory模拟Controller默认激活机制
二、 Controller类型的缓存
三、 Controller的释放
四、会话状态行为的控制

一、Controller类型的解析

激 活目标Controller对象的前提是能够正确解析出对应的Controller类型。对于DefaultControllerFactory来,用于 解析目标Controller类型的信息包括:通过与当前请求匹配的路由对象生成的RouteData(其中包含Controller的名称和命名空间) 和包含在当前ControllerBuilder中的命名空间。很对读者可以首先想到的是通过Controller名称得到对应的类型,并通过命名空间组 成Controller类型的全名,最后遍历所有程序集以此名称去加载相应的类型即可。

这貌似一个不错的解决方案,实际上则完全不可行。不要忘了作为请求地址URL一部分的Controller名称是不区分大小写的,而类型名称则是区分大小的;不论是注册路由时指定的命名空间还是当前ControllerBuilder的默认命名空间,有可能是包含统配符(*)。由于我们不能通过给定的Controller名称和命名空间得到Controller的真实类型名称,自然就不可能通过名称去解析Controller的类型了。

ASP.NET MVC的Controller激活系统反其道而行之。它先 遍历通过BuildManager的静态方法GetReferencedAssemblies方法得到的编译Web应用所使用的程序集,通过反射得到所有 实现了接口IController的类型,最后通过给定的Controller的名称和命名空间作为匹配条件在这个预先获取的类型列表中得到目标 Controller的类型

实例演示:创建一个自定义ControllerFactory模拟Controller默认激活机制

为 了让读者对默认采用的Controller激活机制,尤其是Controller类型的解析机制有一个深刻的认识,我们通过一个自定义的 ControllerFactory来模拟其中的实现。由于我们采用反射的方式来创建Controller对象,所以我们将该自定义 ControllerFactory起名为ReflelctionControllerFactory。[源代码从这里下载]

   1: public class ReflelctionControllerFactory : IControllerFactory
   2: {
   3:     //其他成员
   4:     private static List<Type> controllerTypes;
   5:     static ReflelctionControllerFactory()
   6:     {
   7:         controllerTypes = new List<Type>();
   8:         foreach (Assembly assembly in BuildManager.GetReferencedAssemblies())
   9:         {
  10:             controllerTypes.AddRange(assembly.GetTypes().Where(type => typeof(IController).IsAssignableFrom(type)));
  11:         }
  12:     }
  13:
  14:     public IController CreateController(RequestContext requestContext, string controllerName)
  15:     {
  16:         Type controllerType = this.GetControllerType(requestContext.RouteData, controllerName);
  17:         if (null == controllerType)
  18:         {
  19:             throw new HttpException(404, "No controller found");
  20:         }
  21:         return (IController)Activator.CreateInstance(controllerType);
  22:     }
  23:
  24:     private static bool IsNamespaceMatch(string requestedNamespace, string targetNamespace)
  25:     {
  26:         if (!requestedNamespace.EndsWith(".*", StringComparison.OrdinalIgnoreCase))
  27:         {
  28:             return string.Equals(requestedNamespace, targetNamespace, StringComparison.OrdinalIgnoreCase);
  29:         }
  30:         requestedNamespace = requestedNamespace.Substring(0, requestedNamespace.Length - ".*".Length);
  31:         if (!targetNamespace.StartsWith(requestedNamespace, StringComparison.OrdinalIgnoreCase))
  32:         {
  33:             return false;
  34:         }
  35:         return ((requestedNamespace.Length == targetNamespace.Length) || (targetNamespace[requestedNamespace.Length] == '.'));
  36:     }
  37:
  38:    private Type GetControllerType(IEnumerable<string> namespaces, Type[] controllerTypes)
  39:     {
  40:         var types = (from type in controllerTypes
  41:                         where namespaces.Any(ns => IsNamespaceMatch(ns, type.Namespace))
  42:                         select type).ToArray();
  43:         switch (types.Length)
  44:         {
  45:             case 0: return null;
  46:             case 1: return types[0];
  47:             default: throw new InvalidOperationException("Multiple types were found that match the requested controller name.");
  48:         }
  49:     }
  50:
  51:     protected virtual Type GetControllerType(RouteData routeData, string controllerName)
  52:     {
  53:         //省略实现
  54:     }
  55: }

如上面的代码片断所示,ReflelctionControllerFactory具有一个静态的controllerTypes字段由于保存所有 Controller的类型。在静态构造函数中,我们调用BuildManager的GetReferencedAssemblies方法得到所有用于编 译Web应用的程序集,并从中得到所有实现了IController接口的类型,这些类型全部被添加到通过静态字段controllerTypes表示的 类型列表。

Controller类型的解析实现在受保护的GetControllerType方法中,在用于最终激活Controller对象的 CreateController方法中,我们通过调用该方法得到与指定RequestContext和Controller名称相匹配的 Controller类型,最终通过调用Activator的静态方法CreateInstance根据该类型创建相应的Controller对象。如果 不能找到匹配的Controller类型(GetControllerType方法返回Null),则抛出一个HTTP状态为404的 HttpException。

ReflelctionControllerFactory中定义了两个辅助方法,IsNamespaceMatch用于判断Controller 类型真正的命名空间是否与指定的命名空间(可能包含统配符)相匹配,在进行字符比较过程中是忽略大小写的。私有方法GetControllerType根 据指定的命名空间列表和类型名称匹配的类型数组得到一个完全匹配的Controller类型。如果得到多个匹配的类型,直接抛出 InvalidOperation异常,并提示具有多个匹配的Controller类型;如果找不到匹配类型,则返回Null。

在如下所示的用于解析Controller类型的GetControllerType方法中,我们从预先得到的所有Controller类型列表中 筛选出类型名称与传入的Controller名称相匹配的类型。我们首先通过路由对象的命名空间对 之前 得到的类型列表进行进一步筛选,如果能够找到一个唯一的类型,则直接将其作为Controller的类型返回。为了确定是否采用后备命名空间对 Controller类型进行解析,我们从作为参数参数的RouteData对象的DataTokens中得到获取一个Key为“UseNamespaceFallback”的元素,如果该元素存在并且值为False,则直接返回Null。

如果RouteData的DataTokens中不存在这样一个UseNamespaceFallback元素,或者它的值为True,则首先里当 前ControllerBuilder的默认命名空间列表进一步对Controller类型进行解析,如果存在唯一的类型则直接当作目标 Controller类型返回。如果通过两组命名空间均不能得到一个匹配的ControllerType,并且只存在唯一一个与传入的 Controller名称相匹配的类型,则直接将该类型作为目标Controller返回。如果这样的类型具有多个,则直接抛出 InvalidOperationException异常。

   1: public class ReflelctionControllerFactory : IControllerFactory
   2: {
   3:     //其他成员
   4:     protected virtual Type GetControllerType (RouteData routeData, string controllerName)
   5:     {
   6:         //根据类型名称筛选
   7:         var types = controllerTypes.Where(type => string.Compare(controllerName + "Controller", type.Name, true) == 0).ToArray();
   8:         if (types.Length == 0)
   9:         {
  10:             return null;
  11:         }
  12:
  13:         //通过路由对象的命名空间进行匹配
  14:         var namespaces = routeData.DataTokens["Namespaces"] as IEnumerable<string>;
  15:         namespaces = namespaces ?? new string[0];
  16:         Type contrllerType = this.GetControllerType(namespaces, types);
  17:         if (null != contrllerType)
  18:         {
  19:             return contrllerType;
  20:         }
  21:
  22:         //是否允许采用后备命名空间
  23:         bool useNamespaceFallback = true;
  24:         if (null != routeData.DataTokens["UseNamespaceFallback"])
  25:         {
  26:             useNamespaceFallback = (bool)(routeData.DataTokens["UseNamespaceFallback"]);
  27:         }
  28:
  29:         //如果不允许采用后备命名空间,返回Null
  30:         if (!useNamespaceFallback)
  31:         {
  32:             return null;
  33:         }
  34:
  35:         //通过当前ControllerBuilder的默认命名空间进行匹配
  36:         contrllerType = this.GetControllerType(ControllerBuilder.Current.DefaultNamespaces, types);
  37:         if (null != contrllerType)
  38:         {
  39:             return contrllerType;
  40:         }
  41:
  42:         //如果只存在一个类型名称匹配的Controller,则返回之
  43:         if (types.Length == 1)
  44:         {
  45:             return types[0];
  46:         }
  47:
  48:         //如果具有多个类型名称匹配的Controller,则抛出异常
  49:         throw new InvalidOperationException("Multiple types were found that match the requested controller name.");
  50:     }
  51: }

二、 Controller类型的缓存

为了避免通过遍历所有程序集对目标Controller类型的解析,ASP.NET MVC对解析出来的Controller类型进行了缓存以提升性能。与针对用于Area注册的AreaRegistration类型的缓存类 似,Controller激活系统同样采用基于文件的缓存策略,而用于保存Controller类型列表的名为MVC-ControllerTypeCache.xml的 文件保存在ASP.NET的临时目录下面。具体的路径如下,其中第一个针对寄宿于IIS中的Web应用,后者针对直接通过Visual Studio Developer Server作为宿主的应用。而用于保存所有AreaRegistration类型列表的MVC- AreaRegistrationTypeCache.xml文件也保存在这个目录下面。

  • %Windir%\Microsoft.NET\Framework\v{version}\Temporary ASP.NET Files\{appname}\…\…\UserCache\
  • %Windir%\Microsoft.NET\Framework\v{version}\Temporary ASP.NET Files\root\…\…\UserCache\

对针对Web应用被启动后的第一个请求时,Controller激活系统会读取这个用于缓存所有Controller类型列表的 ControllerTypeCache.xml文件并反序列化成一个List<Type>对象。只有在该列表为空的时候才会通过遍历程序集 和反射的方式得到所有实现了接口IController的公有类型,而被解析出来的Controller类型重写被写入 ControllerTypeCache.xml文件中。这个通过读取缓存文件或者重新解析出来的Controller类型列表被保存到内容中,在Web 应用活动期间内被Controller激活系统所用。

下面的XML片断反映了这个用于Controller类型列表缓存的ControllerTypeCache.xml文件的结构,我们可以看出它包含了所有的Controller类型的全名和所在的程序集和托管模块信息。

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <!--This file is automatically generated. Please do not modify the contents of this file.-->
   3: <typeCache lastModified="3/22/2012 1:18:49 PM" mvcVersionId="80365b23-7a1d-42b2-9e7d-cc6f5694c6d1">
   4:   <assembly name="Artech.Admin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
   5:     <module versionId="eb343e3f-2d63-4665-a12a-29fb30dceeed">
   6:       <type> Artech.Admin .HomeController</type>
   7:       <type> Artech.Admin .EmployeeController </type>
   8:     </module>
   9:   </assembly>
  10:   <assembly name="Artech.Portal, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
  11:     <module versionId=" 3717F116-35EE-425F-A1AE-EB4267497D8C ">
  12:       <type>Artech. Portal.Controllers.HomeController</type>
  13:       <type>Artech. Portal.ProductsController</type>
  14:     </module>
  15:   </assembly>
  16: </typeCache>

三、 Controller的释放

作为激活Controller对象的ControllerFactory不仅仅用于创建目标Controller对象,还具有两个额外的功能,即通 过ReleaseController方法对激活的Controller对象进行释放和回收,以及通过 GetControllerSessionBehavior返回用于控制当前会话状态行为的SessionStateBehavior枚举。

对于默认使用DefaultControllerFactory来说,针对Controller对象的释放操作很简单:如果Controller类型实现了IDisposable接口,则直接调用其Dispose方法即可;否则直接忽略。我们将这个逻辑也实现在了我们自定义的ReflelctionControllerFactory中。

   1: public class ReflelctionControllerFactory : IControllerFactory
   2: {
   3:     //其他操作
   4:     public void ReleaseController(IController controller)
   5:     {
   6:         IDisposable disposable = controller as IDisposable;
   7:         if (null != disposable)
   8:         {
   9:             disposable.Dispose();
  10:         }
  11:     }
  12: }

四、会话状态行为的控制

至于用于返回SessionStateBehavior枚举的GetControllerSessionBehavior方法来说,在默认的情况下 的返回值为SessionStateBehavior.Default。通过前面的介绍我们知道在这种情况下具体的会话状态行为取决于创建的 HttpHandler所实现的标记接口。对于ASP.NET MVC应用来说,默认用于处理请求的HttpHandler是一个叫做MvcHandler的对象,如下面的代码片断所示,HttpHandler实现了IRequiresSessionState接口,意味着默认情况下会话状态是可读写的(相当于SessionStateBehavior.Requried)。

   1: public class MvcHandler :
   2: IHttpAsyncHandler,
   3: IHttpHandler,
   4: IRequiresSessionState
   5: {
   6:     //其他成员
   7: }

不过我们可以通过在Controller类型上应用SessionStateAttribute特性来具体控制会话状态行为。如下面的代码片断所示,SessionStateAttribute具有一个SessionStateBehavior类型的只读属性Behavior用于返回具体行为设置的会话状态行为选项,该属性是在构造函数中被初始化的。

   1: [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
   2: public sealed class SessionStateAttribute : Attribute
   3: {
   4:     public SessionStateAttribute(SessionStateBehavior behavior);
   5:     public SessionStateBehavior Behavior { get; }
   6: }

也就是说DefaultControllerFactory会通过解析出来的Controller类型得到应用在上面的SessionStateAttribute特性,如果这样的特性存在则直接返回它的Behavior属性所表示的SessionStateBehavior枚举;如果不存在则返回SessionStateBehavior.Default,具体的逻辑反映在我们自定义的ReflelctionControllerFactory的GetControllerSessionBehavior方法中。

   1: public class ReflelctionControllerFactory : IControllerFactory
   2: {
   3:     //其他成员
   4:     public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName)
   5:     {
   6:         Type controllerType = this.GetControllerType(requestContext.RouteData, controllerName);
   7:         if (null == controllerType)
   8:         {
   9:             return SessionStateBehavior.Default;
  10:         }
  11:         SessionStateAttribute attribute = controllerType.GetCustomAttributes(true).OfType<SessionStateAttribute>()
  12:            .FirstOrDefault();
  13:         attribute = attribute ?? new SessionStateAttribute(SessionStateBehavior.Default);
  14:         return attribute.Behavior;
  15:     }
  16: }
ASP.NET MVC Controller激活系统详解:总体设计
ASP.NET MVC Controller激活系统详解:默认实现
ASP.NET MVC Controller激活系统详解:IoC的应用[上篇]
ASP.NET MVC Controller激活系统详解:IoC的应用[下篇]

ASP.NET MVC Controller激活系统详解:总体设计

mikel阅读(1037)

我们将整个ASP.NET MVC框架划分为若干个子系统,那么针对请求上下文激活目标Controller对象的子系统被我们成为Controller激活系统。在正式讨论 Controller对象具体是如何被创建爱之前,我们先来看看Controller激活系统在ASP.NET MVC中的总体设计,了解一下组成该子系统的一些基本的组件,以及它们对应的接口或者抽象类是什么。

目录
一、Controller
二、 ControllerFactory
三、ControllerBuilder
实例演示:如何提升命名空间的优先级
针对Area的路由对象的命名空间
四、 Controller的激活与URL路由

一、Controller

我们知道作为Controller的类型直接或者间接实现了IController接 口。如下面的代码片断所示,IController接口仅仅包含一个参数类型为RequestContext的Execute方法。当一个 Controller对象被激活之后,核心的操作就是根据请求上下文解析出目标Action方法,并通过Model绑定机制从请求上下文中提取相应的数据 映射为方法的参数并最终执行Action方法。所有的这些操作都是调用这个Execute方法来执行的。

   1: public interface IController
   2: {
   3:     void Execute(RequestContext requestContext);
   4: }

定义在IController接口中的Execute是以同步的方式执行的。为了支持以异步方式对请求的处理,IController接口的异步版 本System.Web.Mvc.IAsyncController被定义出来。如下面的代码片断所示,实现了IAsyncController接口的异 步Controller的执行通过BeginExecute/EndExecute方法组合来完成。

   1: public interface IAsyncController : IController
   2: {
   3:     IAsyncResult BeginExecute(RequestContext requestContext, AsyncCallback callback, object state);
   4:     void EndExecute(IAsyncResult asyncResult);
   5: }

抽象类ControllerBase实现了IController接口,它具有如下几个重要的属性。TemplateData、ViewBag和 ViewData用于存储从Controller向View传递的数据或者变量。其中TemplateData和ViewData具有基于字典的数据结 构,Key和Value分别表示变量的名称和值,所不同的前者用于存储基于当前HTTP上下文的变量(在完成当前请求后,存储的数据会被回收)。 ViewBag和ViewData具有相同的作用,甚至对应着相同的数据存储,它们之间的不同之处在于前者是一个动态对象,我们可以为其指定任意属性。

   1: public abstract class ControllerBase : IController
   2: {
   3:     //其他成员
   4:     public ControllerContext ControllerContext { get; set; }
   5:     public TempDataDictionary TempData { get; set; }
   6:     public object ViewBag { [return: Dynamic] get; }
   7:     public ViewDataDictionary ViewData { get; set; }
   8: }

ASP.NET MVC中我们会陆续遇到一系列的上下文(Context)对象,之前我们已经对表示请求上下文的RequestContext(HttpContext + RouteData)进行了详细的介绍,现在我们来介绍另一个具有如下定义的上下文类型ControllerContext

   1: public class ControllerContext
   2: {
   3:     //其他成员
   4:     public ControllerContext();
   5:     public ControllerContext(RequestContext requestContext, ControllerBase controller);
   6:     public ControllerContext(HttpContextBase httpContext,
   7:     RouteData routeData, ControllerBase controller);
   8:
   9:     public virtual ControllerBase Controller { get; set; }
  10:     public RequestContext RequestContext { get; set; }
  11:     public virtual HttpContextBase HttpContext { get; set; }
  12:     public virtual RouteData RouteData { get; set; }
  13: }

顾名思义,ControllerContext就是基于某个Controller对象的上下文。从如下的代码所 示,ControllerContext是实际上是对一个Controller对象和RequestContext的封装,这两个对象分别对应着定义在 ControllerContext中的同名属性,并且可以在构造函数中被初始化。而通过属性HttpContext和RouteData属性返回的 HttpContextBase和RouteData对象在默认情况下实际上就是组成RequestContext的核心元素。 ControllerContext的这四个属性都是可读可写的,我们对其进行任意地修改。当ControllerBase的Execute方法被执行的 时候,它会根据传入的ReuqestContext创建ControllerContext对象,而后续的操作可以看成是在该上下文中进行。

当我们在进行开发的时候,通过VS默认创建的Controller类型实际上继承自抽象类Controller。 该类型中定义了很多的辅助方法和属性以编程变得简单。如下面的代码片断所示,除了直接继承ControllerBase之外,Controller类型还 显式实现了IController和IAsyncController接口,以及代表ASP.NET MVC 四大筛选器(AuthorizationFilter、ActionFilter、ResultFilter和ExceptionFilter)的4个接 口。

   1: public abstract class Controller :
   2:     ControllerBase,
   3:     IController,
   4:     IAsyncController,
   5:     IActionFilter,
   6:     IAuthorizationFilter,
   7:     IExceptionFilter,
   8:     IResultFilter,
   9:     IDisposable,
  10:     ...
  11: {
  12:    //省略成员
  13: }

 

二、 ControllerFactory

ASP.NET MVC为Controller的激活定义相应的相应的工厂,我们将其统称为ControllerFactory,所有的ControllerFactory实现了接口IControllerFactory接 口。如下面的代码片断所示,Controller对象的激活最终最终通过IControllerFactory的CreateController方法来 完成,该方法的两个参数分别表示当前请求上下文和从路由信息中获取的Controller的名称(最初来源于请求地址)。

   1: public interface IControllerFactory
   2: {
   3:     IController CreateController(RequestContext requestContext, string controllerName);
   4:     SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName);
   5:     void ReleaseController(IController controller);
   6: }
   7: public enum SessionStateBehavior
   8: {
   9:     Default,
  10:     Required,
  11:     ReadOnly,
  12:     Disabled
  13: }

处理负责创建Controller处理请求之前,ControllerFactory还需要在完成请求处理之后实施对Controller的释放回 收,后者实现在ReleaseController方法中。IControllerFactory的另一个方法 GetControllerSessionBehavior方法返回一个SessionStateBehavior枚举。熟悉ASP.NET的读者应该对SessionStateBehavior不会感到陌生,它用于表示请求处理过程中会话状态支持的模式,它的四个枚举值分别具有如下的含义:

  • Default:使用默认 ASP.NET 逻辑来确定请求的会话状态行为。
  • Required:为请求启用完全的读写会话状态行为。
  • ReadOnly:为请求启用只读会话状态。
  • Disabled:禁用会话状态。

对于Default选项来说,ASP.NET通过映射的HttpHandler类型是否实现了相关接口来决定具体的会话状态控制行为。在 System.Web.SessionState命名空间下定义了IRequiresSessionState和 IRequiresSessionState接口,如下面的代码片断所示,这两个都是不具有任何成员的空接口(我们一般称之为标记接口),而 IReadOnlySessionState继承自IRequiresSessionState。如果HttpHandler实现了接口 IReadOnlySessionState,则意味着采用ReadOnly模式,如果只实现了IRequiresSessionState则采用 Required模式。

   1: public interface IRequiresSessionState
   2: {}
   3: public interface IReadOnlySessionState : IRequiresSessionState
   4: {}

具体采用何种会话状态行为取决于当前HTTP上下文(HttpContext.Current)。对于之前的版本,我们不能对当前HTTP上下文的 会话状态行为模式进行动态的修改,ASP.NET 4.0为HttpContext定义了如下一个SetSessionStateBehavior方法是我们可以自由地选择会话状态行为模式。相同的方法同 样定义在HttpContextBase中,它的子类HttpContextWrapper重写了这个方法并在内部会调用封装的HttpContext的 同名方法。

   1: public sealed class HttpContext : IServiceProvider, IPrincipalContainer
   2: {
   3:     //其他成员
   4: public void SetSessionStateBehavior(
   5:     SessionStateBehavior sessionStateBehavior);
   6: }
   7: public class HttpContextBase: IServiceProvider
   8: {
   9:     //其他成员
  10:     public void SetSessionStateBehavior(SessionStateBehavior sessionStateBehavior);
  11: }

 

三、ControllerBuilder

用于激活Controller对象的ControllerFactory最终通过ControllerBuilder注 册到ASP.NET MVC应用中。如下面的代码所示,ControllerBuilder定义了一个静态只读属性Current返回当前ControllerBuilder 对象,这是针对整个Web应用的全局对象。两个SetControllerFactory方法重载用于注册ControllerFactory的类型或者 实例,而GetControllerFactory方法返回一个具体的ControllerFactory对象。

   1: public class ControllerBuilder
   2: {
   3:     public IControllerFactory GetControllerFactory();
   4:     public void SetControllerFactory(Type controllerFactoryType);
   5:     public void SetControllerFactory(IControllerFactory controllerFactory);
   6:
   7:     public HashSet<string> DefaultNamespaces { get; }
   8:     public static ControllerBuilder Current { get; }
   9: }

具体来说,如果我们是注册的ControllerFactory的类型,那么GetControllerFactory在执行的时候会通过对注册类 型的反射(调用Activator的静态方法CreateInstance)来创建具体的ControllerFactory(系统不会对创建的 Controller进行缓存);如果注册的是一个具体的ControllerFactory对象,该对象直接从 GetControllerFactory返回。

被ASP.NET路由系统进行拦截处理后会生成一个用于封装路由信息的RouteData对象,而目标Controller的名称就包含在通过该 RouteData的Values属性表示的RouteValueDisctionary对象中,对应的Key为“controller”。而在默认的情 况下,这个作为路由数据的名称只能帮助我们解析出Controller的类型名称,如果我们在不同的命名空间下定义了多个同名的Controller类, 会导致激活系统无法确定具体的Controller的类型从而抛出异常。

为了解决这个问题,我们必须为定义了同名Controller类型的命名空间设置不同的优先级,具体来说我们有两种提升命名空间优先级的方式。第一种方式就是在调用RouteCollection的扩展方法MapRoute时指定一个命名空间的列表。通过这种方式指定的命名空间列表会保存在Route对象的DataTokens属性表示的RouteValueDictionary字典中,对应的Key为“Namespaces”。

   1: public static class RouteCollectionExtensions
   2: {
   3:     //其他成员
   4:     public static Route MapRoute(this RouteCollection routes, string name, string url, string[] namespaces);
   5:     public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, string[] namespaces);
   6:     public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces);
   7: }

而另一种提升命名空间优先级的方式就是将其添加到当前的ControllerBuilder中的默认命名空间列表中。从上面的给出的 ControllerBuilder的定义可以看出,它具有一个HashSet<string>类型的只读属性 DefaultNamespaces就代表了这么一个默认命名空间列表。对于这两种不同的命名空间优先级提升方式,前者(通过路由注册)指定命名空间具有 更高的优先级。

实例演示:如何提升命名空间的优先级

为了让读者对此如何提升命名空间优先级具有一个深刻的印象,我们来进行一个简单的实例演示。我们使用Visual Studio提供的项目模板创建一个空的ASP.NET MVC应用,并且使用如下所示的默认路由注册代码。

   1: public class MvcApplication : System.Web.HttpApplication
   2: {
   3:     public static void RegisterRoutes(RouteCollection routes)
   4:     {
   5:         routes.MapRoute(
   6:             name: "Default",
   7:             url: "{controller}/{action}/{id}",
   8:             defaults: new { controller = "Home", action = "Index",
   9:                 id = UrlParameter.Optional }
  10:         );
  11:     }
  12:     protected void Application_Start()
  13:     {
  14:         //其他操作
  15:         RegisterRoutes(RouteTable.Routes);
  16:     }
  17: }
  18: public class MvcApplication : System.Web.HttpApplication
  19: {
  20:     public static void RegisterRoutes(RouteCollection routes)
  21:     {
  22:         routes.MapRoute(
  23:             name: "Default",
  24:             url: "{controller}/{action}/{id}",
  25:             defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
  26:         );
  27:     }
  28:     protected void Application_Start()
  29:     {
  30:         //其他操作
  31:         RegisterRoutes(RouteTable.Routes);
  32:     }
  33: }

然后我们在Controllers目录下添加一个.cs 文件,并在该文件中定义两个同名的Controller类。如下面的代码片断所示,这两个HomeCotroller类分别定义在命名空间Artech.MvcAppArtech.MvcApp.Controllers之中,而Index操作返回的是一个将Controller类型全名为内容的ContentResult对象。

   1: namespace Artech.MvcApp.Controllers
   2: {
   3:     public class HomeController : Controller
   4:     {
   5:         public ActionResult Index()
   6:         {
   7:             return this.Content(this.GetType().FullName);
   8:         }
   9:     }
  10: }
  11: namespace Artech.MvcApp
  12: {
  13:     public class HomeController : Controller
  14:     {
  15:         public ActionResult Index()
  16:         {
  17:             return this.Content(this.GetType().FullName);
  18:         }
  19:     }
  20: }

现在我们直接运行该Web应用。由于具有多个Controller与注册的路由规则相匹配导致ASP.NET MVC的Controller激活系统无法确定目标哪个类型的Controller应该被选用,所以会出现如下图所示的错误。[源代码从这里下载]

image

目前定义了HomeController的两个命名空间具有相同的优先级,现在我们将其中一个定义在当前ControllerBuilder的默认 命名空间列表中以提升匹配优先级。如下面的代码片断所示,在Global.asax 的Application_Start方法中,我们将命名空间“Artech.MvcApp.Controllers”添加到当前ControllerBuilder的DefaultNamespaces属性所示的命名空间列表中

   1: public class MvcApplication : System.Web.HttpApplication
   2: {
   3:     protected void Application_Start()
   4:     {
   5:         //其他操作
   6:         ControllerBuilder.Current.DefaultNamespaces.Add("Artech.MvcApp.Controllers");
   7:     }
   8: }

对用同时匹配注册的路由规则的两个HomeController,由于“Artech.MvcApp.Controllers”命名空间具有更高的匹配优先级,所有定义其中的HomeController会被选用,这可以通过如下图所示的运行结果看出来。[源代码从这里下载]

image

为了检验在路由注册时指定的命名空间和作为当前ControllerBuilder的命名空间哪个具有更高匹配优先级,我们修改定义在Global.asax中的路由注册代码。如下面的代码片断所示,我们在调用RouteTable的静态属性Routes的MapRoute方法进行路由注册的时候指定了命名空间(“Artech.MvcApp”)

   1: public class MvcApplication : System.Web.HttpApplication
   2: {
   3:     public static void RegisterRoutes(RouteCollection routes)
   4:     {
   5:         routes.MapRoute(
   6:             name: "Default",
   7:             url: "{controller}/{action}/{id}",
   8:             defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
   9:             namespaces:new string[]{"Artech.MvcApp"}
  10:         );
  11:     }
  12:
  13:     protected void Application_Start()
  14:     {
  15:         //其他操作
  16:         RegisterRoutes(RouteTable.Routes);
  17:         ControllerBuilder.Current.DefaultNamespaces.Add("Artech.MvcApp.Controllers");
  18:     }
  19: }

再次运行我们的程序会在浏览器中得到如图3-3所示的结果,从中可以看出定义在命名空间“Artech.MvcApp”中的 HomeController被最终选用,可见较之作为当前ControllerBuilder的默认命名空间,在路由注册过程中执行的命名空间具有更高 的匹配优先级,前者可以视为后者的一种后备。[源代码从这里下载]

image

在路由注册时指定的命名空间比当前ControllerBuilder的默认命名空间具有更高的匹配优先级,但是对于这两个集合中的所有命名空间却具有相同的匹配优先级。换句话说,用 于辅助解析Controller类新的命名空间分为三个梯队,简称为路由命名空间、ConrollerBuilder命名空间和Controller类型 命名空间,如果前一个梯队不能正确解析出目标Controller的类型,则将后一个梯队的命名空间作为后备;反之,如果根据某个梯队的命名空间进行解析 得到多个匹配的Controller类型,会直接抛出异常

现在我们对本例的路由注册代码作了如下的修改,为注册的路由对象指定了两个命名空间(分别是两个HomeContrller所在的命名空间),运行我们的程序依然会得到如第一张图所示的错误。

   1: public class MvcApplication : System.Web.HttpApplication
   2: {
   3:     public static void RegisterRoutes(RouteCollection routes)
   4:     {
   5:         routes.MapRoute(
   6:             name: "Default",
   7:             url: "{controller}/{action}/{id}",
   8:             defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
   9:             namespaces: new string[] { "Artech.MvcApp", "Artech.MvcApp.Controllers" }
  10:         );
  11:     }
  12:
  13:     protected void Application_Start()
  14:     {
  15:         //其他操作
  16:         RegisterRoutes(RouteTable.Routes);
  17:     }
  18: }

针对Area的路由对象的命名空间

针对某个Area的路由映射是通过相应的AreaRegistration进行注册的,具体来说是在AreaRegistration的RegisterArea方法中调用AreaRegistrationContext对象的MapRoute方法进行注册的。如果在调用MapRoute方法中指定了表示命名空间的字符串,将自动作为注册的路由对象的命名空间,否则会将表示AreaRegistration所在命名空间的字符串加上“.*”后缀作为路由对象的命名空间。这里所说的“路由对象的命名空间”指的就是通过Route对象的DataTokens属性表示的RouteValueDictionary对象中Key为“Namespaces”的字符串数组,而该字符串最终会转移到生成的RouteData的DataTokens中。

除此之外,在调用AreaRegistrationContext的MapRoute方法时还会在注册Route对象DataTokens中添加一个Key为“UseNamespaceFallback” 的条目表示是否采用后备命名空间对Controller类型进行解析。如果注册对象具有命名空间(调用MapRoute方法时指定了命名空间或者对应的 AreaRegistration类型定义在某个命名空间中),该条目的值为False;否则为True。该条目同样反映在通过该Route对象生成的 RouteData对象的DataTokens属性中。[关于ASP.NET MVC路由,在我的文章《ASP.NET MVC路由扩展:路由映射》中具有详细的介绍]

在解析Controller真实类型的过程中,会先通过RouteData包含的命名空间来解析Controller类型。如 果Controller类型解析失败,则通过包含在通过RouteData的DataTokens属性表示的RouteValueDictionary对 象中的这个UseNamespaceFallback值来判断是否使用“后备”命名空间进行解析。具体来说,如果该值为True或者不存在,则先通过当前 ControllerBuilder的命名空间解析,如果失败则忽略命名空间直接采用类型名称进行匹配;否则直接因找不到匹配的Controller而抛 出异常

我们通过具体的例子来说明这个问题。在一个通过Visual Studio的ASP.NET MVC项目创建的空Web应用中,我们添加一个名称为Admin的Area,此时IDE会默认为我们添加如下一个AdminAreaRegistration类型。

   1: namespace Artech.MvcApp.Areas.Admin
   2: {
   3:     public class AdminAreaRegistration : AreaRegistration
   4:     {
   5:         public override string AreaName
   6:         {
   7:             get{return "Admin";}
   8:         }
   9:         public override void RegisterArea(AreaRegistrationContext context)
  10:         {
  11:             context.MapRoute("Admin_default", "Admin/{controller}/{action}/{id}",
  12:                 new { action = "Index", id = UrlParameter.Optional }
  13:             );
  14:         }
  15:     }
  16: }

AdminAreaRegistration类型定义在命名空间Artech.MvcApp.Areas.Admin中。现在我们在该Area中添加一个Controller类,其名为HomeController。默认情况下,我们添加的Controller类型和AdminAreaRegistration具有相同的命名空间,但是现在我们刻意将命名空间改为Artech.MvcApp.Areas

   1: namespace Artech.MvcApp.Areas
   2: {
   3:     public class HomeController : Controller
   4:     {
   5:         public ActionResult Index()
   6:         {
   7:             return Content("...");
   8:         }
   9:     }
  10: }

现在我们在浏览器中通过匹配的URL(/Admin/Home/Index)来访问 Area为Admin的HomeController的Index操作,会得到如下图所示的HTTP状态为404的错误。这就是因为在对 Controller类型进行解析的时候是严格按照对应的AreaRegistration所在命名空间来进行的,很显然在这个范围内是不可能找得到对应 的Controller类型的。[源代码从这里下载]

image

四、Controller的激活与URL路由

ASP.NET路由系统是HTTP请求抵达服务端的第一道屏障,它根据注册的路由规则对拦截的请求进行匹配并解析包含目标Controller和 Action名称的路由信息。而当前ControllerBuilder具有用于激活Controller对象的ControllerFactory,我 们现在看看两者是如何结合起来的。

通过《ASP.NET路由系统实现原理:HttpHandler的动态映射》介绍我们知道ASP.NET 路由系统的核心是一个叫做UrlRoutingModule的自定义HttpModule,路由的实现是它通过注册代表当前Web应用的 HttpApplication的PostResolveRequestCache事件对HttpHandler的动态映射来实现的。具体来说,它通过以 RouteTable的静态属性Routes代表的全局路由表对请求进行匹配并得到一个RouteData对象。RouteData具有一个实现了接口 IRouteHandler的属性RouteHandler,通过该属性的GetHttpHandler方法得到最终被映射到当前请求的 HttpHandler

对于ASP.NET MVC应用来说,RouteData的RouteHandler属性类型为MvcRouteHandler,体现在MvcRouteHandler类型上 关于HttpHandler的提供机制基本上(不是完全等同)可以通过如下的代码来表示。MvcRouteHandler维护着一个 ControllerFactory对象,该对象可以在构造函数中指定,如果没有显示指定则直接通过调用当前ControllerBuilder的 GetControllerFactory方法获取。

   1: public class MvcRouteHandler : IRouteHandler
   2: {
   3:     private IControllerFactory _controllerFactory;
   4:      public MvcRouteHandler(): this(ControllerBuilder.Current.GetControllerFactory())
   5:     { }
   6:     public MvcRouteHandler(IControllerFactory controllerFactory)
   7:     {
   8:         _controllerFactory = controllerFactory;
   9:     }
  10:     IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext)
  11:     {
  12:         string controllerName = (string)requestContext.RouteData.GetRequiredString("controller");
  13:         SessionStateBehavior sessionStateBehavior = _controllerFactory.GetControllerSessionBehavior(requestContext, controllerName);
  14:         requestContext.HttpContext.SetSessionStateBehavior(sessionStateBehavior);
  15:
  16:         return new MvcHandler(requestContext);
  17:     }
  18: }

在用于提供HttpHandler的GetHttpHandler方法中,除了返回一个实现了IHttpHandler接口的MvcHandler 对象之外,还需要对当前HTTP上下文的会话状态行为模式进行设置。具体来说,首先通过包含在传入RequestContext的RouteData对象 得到Controller的名称,该名称连同RequestContext对象一起传入ControllerFactory的 GetControllerSessionBehavior方法得到一个类型为SessionStateBehavior的枚举。最后通过 RequestContext得到表示当前HTTP上下文的HttpContextBase对象(实际上是一个HttpContextWrapper对 象)并调用其SetSessionStateBehavior方法。

绍我们知道RouteData中的RouteHandler属性最初来源于对应的Route对象的同名属性,而当我们调用 RouteCollection的扩展方法MapRoute方法时,其内部会直接创建并添加一个Route对象。由于在创建Route对象是并没有显式指 定ControllerFactory,所以通过当前ControllerBuilder的GetControllerFactory方法得到的 ControllerFactory默认被使用。

通过当前ControllerBuilder的GetControllerFactory方法得到的ControllerFactory仅仅用于获 取会话状态行为模式,而MvcHandler真正将它用于创建Controller。MvcHandler中关于对请求处理的逻辑基本上可以通过如下的代 码片断来体现。如下面的代码片断所示,MvcHandler具有一个表示当前请求上下文的RequestContext属性,该属性在构造函数中被初始 化。

   1: public class MvcHandler : IHttpHandler
   2: {
   3:     public RequestContext RequestContext { get; private set; }
   4:     public bool IsReusable
   5:     {
   6:         get { return false; }
   7:     }
   8:     public MvcHandler(RequestContext requestContext)
   9:     {
  10:         this.RequestContext = requestContext;
  11:     }
  12:     public void ProcessRequest(HttpContext context)
  13:     {
  14:         IControllerFactory controllerFactory = ControllerBuilder.Current.GetControllerFactory();
  15:         string controllerName = this.RequestContext.RouteData.GetRequiredString("controller");
  16:         IController controller = controllerFactory.CreateController(this.RequestContext, controllerName);
  17:         try
  18:         {
  19:             controller.Execute(this.RequestContext);
  20:         }
  21:         finally
  22:         {
  23:             controllerFactory.ReleaseController(controller);
  24:         }
  25:     }
  26: }

在ProcessRequest方法中,通过RequestContext对象得到目标Controller的名称,并通过它利用当前 ControllerBuilder创建的ControllerFactory激活Controller对象。在执行了被激活Controller对象的 Execute方法之后调用ControllerFactory的ReleaseController对其进行释放清理工作。

ASP.NET MVC Controller激活系统详解:总体设计
ASP.NET MVC Controller激活系统详解:默认实现
ASP.NET MVC Controller激活系统详解:IoC的应用[上篇]
ASP.NET MVC Controller激活系统详解:IoC的应用[下篇]

构建插件式的应用程序框架(五)----管理插件 - 纶巾客 - 博客园

mikel阅读(1022)

来源: 构建插件式的应用程序框架(五)----管理插件 – 纶巾客 – 博客园

我们现在已经搭建了插件式的应用程序框架,接下来的工作就是要充实框架的内容,提供基本的服务,也就是Service。我想首要的任务就是提供插件的管理服务,我在前面的文章也提到了,要实现动态加载必须要知道插件寄宿在哪里,哪些要加载,哪些不加载,这些就是这篇文章要讨论的问题。
首先解决的就是插件放在什么地方,我采取的传统的方法,将插件放到应用程序所在目录下的制定目录,我会在应用程序所在的目录下创建一个文件夹,命名为Plugins。接下来的工作就是要通知哪些插件是要加载的,哪些是不需要加载的,我会将这些信息放到应用程序的配置文件中的制定配置块中,当应用程序运行的时候,就会读取配置文件,并根据获得的信息加载插件。另外我们的应用程序框架是建立在Service基础之上,所以我需要创建一个管理插件的service。
我们现在定义一个插件管理的Service接口。

using System;
using System.Collections.Generic;
using System.Text;

namespace PluginFramework
{
public interface IPluginService
{
IApplication Application { get;set;}
void AddPlugin(String pluginName, String pluginType, String Assembly, String pluginDescription);
void RemovePlugin(String pluginName);
String[] GetAllPluginNames();
Boolean Contains(String pluginName);
Boolean LoadPlugin(String pluginName);
Boolean UnLoadPlugin(String pluginName);
IPlugin GetPluginInstance(String pluginName);
void LoadAllPlugin();
}
}

PluginService要实现的目标首先是在配置文件中添加/删除要加载的插件以及相关的信息,接下来就是动态的加载插件。我们要定义几个类型:Plugin配置区块类型,Plugin元素类型,plugin元素集合类型,以便我们能够读取插件的信息。
最后我们实现PluginService:

      

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Configuration;
using System.Reflection;
using System.Windows.Forms;
using System.IO;
using System.Collections;

namespace PluginFramework
{
public class PluginService : IPluginService
{
private IApplication application = null;
private PluginConfigurationSection config = null;
private Dictionary<String, IPlugin> plugins = new Dictionary<string, IPlugin>();
private XmlDocument doc = new XmlDocument();

public PluginService()
{

}

public PluginService(IApplication application)
{
this.application = application;

}

IPluginService Members
}
}

      

由于代码比较多,我也就不一一列举了,只把比较重要的代码列出来,其余的我会提供源代码的下载。在实现了PluginService以后,我们需要有一个地方能够使用这个Service来管理插件,我的做法是在一个菜单里添加一个项目,当用户点击这个项目的时候弹出插件管理的对话框,用户在这个对话框中选择使用那些插件,当插件被选中的时候,插件会被立即加载进来,并且记录到配置文件里,当用户下次运行应用程序的时候,插件默认会被自动的加载。

       另外从现在开始我们就需要使用配置文件了,所以,我们需要给应用程序添加一个app.config文件,文件内容如下:

<?xml version=”1.0″ encoding=”utf-8″ ?>
<configuration>
<configSections>
<section
name=”PluginSection”
type=”PluginFramework.PluginConfigurationSection, PluginFramework”
/>
</configSections>
<PluginSection>

</PluginSection>
</configuration>

样子,总体来说我们就为Plugin的管理提供了一个基本的实现,如果大家还有什么不明白的地方,可以参考我提供的源代码或者通过e-mail和我联系。
源代码下载

构建插件式的应用程序框架(四)----服务容器 - 纶巾客 - 博客园

mikel阅读(1054)

来源: 构建插件式的应用程序框架(四)----服务容器 – 纶巾客 – 博客园

      在构建插件式的应用程序框架(二)----订立契约一文中,可以看到我们的IApplication接口是派生于IServiceContainer接口的。为什么要派生于IServiceContainer呢?我们来看看IServiceContainer的定义,它有几个AddService方法和RemoveService方法以及从IserviceProvider继承过来的GetService方法。Service本身是.NET设计时架构的基础,Service提供设计时对象访问某项功能的方法实现,说起来还真拗口。就我看来,ServiceContainer机 制的本质就是解耦合,就是将类型的设计时功能从类型本身剥离出来。如果你把类型的设计时功能也封装到类型里,这样的类型包含了很多只有开发人员才会用到而 最终用户根本不需要的功能,使得类型既臃肿有不便于扩展。而将设计时功能剥离出来,这样类型就可以不依赖于特定的设计环境,之所以现在有这么多非官方的.NET设计环境可能就是这个原因吧。
我们的插件式的应用程序框架正好也需要这样一个松散的架构,我就移花接木把它应用到我们的框架中。
ServiceContainer是.NET提供的IserviceContainer的实现,如果没有特殊的需要我们不必扩展它,而是直接的利用它。在上一篇文章中我们在实现IApplication接口的时候就直接使用的ServiceContainer。我们在使用Service架构的时候,总是倾向于有一个根容器,各个Service容器构成了一个Service容器树,每一个节点的服务都可以一直向上传递,直到根部,而每一个节点请求Service的时候,我们总是可以从根节点获得。我把这个根节点比喻成一个服务中心,它汇总了所有可提供的服务,当某个对象要请求服务(GetService)只需要向根结点发送要获得的服务,根结点就可以把服务的对象传递给它。
从另外一个角度看,ServiceContainer为我们的插件是应用程序提供了有力的支持,利用ServiceContainer,你不但可以获得应用程序所提供的所有的功能,而且你还可以通过插件向应用程序添加Service,而你添加的Service又可以服务另外的Service,这样我们的应用程序框架就更加的灵活了。但是任何东西都是有两面性的,带来灵活的同时也为开发人员的工作增加了复杂度,所以使用ServcieContianer开发的应用程序必须提供足够详细的文档,否则开发人员可能根本不知道你到底有多少Service可以用,因为很多的Service是通过插件提供的,可能应用程序的作者都不会知道程序发布以后会出现多少Service。
写了这么多,可能接触过ServiceContainer的朋友已经觉得罗唆了,没接触过的还是觉得说得莫明其妙。有空接着写,我会创建几个简单的服务演练演练,增强一下感性认识,呵呵。

构建插件式的应用程序框架(二)----订立契约 - 纶巾客 - 博客园

mikel阅读(1123)

来源: 构建插件式的应用程序框架(二)----订立契约 – 纶巾客 – 博客园

无论是用COM的方式,还是普通DLL,抑或.NET方 式来实现插件框架,首先要面临的问题就是如何订立契约。如同我上一篇文章讲到的一样,契约是应用程序和插件之间进行交互的依据和凭证。应用程序必须声明我 有什么样的功能可被插件使用,并且插件必须符合什么条件才能被我使用。反之,插件必须要知道应用程序提供什么样的功能,我才能将自己的功能融入到应用程序 的体系中。本系列文章主要讲如何使用.NET实现插件式的应用程序框架,所以其它的方式我就不再提了。

如何使用.NET订立契约呢?首先想到的Interface, 其次是抽象类,但是在插件模式中我使用接口,因为我们是在满足应用程序的主要目的的基础上来提供附加的插件功能,就这一点来说,接口更灵活,更容易扩展。 接下来,如何订立契约的内容呢?这就要根据你的业务需求了,为了讲解的方便,我们定义一个最最基本的插件式应用程序的插件契约。我们做一个假定,我们的应 用程序是一个多文档的应用程序,包含一个主菜单栏,一个工具栏,菜单栏可以在程序的上下左右四个方向停靠,另外还有一个状态栏。到后边,如果有必要,我会 扩展这个应用程序,让他本身提供更多的可供插件使用的功能。所以就目前而言,我想实现的功能就是让插件为主程序添加工具条,菜单项并实现一些简单的功能。

应 用程序向插件提供服务有两种方式,一种是直接再应用程序接口中声明属性或者方法,一种是将应用程序接口声明成一个服务容器。我打算两种方式都用,明确的功 能就在接口中直接声明成属性或者方法,另外将应用程序声明成一个服务容器,以方便插入更多的服务功能,提高应用程序的可扩展性。

下边是一个非常简单的应用程序接口定义,对于我们的假定已经足够了。
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel.Design;
using System.Windows.Forms;

namespace PluginFramework
{
public interface IApplication:IServiceContainer
{
ToolStripPanel LeftToolPanel { get;}
ToolStripPanel RightToolPanel { get;}
ToolStripPanel TopToolPanel { get;}
ToolStripPanel BottomToolPanel { get;}

MenuStrip MainMenuStrip { get;}
StatusStrip StatusBar { get;}
}
}

插件的接口定义:
using System;
using System.Collections.Generic;
using System.Text;

namespace PluginFramework
{
public interface IPlugin
{
IApplication Application { get;set;}
String Name { get;set;}
String Description { get;set;}
void Load();
void UnLoad();

event EventHandler<EventArgs> Loading;
}
}

 

时间又不早了,今天就写到这里,明天接着写。