[转载]ASP.NET MVC权限设计思考

mikel阅读(920)

转载.NET MVC权限设计思考 – 小城岁月 – 博客园.

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

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

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

View Code

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

            AddVersionHeader(httpContext);
            RemoveOptionalRoutingParameters();

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

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

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

View Code

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

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

View Code

  protected internal virtual void ProcessRequest(HttpContextBase httpContext) {
            SecurityUtil.ProcessInApplicationTrust(() => {
                IController controller;
                IControllerFactory factory;
                ProcessRequestInit(httpContext, out controller, out factory);

                try {
                    controller.Execute(RequestContext);
                }
                finally {
                    factory.ReleaseController(controller);
                }
            });
        }

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

View Code

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

            VerifyExecuteCalledOnce();
            Initialize(requestContext);

            using (ScopeStorage.CreateTransientScope()) {
                ExecuteCore();
            }
        }

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

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

View Code

 protected override void ExecuteCore() {
            // If code in this method needs to be updated, please also check the BeginExecuteCore() and
            // EndExecuteCore() methods of AsyncController to see if that code also must be updated.

            PossiblyLoadTempData();
            try {
                string actionName = RouteData.GetRequiredString("action");
                if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) {
                    HandleUnknownAction(actionName);
                }
            }
            finally {
                PossiblySaveTempData();
            }
        }

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

View Code

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

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

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

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

首先设计个继承Controller的基类BaseController,详细设计请参照注释

View Code

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

View Code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Web.Routing;
using Microsoft.Practices.Unity;

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

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

        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {             
            if (controllerType.IsSubclassOf(typeof(Core.BaseController)))
            {              
                //早期切入点 方式1,使用默认的无构造方式
                //目的必免直接反射Controller,提高效率
                Core.BaseController controller = container.Resolve(controllerType) as Core.BaseController;
                controller.OnInit(requestContext);  //切入点 
                
                //假如这个位置使用构造注入那OnInit就会构造函数执行时就实现相关验证业务包括UserState已经创建
                //这样做导致Controller每次都要被动态反射创建,但好处是可以使用权限切入点更早
                //这样权限业务可以更早的注入业务层中(如UserState传递至业务层)
                //代码省略
                
                return controller;
            }
            else
                return container.Resolve(controllerType) as IController;
        }

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


}

我们再来实现个MenuController ,详细参照注释

View Code

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

namespace Demo.Mvc.Controller
{    
          
     
    [HandleError]
    public class MenuController:Core.BaseController
    {
        private IMenuService service;

        //如果我们ControllerFactory中使用构造生成Controller,那OnInit在构造的同时也成功获得了UserState
        //并且可以在这个时候将UserState注入到业务层中
        public MenuController(IMenuService service)
        {
             
            this.service = service;
            //this.service.UserState = this.UserState;   
            //这样做的优点就是不用再重载基类的Initialize方法,前提ControllerFactory中使用构造生成Controller,这样可以过早的拿到Session.
        }

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

        public MenuController()
        { }

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

        public ActionResult Index(UserVO userVo)
        {
            if (ModelState.IsValid)
            {
                ViewData["Key1"] = "TEST1";
                TempData["Key2"] = "TEST2";
            }
            else
            {
                 
            }
            return View();   //默认封装ViewResult返回   
        }

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

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

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

[转载]Lucene.Net, SQL Server 2008全文检索, Like模糊查询的一点心得

mikel阅读(1251)

[转载]Lucene.Net, SQL Server 2008全文检索, Like模糊查询的一点心得 – BobLiu – 博客园.

1.Like 模糊查询

例如: select * from table where productdes like ‘%cad%’

它不能利用索引查询, 只能是全表扫描, 效率较低, 但查询结果准确.

2.SQL Server 2008全文检索

为了提高效率, 换用SQL server的全文检索, 怎么建全文检索就此略去, 不提, 只看查询方法.

例如: select * from table where contains(ProductDesc, ‘*cad*’)

它查询的效率很高, 中文分词没有问题, 但缺点竟然是在英文查询上, 如果想模糊查询带cad三个字母的数据, 它只能搜索出以cad为完整单词的数据, 例如: 它能查出abc cad , cad def, 或者cad, 它不能查出autocad这种字母连在一起的数据, 也就是说, SQL server的全文检索的英文分词是空格.

3.Lucene全文检索

SQL server全文检索不灵了, 只能找第三方的方案了, 首当其冲的就是Lucene了, 但在.net下, Lucene却很不顺当. NLucene是将 Lucene 从 Java 移植到 .NET 的一个 SourceForge 项目,它从 Lucene 1.2 版本转化而来,
但2002年就停止更新了. 因为 NLucene 项目到2002年就没有再推出新的版本,可Lucene 却一直在发展,于是有人把Lucene 1.3版移植到.NET就成了Lucene .NET,但是Lucene .Net发展到2.0版的时候变成了商业化的产品,脱离了开源项目. 受到Lucene.Net脱离开源项目的影响,有人为了继续发展开源.Net搜索引擎,于是在Lucene.Net的原有基础上继续发展该项目,但是名字 改成了DotLucene以区别于Lucene.Net。但现在打开官网一看, 得, 又停止了. 看来, 只能用Lucene.Net2.0这最后一个开源版本了.

建索引, 查索引的代码如下:

private void btnCreateIndex_Click(object sender, EventArgs e)
{
StringBuilder sbSQL = new StringBuilder();
sbSQL.AppendLine(” Select ProductName, ProductDesc from Tbl_Product  “);
dt = DataHelper.RetrieveData(sbSQL.ToString());

//建立索引
IndexWriter writer = CreateIndex(dt);
}

public IndexWriter CreateIndex(DataTable dt)
{
IndexWriter writer = new IndexWriter(“c:/index/”, new StandardAnalyzer(), true);
try
{
//建立索引字段
foreach(DataRow dr in dt.Rows)
{
Document doc=new Document();
doc.Add(new Field(“ProductName”, dr[0].ToString(), Field.Store.YES, Field.Index.TOKENIZED));
doc.Add(new Field(“ProductDesc”, dr[1].ToString(), Field.Store.YES, Field.Index.TOKENIZED));
writer.AddDocument(doc);
}
writer.Optimize();
writer.Close();
}
catch(Exception e)
{
MessageBox.Show(e.Message);
}
return writer;
}

private void Search_Click(object sender, EventArgs e)
{
string key = this.textBox1.Text;

IndexSearcher search = new IndexSearcher(@”c:/index/”); //把刚才建立的索引取出来

Term t = new Term(“ProductDesc”, “*” + key + “*”);
WildcardQuery query = new WildcardQuery(t);

Hits hit = search.Search(query);

DataTable dt = new DataTable();
dt.Columns.Add(“ProductDesc”);

for (int i = 0; i <= hit.Length() – 1; i++)
{
DataRow dr = dt.NewRow();
dr[0] = hit.Doc(i).GetField(“ProductDesc”).StringValue();
dt.Rows.Add(dr);
}
this.dataGridView1.DataSource = dt;
}

关键是下面这两句:

Term t = new Term(“ProductDesc”, “*” + key + “*”);
WildcardQuery query = new WildcardQuery(t);

它通过WildcardQuery(通配符查询, 包括?和*) 实现了类似like的模糊查询, 这样, 就解决了上面like查得出但慢, SQLServer full-text search虽快但查不出来的问题.

但据说lucene建(更新)索引时不能查, 查时则不能建(更新)索引, 还没细看,如果是这样, 还是有些不爽.

4.全文索引和like语句比较

全文索引的执行效率高.一般全文索引使用的是倒排索引,能够支持多关键字的索引,而LIKE只有前缀匹配时才能使用索引,否则就是全表扫描,效率当然很低

当然是全文索引效率高。但全文索引存在填充问题,需要在增加内容后进行增量填充,否则检索不到新增的内容的。可以设置调度来自动运行。

5.倒排索引 inverted index

为什么Sql server全文检索和Lucune全文检索速度快呢, 因为它和大多数搜索引擎一样, 都使用了倒排索引 inverted index

从别人那里抄了例子, 如下:

假设有3篇文章:

文章1的内容为:I was a student, i came from chengdu.

文章2的内容为:My father is a teacher, and i am a student.

文章3的内容为:Chengdu is my hometown. my father’s hometown is wuhan.

Lucene是基于关键字索引和查询的。首先取得这3篇文章的关键字,通常需要如下处理措施(在Lucene中以下措施由Analyzer类完成):

1)需要把3篇文章的内容切分成一个个单词,作为索引关键字的候选集。英文中单词与单词之间有空格,分词比较好处理,但中文字与字之间没有空隔,是连在一起的,所以中文分词处理要麻烦。

2)需要将文章中分离出来的没有实际意义的单词过滤掉。比如a、from、and,中文中的“的”、“也”、“啊”等通常无具体含义的词可以去掉。还要过滤掉标点符号。

3)单词需要统一大小写。

4)查找与当前关键字相关联的其他关键字,即统一单词不同的时态。如“came”、“coming”统一成“come”,再进行查找。

经过上面处理后,3篇文章的所有关键字为:

文章1:[i] [am] [student] [i] [come] [chengdu]

文章2:[my] [father] [is] [teacher] [i] [am] [student]

文章3:[chengdu] [is] [my] [hometown] [my] [father] [hometown] [is] [wuhan]

有了关键字后,就可以建立倒排索引了。上面的对应关系是顺排的,即“文章号→关键字”,倒排处理为“关键字→文章号”。文章1、2、3经过倒排后变成如表5-1所示。

表5-1  倒排文件

i

12

am

12

student

12

come

1

chengdu

1,3

my

2,3

father

2,3

is

2,3

teacher

2

hometown

3

wuhan

3

最后推荐几个学习资料:

1.全文检索与Lucene学习
http://www.cnblogs.com/keith2011/archive/2011/09/12/2173984.html

2.Lucene:WildcardQuery
http://hi.baidu.com/lewutian/blog/item/2f5adafb89cac468034f56ed.html

3.lucene.net 查询与添加索引的同步问题
http://blog.csdn.net/poson/article/details/2201880

4.倒排文件索引(Inverted File Index)的建立(2)
http://book.51cto.com/art/200906/128320.htm

[转载]来源于WCF的设计模式:可扩展对象模式

mikel阅读(987)

[转载]来源于WCF的设计模式:可扩展对象模式 – Artech – 博客园.

我一直很喜欢剖析微软一些产品、框架的底层实现。在我看来,这不但让我们可以更加深入地 了解其运作的原理,同时也能提高设计/架构的技能。因为对于这些框架或者产品来说,高质量的设计是它们能够成功的一个最基本的因素。比如说比如 ASP.NET,不但能够支持传统的Web Form应用,MVC同样建立在它基础之上。比如说WCF,从其诞生的那一天开始,真个架构体系就从未改变。这些应用在这些产品和框架上的设计其实是最值 得我们学习的设计案例。比如说,今天我们介绍的“可扩展对象模式(Extensible Object Pattern)”就来源于WCF。[源代码从这里下载]

一、一个简单的“可扩展对象模式”的实现

为 了让这种所谓的“可扩展对象模式”有一个大概的了解,我们先来演示一个简单的例子。现在有一个表示房间的类型Room,它具有几个基本的属性Walls、 Windows和Door分别表示房间的墙、窗户和门。现在我们要来创建一个Room对象,即分别创建组成这个Room对象的各个构成元素。按照“大事化 小”这个基本的设计原则,我们分别创建相应的Builder来分别为Room构建相应的元素。按照“可扩展对象模式”的原理,Room对象就是一个可扩展 对象,而相应的Builder实现了对它的扩展。现在我们将Room这个类型定义在实现了接口 IExtensibleObject<Room>的可扩展对象。

   1: public class Room : IExtensibleObject<Room>
   2: {
   3:     public Room()
   4:     {
   5:         this.Extensions = new ExtensionCollection<Room>(this);
   6:     }
   7:     public Door Door { get; set; }
   8:     public IList<Wall> Walls { get; set; }
   9:     public Window Window { get; set; }
  10:     public IExtensionCollection<Room> Extensions { get; private set; }
  11: }
  12: public class Door{}
  13: public class Wall{}
  14: public class Window{}

为Room对象构建门、窗和墙的Builder(DoorBuilder、WindowBuilder和WallBuilder)均实现了相同的接口IExtension<Room>,表明它们都是针对Room的扩展。

   1: public class DoorBuilder : IExtension<Room>
   2: {
   3:     public Door Door { get; private set; }
   4:     public void Attach(Room owner)
   5:     {
   6:         owner.Door = this.Door = new Door();
   7:     }
   8:     public void Detach(Room owner)
   9:     {
  10:         if (this.Door == owner.Door)
  11:         {
  12:             owner.Door = null;
  13:             this.Door = null;
  14:         }
  15:     }
  16: }
  17:
  18: public class WindowBuilder : IExtension<Room>
  19: {
  20:     public Window Window { get; private set; }
  21:     public void Attach(Room owner)
  22:     {
  23:         owner.Window = this.Window = new Window();
  24:     }
  25:     public void Detach(Room owner)
  26:     {
  27:         if (this.Window == owner.Window)
  28:         {
  29:             owner.Window = null;
  30:             this.Window = null;
  31:         }
  32:     }
  33: }
  34:
  35: public class WallBuilder : IExtension<Room>
  36: {
  37:     public Wall[] Walls { get; private set;}
  38:     public void Attach(Room owner)
  39:     {
  40:         owner.Walls = this.Walls = new Wall[] { new Wall(), new Wall(), new Wall(), new Wall() };
  41:     }
  42:     public void Detach(Room owner)
  43:     {
  44:         if (null == owner.Walls || null== this.Walls)
  45:         {
  46:             this.Walls = null;
  47:             return;
  48:         }
  49:         Array.ForEach(this.Walls, wall =>
  50:             {
  51:                 if (owner.Walls.Contains(wall))
  52:                 {
  53:                     owner.Walls.Remove(wall);
  54:                 }
  55:             });
  56:         this.Walls = null;
  57:     }
  58: }

现在我们真正创建Room对象的程序写成如下形式:先创建可扩展对象Room,并将用于用于构建相应元素的三个Builder添加到以 Extensions属性表示的扩展集合中。经过这个简单的过程,一个完整的Room对象就已经被正常的构建了。这个简单的应用体现和很好的可扩展性:任 何针对Room对象的扩展都可以通过相应扩展对象(IExtension<Room>)来实现,如果我们需要,只需要将这些扩展添加到它的 Extensions集合中就可以了。

   1: Room room = new Room();
   2: room.Extensions.Add(new DoorBuilder());
   3: room.Extensions.Add(new WindowBuilder());
   4: room.Extensions.Add(new WallBuilder());
   5:
   6: Debug.Assert(room.Door != null, "Door has not been built!");
   7: Debug.Assert(room.Window != null, "Window has not been built!");
   8: Debug.Assert(room.Walls != null, "Walls have not been built!");

二、IExtensibleObject<T>和IExtension<T>

这个可扩展对象模式涉及到两个基本的接口,即IExtensibleObject<T>和IExtension<T>,前 者代表可扩展对象,后者代表对这个可扩展对象的扩展,而这个泛型参数T则代表定义成可扩展对象的类型。我想如果你是第一个接触者两个接口,看到这个介绍你 可能会觉得很晕,尤其是“可扩展对象类型T实现接口IExtensibleObject<T>”,不过多想想,应该会很快绕过弯子来的。

IExtensibleObject<T>接口仅仅定义了一个唯一的属性Extensions,而它类型是 IExteniosnCollection<T>代表针对可扩展对象类型T的扩展的集合。System.ServiceModel定义了具体 的集合类型ExteniosnCollection<T>实现了IExteniosnCollection<T>接口

   1: public interface IExtensibleObject<T> where T: IExtensibleObject<T>
   2: {
   3:     IExtensionCollection<T> Extensions { get; }
   4: }

而所谓的“针对可扩展对象类型T的扩展”就是实现了第二个接口IExtension<T>的对象。该接口具有两个方法Attach和 Detach,具体相同的参数owner。当我们将某个扩展对象添加到某个可扩展对象的Extensions集合中的时候,Attach方法会自动被调 用,而传入的参数owner就是这个需要被扩展的对象。与此相对,Detach则在当该扩展对象从可扩展对象的Extensions集合中移出的时候被调 用。

   1: public interface IExtension<T> where T: IExtensibleObject<T>
   2: {
   3:     void Attach(T owner);
   4:     void Detach(T owner);
   5: }

在前面的例子中,我们将DoorBuilder、WindowBuilder和WallBuilder实现 IExtension<Room>接口,将相应的针对门、窗和墙的构建实现在Attach方法中。所以当它们被作为对Room对象的扩展添加 到一个具体的Room对象的Extensions集合上的时候,这个Romm对象就分别有了一道门、一扇窗和四面墙。此外,由于被添加的Builder有 可能被移除,如果被移除后,先前被创建的门、窗和墙应该也一并移掉,而这些操作被定义在Detach方法中。

三、总结

在这里,我们将围绕着IExtensibleObject<T>和IExtension<T>这两个接口的设计方式说成一 种“设计模式”,可能不太妥当。实际上,任何存在扩展可能的类型都可以按照这样的方式来设计。而我们熟悉的一些设计模式都可以按照“可扩展对象”的方式来 设计。文中Room采用的涉及模式可以看成是Builder模式。

注:关于“可扩展对象模式”,李会军同学写了一篇很好的文章《技巧:使用可扩展对象模式扩展HttpApplication

[转载]Anthologize:利用WordPress轻松制作电子书!

mikel阅读(975)

[转载]Anthologize:利用Wordpress轻松制作电子书!.

WordPress 3.0有一个专门用来选辑文章制作电子书的plugin叫作Anthologize
Anthologize这个字,原意就是汇编、选辑成册的意思。
而支援WordPress 3.0的这个Anthologize外挂,正如它字面上的意思,就是用来装置在WordPress 3.0博客平台上,供作者从自己的博客里挑选文章,制作属于自己的电子书。
安装
Anthologize的安装,就跟其他所有WordPress外挂一样。
你可以在WordPress的管理介面里,选择「插件」>「新增插件」>「安装插件」,搜寻Anthologize之后自动安装。
或者,你也可以到Anthologize官网下载最新版本的压缩档,然后,再把解压之后的档案上传到「wp-content/plugins」手动安装。
使用
一旦在WordPress的管理介面里启用Anthologize这个插件,就可以开始使用Anthologize来制作电子书。
基本上,Anthologize把「Project」视为一本电子书,所以,使用Anthologize来制作电子书的第一个步骤就是「New Project」
同理,在Add New Project这个步骤必须填写的「Project Title」就是书名,「Subtitle」就是副标。
编辑
前面提到Anthologize把单本书称作「Project」,而每一本书里的章节,Anthologize则称之为「Part」。
在Anthologize的Project管理介面里,你博客的所有文章都被称作「Items」,你可以把它当作是「Part」的素材原料。
一如建立新书的第一个步骤是「New Project」,建立一个新章节的第一个步骤就是「New Part」。
「Part」建好之后,你就可以利用Anthologize所提供简单易用的拖拉介面,直接把「Items」里的文章拖拉到「Part」里。
管理
「Project」建立完成后,你随时可以到「My Project」去管理你已经建好的电子书专案。
输出
Anthologize提供输出4种电子书格式:
ePub
PDF
TEI (plus HTML)
RTF
以及Letter(215.9×279.4 mm)与A4(210×297 mm)两种尺寸。
由于Anthologize目前还在Alpha,很难说它是否会成为具影响力的电子书工具,但,至少它提供并实现了博客直接出书的便利可能。

[转载]10个最好的wordpress搜索引擎优化(SEO)插件

mikel阅读(912)

[转载]10个最好的wordpress搜索引擎优化(SEO)插件 – 孟晨 – 博客园.

搜索引擎喜欢博客,对于wordpress博客平台而言,你不需要具备一 些基本的SEO技能,wordpress对搜索引擎非常友好,它天生就是为不懂SEO的bloggers而设计的。本文不是为那些搜索引擎优化专家准备 的,如果你拥有自己的博客,你只希望专心的投入到博客的文章创新和写作中去,又不想在搜索引擎优化上面耗费你宝贵的时间和精力,那么本文为你介绍的20个wordpress SEO插件可以帮助你获得更多的流量和读者。

什么是Wordpress SEO插件?

WordPress SEO插件是简单的搜索引擎优化工具,它可以帮助你选择正确的tags,告诉搜索引擎蜘蛛如何在你的博客上工作,优化你的文章标题,以及优化你的博客内部链接……使用SEO插件可以让你无须考虑过多的搜索引擎优化细节和SEO技巧,同时它们能让你的博客构架对搜索引擎更加友好,从而让你的博客文章更快被搜索引擎索引,并获得不错的搜索引擎排名。

10个最好的WordPress搜索引擎优化插件

All in One SEO Pack – wordpress平台上最受欢迎的插件之一,该WordPress 插件可以帮助你选择最好的文章标题和关键词,以及让你避免发布重复内容。利用该插件,你可以给每篇页面添加独立的关键词和摘要,还可以防止存档页面被抓取以免被 Google 降权。

Automatic SEO Links – Automatic SEO Links允许你选择一个关键词或者文章标题来自动进行链接,比如我们可以设置“网络营销博客” 的自动链接URL为“http://zhengyong.net”,那么在你post的文章中出现“网络营销博客”这个词的时候,插件会将该关键词自动添 加链接。你可以为关键词设置为内部或者外部链接,设置锚文本,选择是否为链接添加 “nofollow” 等等。该插件还有个特色就是一篇文章中只会出现一次相同的自动链接,避免过度优化而造成搜索引擎惩罚。

Google XML Sitemaps – 个人认为这个是每个wordpress博客都必须的SEO插件工具。虽然说的是google xml站点地图生成工具,但实际上该插件生成的XML-sitmap也可以被ask,msn,yahoo等搜索引擎蜘蛛读取。

更多站点地图工具参考:Sitemap Tools – 免费的站点地图生成工具及插件介绍Google站点地图生成工具BETA版

HeadSpace2 – 该插件让你可以安装各种类型的meta-data,为页面添加特别的JavaScript和css代码,以及tags提示等,也是一个非常有用的SEO插件。

Meta Robots WordPress plugin – 可以为你博客的任何页面添加针对搜索引擎机器人的metadata。为页面和目录添加nofollow属性,这对一些利用web service的会员计划来建立的博客商店来说非常重要,可以避免搜索引擎索引重复内容。你还可以防止登陆页,归档页等被索引。

Nofollow Case by Case – 该插件让你去除在文章评论中的nofollow属性,你可以选择在哪个文章中支持评论的dofollow,避免全站都采用dofollow的评论,防止垃圾评论。

Platinum SEO Plugin – Platinum SEO Plugin 支持永久链接的自动301重定向,自动生成meta tags,以及post slug优化等,还可以帮助你避免副本内容。

Redirection – 在很多时候,你因为一些不同的原因需要对博客文章进行移动或者修改,这很容易造成原有页面在搜索引擎的排名下降。Redirection插件可以通过301重定向帮助你捕获404错误日志,因此你可以发现并修改这些错误,你还可以设置一个错误RSS feed。

SEO Blogroll – 如果你担心你的blogroll中的博客链接分享你的PageRank,那么使用SEOBlogroll,你可以对链接分组进行分别设置,并为一些链接组分配nofollow属性,避免该链接组中的博客分享你的网站权重。

SEO for Paged Comments– WordPress 2.7评论分页功能有效的降低了页面体积,增加页面载入速度,尤其对于留言较多的博客确实很实用,但WordPress默认的评论分页随着带来的SEO问 题–内容重复,正文页面与评论分页页面内容一样。该插件将评论分页页面的post正文显示摘要,再加上一个至post的permalink链接,从而区分 了正文页面,避免重复内容,完全达到了SEO的要求。

[转载]系统设计与架构笔记:键值对在架构设计里的应用

mikel阅读(746)

[转载]系统设计与架构笔记:键值对在架构设计里的应用 – 夏天的森林 – 博客园.

系统设计与架构笔记:键值对在架构设计里的应用

  1. 谈谈我对程序的理解

作为程序员你对程序是如何理解的?写这篇文章的时候,我认真思考了下,发现我对程序的理解不是和教科书一样的,我每次听到程序二字我想到的只有两个东西:代码和数据,而每次写程序的时候也就是写代码操作数据的过程。

程序开发和做菜很像,数据就是食材,代码就是厨艺,做出的软件就是一道菜了,至于这个菜好不好吃,到底是看食材还是看厨艺了?呵呵,当我抛出 这个问题的时候,我的第一反应是菜不好吃当然是手艺不好了,不知道其他童鞋是不是这么想的。认真想下,一道好菜一般都是二者必须兼备,当然不排除某一项突 出也可以达到同样的效果,但这种情况毕竟不是大众化,而是属于少数精英的,做软件也是如此,代码与数据是不可偏废的。这里我要提的是数据。

根据我的经验和知识(分类标准我一直想不太好,所以说是自己的经验和知识),我把数据分为两类:落地数据和不落地数据。

  1. 落地数据:就是被持久化的数据,这种数据一般放在硬盘或是其他的持久化存储设备里,例如:图片、系统日志、在页面上显示的数据以及保存在关系数据库里的数据等等,落地数据一定会有一个固定的载体,他们不会瞬时消失的。
  2. 不落地数据:一般指存储在内存或者是网络传输里的数据,这些数据是瞬时,使用完毕就会消失,例如:我们在浏览器发送给服务器的请求;从数据库读取出来的一直到页面展示前的数据等等。

写过程序的人都知道,程序里对这两种类型数据操作是有很大的不同的。

2.由javaEE的mvc设计模式谈起

java一个很重要的贡献就是推出了mvc设计模式,mvc其实应该按vcm顺序读最好。v及view,主要 是前台展示的页面;c及controller,控制层主要作用是接受前台页面数据,根据数据的不同调用后台不同的业务模型,同时业务模型处理好的数据也要 发送到controller,controller再分配给相应的前台页面;m及model,模型层专门负责操作业务模型。下图很好的表达了mvc的理 念:

随着mvc模式的发展,现在流行的架构:view(视图层)+controller(也叫action,控制层)+service(业务模型层)+DAO(数据访问对象层)+数据库的多层结构,如下图:

红线是用户的请求;蓝线是服务器响应用户的请求。红色和蓝线也代表各层数据传输的流向。

不管是传统的mvc模式还是现在流行的多层架构, 各个逻辑层传输的数据都是不落地数据。

在java项目里,一般我们都是传输javabean,而这些javabean都是程序员根据实际业务需求自己定义的,例如下面一个典型的javabean:

public class User {
    
    private String name = "";
    private String sex = "";
    private String age = "";
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    public String getAge() {
        return age;
    }
    public void setAge(String age) {
        this.age = age;
    }
    
    

}

各个逻辑层传输javabean对象的观念一定会深入很多java程序员的人心,当一个项目开始,程序员都会吭哧吭哧的创建javabean对象, 比如数据库表映射的javabean,在struts1.2里还有页面表达对应的javabean,做了这么多年这样的javabean,现在我就思考, 使用javabean真是最好的选择吗?

3.Nojavabean

我这里借用现在很流行的NoSQL的定义作为我文章第三部分的标题。javabean已经深入很多java程序员心理,甚至是很多程序员的习惯,其实它真的那么好用吗?真的那么包治百病吗?其实不尽然,下面我要列举它的缺点:

1.javabean的文件太多。一个java企业项目,都会使用orm,将数据库映射为java对象,一般一张表对应一个javabean,甚至 有些特殊情况还会归并一些表做一个统一的javabean,如果这个系统有1000张表也就会有不少于1000个javabean,在原始的 struts1.2里面还有和页面表单字段一一对应的javabean,大伙可以想象下,这些毫无技术含量的java类也许会像病毒一样去繁殖。

2.javabean会增强各层之间的耦合度。每个层里运算结束,业务数据大多会被封装成javabean对象,不同的业务封装的javabean 是不同的,mvc本来就是为逻辑层解耦做的设计,而传输介质却是包含业务逻辑,如果碰到业务层级的修改,各个层之间都会有相应的结构性修改,增加了维护的 成本。

3.我们至少会多维护一份数据字典。数据库里的某张表一个字段叫USER_NAME,那么到了javabean里可能就变成了userName,当 然我们在orm会有一个映射模型,但是随着数据一层层传递,离数据库越远的数据,数据的本质也就越模糊,特别会给新手产生一定误解,增加了软件开发和维护 成本。

4.这个是缺点是我的在实际项目里碰到的一个难题,我还没把它总结成一个概括性的缺陷,但我相信会有童鞋碰到类似的问题。我在一个项目里做权限管 理,因为某些数据的敏感性我们要做数据集的权限,比如某些人只能查看当天的数据,还有些人只能看当天数据前10条,另一些人只能看最近一个星期的数据,而 且只有三个字段能让他查看。由于权限管理是在该项目三期的任务,我们的权限设计是在原有的系统上进行开发,而且包含两个不同项目,其中一个项目是使用 javabean做传输介质,另一个我们用了另外的方式,最后这个javabean成了我们的梦魇,为了做一个通用解决方案,我们将用javabean作 为传输介质的项目重构了一下,增加了不少工作量。

5.mvc分层思想,其实是为了让专业的人做专业的事,擅长做页面的做页面,擅长逻辑的做逻辑,按mvc思想项目开发往往是横向分组的,但是实际开发中,没人敢这么分配工作,究其原因就是层与层直接传输的介质并不统一。

既然我的标题是Nojavabean,not only javabean,那么就像NoSQL能弥足SQL数据库的不足,以上的不足我们用什么技术来弥补了?

答案就是:键值对。

4.万能的MAP(键值对)

google的三篇论文带来了云时代,用博大的胸怀改变了世界,而这里面最关键的东西就是map(键值对),key-value模式(后面简称 kv)+数组我个人觉得基本可以表述这个世界的90%了。而且基本所有的语言都支持kv的数据结构,这件kv是多么强大的数据结构,世界就是被这个简单方 式而改变的。(本来我想谈谈kv在云计算存储和计算的作用,作为本文主题的补充,但是时间限制,大伙可以自己查阅下相关资料)。

我的替代数据模型就是用map作为数据传输介质,让我产生这个想法的源头是三个技术:mapreduce,ibatis和json;我们中心很重视 hadoop技术,我们这里经常做hadoop相关技术分享,听来听去都有什么mapreduce,都是map,map;而我们现在公司的项目都是用 ibatis做orm,当时表很多,我一时偷懒不是所有的ibatis配置文件都和javabean一一对应,只是返回一个map对象,如果是列表查询就 是List<Map>,没想这么做居然有意外的收获,因为页面数据我们都是封装成json对象,json其实也是一个键值对的模型,数据到了 action也是被传化成了map,所有返回值一样,居然提升了整个开发的效率,而且项目里面少了javabean对象,显的清爽多了。

下面我总结下map的好处,在文章第三部分写道了javabean的缺点,这些缺点倒过来看就是map的优点了,我这里就不再复述了:

1.不管什么技术从map取值的方式是统一的:例如在JavaScript里面json:

obj.XXX或者obj[XXX]

java里的map:

map.get(XXX)或map.put(XXX)

map取值方式是用key,key是字符串,是可以随时定义,而javabean是通过定义的方法读取,如果使用map会将传输数据的业务属性封闭在map内部,这样带来了操作的方便。

2.map技术几乎在所有的主流技术里面都有,那么用这种数据结构作为传输介质是跨平台的

3.map数据结构算法很成熟,做复杂计算是十分方便。

ok,写道这里吧,还有很多东西没讲清楚,写的太累了,明天还要起早赶火车,我的这个想法抛出来就是想听听大伙的意见,现在有点着迷map了,不知道将我们把各个逻辑层用map这种数据结构做传输是不是会带来更多的开发便利,很希望听到大家的意见。

[转载]Eclipse下的项目管理插件介绍

mikel阅读(1069)

[转载]Eclipse下的项目管理插件介绍 – youxiachai – 博客园.

前言

最近,一直学习开源项目的代码,难免会碰到各种版本管理器,作为一名在校大学生,学校的课程完全没提及到,靠着自己的摸索,走了不少弯路,写个列表,望对大家有所帮助.顺便提一下,这里只说客户端,至于服务端,自行根据提供的资料学习

CVS

这里eclipse自带就有,也有一段历史,现在的开源项目很少用的,这里就不做介绍了.

SVN

Eclipse插件:Subclipse:http://subclipse.tigris.org/

下载

Eclipse update site URL: http://subclipse.tigris.org/update_1.6.x
Zipped downloads: http://subclipse.tigris.org/servlets/ProjectDocumentList?folderID=2240

svn是作为cvs的接班人出现,这也是为什么使用cvs的开源项目越来越少的原因,看了一下介绍,安全性得到了显著的提升,还有

相对于的cvs采用了分支管理系统.

特性

  • 统一的版本号。CVS是对每个文件顺序编排版本号,在某一时间各文件的版本号各不相同。而Subversion下,任何一次提交都会对所有文件增 加到同一个新版本号,即使是提交并不涉及的文件。所以,各文件在某任意时间的版本号是相同的。版本号相同的文件构成软件的一个版本。
  • 原子提交。一次提交不管是单个还是多个文件,都是作为一个整体提交的。在这当中发生的意外例如传输中断,不会引起数据库的不完整和数据损坏。
  • 重命名、复制、删除文件等动作都保存在版本历史记录当中。
  • 对于二进制文件,使用了节省空间的保存方法。(简单的理解,就是只保存和上一版本不同之处)
  • 目录也有版本历史。整个目录树可以被移动或者复制,操作很简单,而且能够保留全部版本记录。
  • 分支的开销非常小。
  • 优化过的数据库访问,使得一些操作不必访问数据库就可以做到。这样减少了很多不必要的和数据库主机之间的网络流量。
  • 支持元数据(Metadata)管理。每个目录或文件都可以定义属性(Property),它是一些隐藏的键值对,用户可以自定义属性内容,而且属性和文件内容一样在版本控制范围内。
  • 支持FSFSBerkeley DB两种资料库格式。

Mercurial

Eclipse插件:MercurialEclipse:http://www.javaforge.com/project/HGE

下载

Eclipse update site URL: http://cbes.javaforge.com/update

Mercurial 是一种轻量级分布式版本控制系统,采用 Python 语言实现,易于学习和使用,扩展性强.

特性

Mercurial采用SHA-1散列算法来识别修订版本。Mercurial使用一个基于HTTP的协议来接入网络中的版本库,旨在减少往返的提交、连接数和数据传输。Mercurial也可以工作在ssh环境下,其协议和基于HTTP的协议非常相似。

这个是通用官方使用教程

http://hgbook.red-bean.com/read/

Git

因为,前一段时间想跑去下载Android源码库,就碰到这么一个工具.可惜…Android源码库的官网还没修护好…

Eclipse插件:Egit:http://www.vogella.de/articles/EGit/article.html

下载:

Eclipse update site URL:http://download.eclipse.org/egit/updates-nightly

以下是来至于维基百科的介绍:

Git — The stupid content tracker, 傻瓜内容跟踪器。Linux是这样给我们介绍 Git 的。

Git 是用于 Linux 内核开发的版本控制工具。与常用的版本控制工具 CVS, Subversion 等不同,它采用了分布式版本库的方式,不必服务器端软件支持,使源代码的发布和交流极其方便。 Git 的速度很快,这对于诸如 Linux kernel 这样的大项目来说自然很重要。 Git 最为出色的是它的合并跟踪(merge tracing)能力。

实际上内核开发团队决定开始开发和使用 Git 来作为内核开发的版本控制系统的时候,世界开源社群的反对声音不少,最大的理由是 Git 太艰涩难懂,从 Git 的内部工作机制来说,的确是这样。但是随着开发的深入,Git 的正常使用都由一些友好的脚本命令来执行,使 Git 变得非常好用,即使是用来管理我们自己的开发项目,Git 都是一个友好,有力的工具。现在,越来越多的著名项目采用 Git 来管理项目开发,例如:wine, U-boot[2]

作为开源自由原教旨主义项目,Git 没有对版本库的浏览和修改做任何的权限限制,通过其他工具也可以达到有限的权限控制,比如:gitosis, CodeBeamer MR。原本 Git的使用范围只适用于 Linux / Unix 平台,但逐步并成熟了在 Windows 平台下的使用,主要归功于Cygwinmsysgit环境与TortoiseGit这样易用的 GUI 工具。其实 Git 的源代码中已经加入了对 Cygwin 与 MinGW 编译环境的支持并被逐步完善,对于 Windows 使用者是个福音。

这个是通用教程,当然,我们用eclipse插件就不要这么麻烦了…

http://www.ibm.com/developerworks/cn/linux/l-git/

以上的svn,mercurial,git, codeGoogle 都支持,可以选择喜欢自己使用的插件创建自己的项目库

Eclispse在线插件安装

Help 下

image

image

把下载用网址贴进去就行

image

[转载]Asp.net Mvc 3 加载cshtml文件创建自定义分页

mikel阅读(840)

[转载]老生常谈:Asp.net Mvc 3 加载cshtml文件创建自定义分页 – BirchLee – 博客园.

思路:通过拓展HtmlHelper加载Mvc3的模板页面或者Mvc2中的ascx用户控件实现分页布局和样式的自定义。

实现思路: PageModel 页码类包含:

PageIndex 页码索引

PageText 页码显示文本

Pager 分页控件类

PagerTemp 分页控件加载cshtml或者ascx名称

PagerID 分页控件ID

PagerShow 是否显示分页

PageSize 每页显示的记录数目

CurPage  当前页的索引

PageNum 最少显示的页码数目[偏移量]

Totalpage 总页数

TotalSize 总记录数据

List<PageModel> 页码列表

ControllerName  控制器名称

ActionName   动作名称

PageHelper 分页控件生成类,拓展HtmlHelper用于创建分页控件

public static MvcHtmlString Pager(this HtmlHelper helper,
string pagerId, //分页控件Id
int curPage, //当前页码
int totalSize, //总记录数
string pagerTemp = “_PagerTemp”, //分页控件模板
int pageSize = 10, //每页显示10条
int pageNum = 5 //显示的页码数目
){

//创建分页控件方法

}

_PageTemp  分页控件显示的模板

类似博客园效果:

代码如下:点击下载

[转载]C#正则表达式匹配HTML中的图片路径,图片地址

mikel阅读(883)

[转载]C#正则表达式匹配HTML中的图片路径,图片地址 – 孟晨 – 博客园.

最近的项目中有个关于网页取图的功能需要我自己开发,那就是用正则表达式来匹配图片标签。
一般来说一个 HTML 文档有很多标签,比如“<html>”、“<body>”、“<table>”等,想把文档中的 img 标签提取出来并不是一件容易的事。由于 img 标签样式变化多端,使提取的时候用程序寻找并不容易。于是想要寻找它们就必须写一个非常健全的正则表达式,不然有可能会找得不全,或者找出来的不是正确的 img 标签
我们可以从 HTML 标签的格式去想应该怎么建这个正则表达式。首先要想一下 img 标签有几种写法,忽略大小写不看的话,下面列出 img 标签可能出现的几种情况。
<img> <img/> <img src=/>
这一些标签不用考虑,因为没有图片资源地址。

<img src = /images/pic.jpg/ > <img src =” /images/pic.jpg” > <img src= ‘/images/pic.jpg ‘ / >
这 一些标签都有图片资源地址,另外还有一个特点就是有引号对,可能为单引号,也可能为双引号。因为不需要同时匹配引号对,所以正则表达式可以这么 写:@”<img\s*src\s*=\s*[“”‘]?\s*(?[^\s””‘<>]*)\s*/?\s*>”

<img width=”320″ height=”240″ src=/images/pic.jpg onclick=”window.open(‘/images/pic.jpg’)”>
因为 img 和 src 之间可能会有其他的参数,所以“<img”要有个单词结束,比如说不能是“<imgabc”,同样 src 前面也是一样,使用单词结束符“\b”有一个好处就是省去了表示空格的“\s*”。另外由于 img 标签中不可以出现“<”、“>”这样的符号,所以要改写前面的正则表达式:@”<img\b[^<>]*?\bsrc\s*=\s*[“”‘]?\s*(?<imgUrl>[^\s””‘<>]*)[^<>]*?/?\s*>”

<img width=”320″ height=”240″ src = “
/images/pic.jpg” />
像这种可能会用回车符折行的问题有时候会出现,所以在有空格分开的地方要包含回车换行和 TAB 字符,另外在图片地址中不能出现空格、TAB、回车和换行字符。所以上面的正则表达式可 以改成:@”<img\b[^<>]*?\bsrc[\s\t\r\n]*=[\s\t\r\n]*[“”‘]?[\s\t\r \n]*(?<imgUrl>[^\s\t\r\n””‘<>]*)[^<>]*?/?[\s\t\r\n]*>”

下面写出取得HTML中所有图片地址的静态方法。

     /// <summary>
        /// 取得HTML中所有图片的 URL。
        /// </summary>
        /// <param name="sHtmlText">HTML代码</param>
        /// <returns>图片的URL列表</returns>
        public static string[] GetHtmlImageUrlList(string sHtmlText)
        {
            // 定义正则表达式用来匹配 img 标签
            Regex regImg = new Regex(@"<img\b&#91;^<>]*?\bsrc[\s\t\r\n]*=[\s\t\r\n]*[""']?[\s\t\r\n]*(?<imgUrl>[^\s\t\r\n""'<>]*)[^<>]*?/?[\s\t\r\n]*>", RegexOptions.IgnoreCase);

            // 搜索匹配的字符串
            MatchCollection matches = regImg.Matches(sHtmlText);

            int i = 0;
            string[] sUrlList = new string[matches.Count];

            // 取得匹配项列表
            foreach (Match match in matches)
                sUrlList[i++] = match.Groups["imgUrl"].Value;

            return sUrlList;
        }