[转载]ASP.NET MVC是如何运行的(4): Action的执行

mikel阅读(977)

[转载]ASP.NET MVC是如何运行的(4): Action的执行 – Artech – 博客园.

作为Controller基类ControllerBase的Execute方法的核心在于对Action方法的执行和作为方法返回的ActionResult的执行,两者的执行是通过一个叫做ActionInvoker的组件来完成的。

一、ActionInvoker

我 们同样为ActionInvoker定义了一个接口IActionInvoker。如下面的代码片断所示,该接口定义了一个唯一的方法 InvokeAction用于执行指定名称的Action方法,该方法的第一个参数是一个表示基于当前Controller上下文的 ControllerContext对象。

 1: public interface IActionInvoker
 2: {
 3:     void InvokeAction(ControllerContext controllerContext, string actionName);
 4: }

ControllerContext类型在真正的ASP.NET MVC框架中要负责一些,在这里我们对它进行了简化,仅仅将它表示成对当前Controller和请求上下文的封装,而这两个要素分别通过如下所示的 Controller和RequestContext属性表示。

 1: public class ControllerContext
 2: {
 3:     public ControllerBase Controller { get; set; }
 4:     public RequestContext RequestContext { get; set; }
 5: }

ControllerBase中表示ActionInvoker的同名属性在构造函数中被初始化。在Execute方法中,通过作为方法参数的 RequestContext对象创建ControllerContext对象,并通过包含在RequestContext中的RouteData得到目 标Action的名称,然后将这两者作为参数调用ActionInvoker的InvokeAction方法。

从前面给出的关于ControllerBase的定义我们可以看到在构造函数中默认创建的ActionInvoker是一个类型为 ControllerActionInvoker的对象。如下所示的代码片断反映了整个ControllerActionInvoker的定义,而 InvokeAction方法的目的在于实现针对Action方法的执行。由于Action方法具有相应的参数,在执行Action方法之前必须进行参数 的绑定。ASP.NET MVC将这个机制成为Model的绑定,而这又涉及到另一个重要的组件ModelBinder。

 1: public class ControllerActionInvoker: IActionInvoker
 2: {
 3:     public IModelBinder ModelBinder { get; private set; }
 4:     public ControllerActionInvoker()
 5:     {
 6:         this.ModelBinder = new DefaultModelBinder();
 7:     }
 8:     public void InvokeAction(ControllerContext controllerContext, string actionName)
 9:     {
 10:         MethodInfo method = controllerContext.Controller.GetType().GetMethod(actionName);
 11:         List<object> parameters = new List<object>();
 12:         foreach (ParameterInfo parameter in method.GetParameters())
 13:         {
 14:                 parameters.Add(this.ModelBinder.BindModel(controllerContext,parameter.Name,parameter.ParameterType));
 15:         }
 16:         ActionResult actionResult = method.Invoke(controllerContext.Controller,parameters.ToArray()) as ActionResult;
 17:         actionResult.ExecuteResult(controllerContext);
 18:     }
 19: }

 

二、ModelBinder

我们为ModelBinder提供了一个如下一个简单的定义,这与在真正的ASP.NET MVC中的同名接口的定义不尽相同。该接口具有唯一的BindModel根据ControllerContext和Model名称(在这里实际上是参数名 称)和类型得到一个作为参数的对象。

 1: public interface IModelBinder
 2: {
 3: object BindModel(ControllerContext controllerContext, string modelName, Type modelType);
 4: }

通过前面给出的关于ControllerActionInvoker的定义我们可以看到在构造函数中默认创建的ModelBinder对象是一个 DefaultModelBinder对象。由于仅仅是对ASP.NET MVC的模拟,定义在自定义的DefaultModelBinder中的Model绑定逻辑比ASP.NET MVC中同名类型中实现的要简单得多。

如下面的代码片断所示,绑定到参数上的数据具有三个来源:HTTP-POST Form、RouteData和Values和DataTokens,它们都是字典结构的数据集合。如果参数类型为字符串或者简单的值类型,我们直接根据 参数名称和Key进行匹配;对于复杂类型(比如之前例子中定义的包含Contrller和Action名称的数据类型SimpleModel),则通过反 射根据类型创建新的对象并根据属性名称与Key的匹配关系对相应的属性进行赋值。

 1: public class DefaultModelBinder : IModelBinder
 2: {
 3:     public object BindModel(ControllerContext controllerContext, string modelName, Type modelType)
 4:     {
 5:         if (modelType.IsValueType || typeof(string) == modelType)
 6:         {
 7:             object instance;
 8:             if (GetValueTypeInstance(controllerContext, modelName, modelType, out instance))
 9:             {
 10:                 return instance;
 11:             };
 12:             return Activator.CreateInstance(modelType);
 13:         }
 14:         object modelInstance = Activator.CreateInstance(modelType);
 15:         foreach (PropertyInfo property in modelType.GetProperties())
 16:         {
 17:             if (!property.CanWrite || (!property.PropertyType.IsValueType && property.PropertyType!= typeof(string)))
 18:             {
 19:                 continue;
 20:             }
 21:             object propertyValue;
 22:             if (GetValueTypeInstance(controllerContext, property.Name, property.PropertyType, out propertyValue))
 23:             {
 24:                 property.SetValue(modelInstance, propertyValue, null);
 25:             }
 26:         }
 27:         return modelInstance;
 28:     }
 29:     private  bool GetValueTypeInstance(ControllerContext controllerContext, string modelName, Type modelType, out object value)
 30:     {
 31:         var form = HttpContext.Current.Request.Form;
 32:         string key;
 33:         if (null != form)
 34:         {
 35:             key = form.AllKeys.FirstOrDefault(k => string.Compare(k, modelName, true) == 0);
 36:             if (key != null)
 37:             {
 38:                 value =  Convert.ChangeType(form[key], modelType);
 39:                 return true;
 40:             }
 41:         }
 42:
 43:         key = controllerContext.RequestContext.RouteData.Values
 44:             .Where(item => string.Compare(item.Key, modelName, true) == 0)
 45:             .Select(item => item.Key).FirstOrDefault();
 46:         if (null != key)
 47:         {
 48:             value = Convert.ChangeType(controllerContext.RequestContext.RouteData.Values[key], modelType);
 49:             return true;
 50:         }
 51:
 52:         key = controllerContext.RequestContext.RouteData.DataTokens
 53:             .Where(item => string.Compare(item.Key, modelName, true) == 0)
 54:             .Select(item => item.Key).FirstOrDefault();
 55:         if (null != key)
 56:         {
 57:             value = Convert.ChangeType(controllerContext.RequestContext.RouteData.DataTokens[key], modelType);
 58:             return true;
 59:         }
 60:         value = null;
 61:         return false;
 62:     }
 63: }

在ControllerActionInvoker的InvokeAction方法中,我们直接将传入的Action名称作为方法名从 Controller类型中得到表示Action操作的MethodInfo对象。然后遍历MethodInfo的参数列表,对于每一个 ParameterInfo对象,我们将它的Name和ParameterType属性表示的参数名称和类型连同创建ControllerContext 作为参数调用ModelBinder的BindModel方法并得到对应的参数值。最后通过反射的方式传入参数列表并执行MethodInfo。和真正的 ASP.NET MVC一样,定义在Contrller的Action方法返回一个ActionResult对象,我们通过指定它的Execute方法是先对请求的响应。

三、ActionResult

我们为具体的ActionResult定义了一个ActionResult抽象基类。如下面的代码片断所示,该抽象类具有一个参数类型为ControllerContext的抽象方法ExecuteResult,我们最终对请求的响应就实现在这里。

 1: public abstract class ActionResult
 2: {
 3:     public abstract void ExecuteResult(ControllerContext context);
 4: }

在之前创建的例子中,Action方法返回的是一个类型为RawContentResult的对象。顾名思义,RawContentResult将初始化时指定的内容(字符串)原封不动地写入针对当前请求的HTTP回复中,具体的实现如下所示。

 1: public class RawContentResult: ActionResult
 2: {
 3:     public string RawData { get; private set; }
 4:     public RawContentResult(string rawData)
 5:     {
 6:         RawData = rawData;
 7:     }
 8:     public override void ExecuteResult(ControllerContext context)
 9:     {
 10:         context.RequestContext.HttpContext.Response.Write(this.RawData);
 11:     }
 12: }

[转载]Asp.Net下FCKeditor配置图片上传最简单的方法

mikel阅读(1056)

[转载]Asp.Net下FCKeditor配置图片上传最简单的方法 – Asp.Net – 拼吾爱程序人生.

FCKeditor的基本配置我就不讲了,讲讲图片上传这块吧;

1. 原先的配置

把FCKeditor/filemanager/connectors 目录删除;

有同学可能会问了,都删除了怎么上传文件?

呵呵。。。

2. 不要引用 FredCK.FCKeditorV2.dll;

因为我都是采用js写的,不采用控件的方式;

其实网上有很多人在尝试往 FCKeditor/filemanager/connectors目录下注入,

确实也有人不小心直接把FCK编辑器没有任何配置的情况下传到网上导致中招了;

3. 现在讲正题吧,一般来说我们用FCK的时候并不多,在一个系统里面可能也就几个地方

现在假如我有一个简单的系统,里面有“新闻”发布和,类似“公司简介”单页发布两个地方要用的FCK

我要把所有文章的图片放到 /Article/yyyyMMdd/guid.jpg

把所有单页的图片放到 /Page/yyyyMMdd/guid.jpg

或者说我要把图片按用户存到不同的地方。。。

4.实现

怎么很简单的事情要说清楚需要做这么多铺垫,太麻烦了。。。。!!!

web页面:

<form id="form1" runat="server">
    <div>
        <div>
            <asp:TextBox ID="TextBox1" TextMode="MultiLine" runat="server"> </asp:TextBox>
        </div>
        <br />
        <asp:Button ID="Button1" runat="server" Text="Button" onclick="Button1_Click" />
    </div>
    </form>

JS:

    <script src="fckeditor/fckeditor.js" type="text/javascript"></script>

    <script type="text/javascript">

        window.onload = function() {
            var oFCKeditor = new FCKeditor('<%= TextBox1.ClientID %>');
            oFCKeditor.BasePath = 'fckeditor/'
            oFCKeditor.Config.ImageUploadURL = "/admin/auploader.aspx";
            oFCKeditor.ReplaceTextarea();
        }
    </script>

这样就搞定了!

不要不相信啊,下面把上传页面的代码简单的写一个吧,对这个也比较重要;

protected void Page_Load(object sender, EventArgs e)
    {
        if (Request.Files.Count > 0)
        {
            HttpPostedFile file = Request.Files[0];
            string path = "/Article/" + System.DateTime.Now.ToString("yyyyMMdd") + "/";
            string serverPath = Server.MapPath(path);
            string fileName = Guid.NewGuid() + file.FileName.Substring(file.FileName.LastIndexOf("."));

            if (!System.IO.Directory.Exists(serverPath))
                System.IO.Directory.CreateDirectory(serverPath);
            file.SaveAs(serverPath + fileName);
            SendFileUploadResponse(0, path + fileName, fileName, "上传成功!");
        }
        else
        {
            SendFileUploadResponse(1, "", "", "上传失败!");
        }
    }


    public void SendFileUploadResponse(int isSucceed, string fileUrl, string fileName, string customMsg)
    {
        System.Web.HttpContext.Current.Response.Clear();
        System.Web.HttpContext.Current.Response.Write("<script type='text/javascript'>");
        System.Web.HttpContext.Current.Response.Write(@"(function(){var d=document.domain;while (true){try{var A=window.top.opener.document.domain;break;}catch(e) {};d=d.replace(/.*?(?:\.|$)/,'');if (d.length==0) break;try{document.domain=d;}catch (e){break;}}})();");
        System.Web.HttpContext.Current.Response.Write("window.parent.OnUploadCompleted(" + isSucceed.ToString().ToLower() + ", '" + fileUrl + "', '" + fileName + "', '" + customMsg + "');");
        System.Web.HttpContext.Current.Response.Write("</script>");
        System.Web.HttpContext.Current.Response.End();
    }

好了搞定了!

这个上传代码没有做严格的后缀限制,大小限制;因为这个页面路径是我们自己配置的嘛,想怎么写就怎么写了;

甚至你可以根据不同的用户存到不同的地方;

这里需要说明一下 SendFileUploadResponse 方法,其实就是为FCk传回消息;

这里:

System.Web.HttpContext.Current.Response.Write("window.parent.OnUploadCompleted(" + isSucceed.ToString().ToLower() + ", '" + fileUrl + "', '" + fileName + "', '" + customMsg + "');");

对应的是
fckeditor/dialog/fck_image/fck_image.js

function OnUploadCompleted( errorNumber, fileUrl, fileName, customMsg )
{
    // Remove animation
    window.parent.Throbber.Hide() ;
    GetE( 'divUpload' ).style.display  = '' ;

    switch ( errorNumber )
    {
        case 0 :    // No errors
            alert( 'Your file has been successfully uploaded' ) ;
            break ;
        case 1 :    // Custom error
            alert( customMsg ) ;
            return ;
        case 101 :    // Custom warning
            alert( customMsg ) ;
            break ;
        case 201 :
            alert( 'A file with the same name is already available. The uploaded file has been renamed to "' + fileName + '"' ) ;
            break ;
        case 202 :
            alert( 'Invalid file type' ) ;
            return ;
        case 203 :
            alert( "Security error. You probably don't have enough permissions to upload. Please check your server." ) ;
            return ;
        case 500 :
            alert( 'The connector is disabled' ) ;
            break ;
        default :
            alert( 'Error on file upload. Error number: ' + errorNumber ) ;
            return ;
    }

    sActualBrowser = '' ;
    SetUrl( fileUrl ) ;
    GetE('frmUpload').reset() ;
}

注意到 0 ,是成功,1 是失败,其他的自己看吧。。。。

对了说明一下这里用的是 2.65 版本测试的,如果有其他版本不一样的灵活变通下哈;

总结一下:

1. 在不同的页面配置不同的图片处理路径,如:

     oFCKeditor.Config.ImageUploadURL = "/admin/aupload.aspx";

     oFCKeditor.Config.ImageUploadURL = "/admin/bupload.aspx";

2. FCk 会把图片post到指定的路径,

然后你自己操作图片,按目录存啊还是按当前用户session存随便来;

当然别忘记验证用户的身份在先,没有权限的不要对他客气,直接返回错误,

验证身份示例代码里面没写,自己根据项目自己加吧

3. 一定要返回值告诉FCK你的操作结果,否则会一直死在那里的。。。

[转载]SQL Server2012中的SequenceNumber尝试

mikel阅读(1037)

[转载]SQL Server2012中的SequenceNumber尝试 – CareySon – 博客园.

简介

SequenceNumber是SQL Server2012推出的一个新特性。这个特性允许数据库级别的序列号在多表或多列之间共享。对于某些场景会非常有用,比如,你需要在多个表之间公用一 个流水号。以往的做法是额外建立一个表,然后存储流水号。而新插入的流水号需要两个步骤:

1.查询表中流水号的最大值

2.插入新值(最大值+1)

现在,利用SQL Server2012中的Sequence.这类操作将会变得非常容易。

 

SequenceNumber的基本概念

SequenceNumber的概念并不是一个新概念,Oracle早就已经实现了(http://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_6015.htm)。与以往的Identity列不同的是。SequenceNumber是一个与构架绑定的数据库级别的对象,而不是与具体的表的具体列所绑定。这意味着SequenceNumber带来多表之间共享序列号的遍历之外,还会带来如下不利影响:

  •     与Identity列不同的是,Sequence插入表中的序列号可以被Update,除非通过触发器来进行保护
  •     与Identity列不同,Sequence有可能插入重复值(对于循环SequenceNumber来说)
  •      Sequence仅仅负责产生序列号,并不负责控制如何使用序列号,因此当生成一个序列号被Rollback之后,Sequence会继续生成下一个号,从而在序列号之间产生间隙。

 

SequenceNumber的用法

SequenceNumber在MSDN中定义的原型如代码1所示。

CREATE SEQUENCE [schema_name . ] sequence_name
    [ AS [ built_in_integer_type | user-defined_integer_type ] ]
    [ START WITH <constant> ]
    [ INCREMENT BY <constant> ]
    [ { MINVALUE [ <constant> ] } | { NO MINVALUE } ]
    [ { MAXVALUE [ <constant> ] } | { NO MAXVALUE } ]
    [ CYCLE | { NO CYCLE } ]
    [ { CACHE [ <constant> ] } | { NO CACHE } ]
    [ ; ]

代码1.Sequence的创建原型

 

由代码1看以看到,参数相对比较简单。从指定数据类型(INT兼容)到开始计数点,步长,最大值和最小值,是否循环和是否缓存几个参数来设置Sequence。

下面图1创建了一个简单的Sequence。

1

图1.创建一个简单的Sequence并进行使用

 

此时,我们可以通过SQL Server 2012新增的视图sys.sequences来看到刚才创建成功的Sequence,如图2所示.

2

图2.sys.sequences视图

 

当然我们可以这个序列按照顺序插入表,如图3所示。

3

图3.在单表中插入序列

 

而SequenceNumber最重要的功能是在多表间共享序列号,如图4所示。

4

图4.多表之间利用Sequence共享序列号

前面图2可以看到,如果我们不指定Sequence的上限和下限,则默认使用所指定数据类型的最大值和最小值作为上限和下限(如图2INT类型的的上下限).当达到上线后,可以指定循环来让Sequence达到上限后从指定的开始值重新开始循环。如图5所示。

5

图5.Sequence设置上限下限和循环

 

还可以通过修改Sequence将其初始值指定为一个特定值,如图6所示。

6

图6.重置Sequence的值

 

Sequence一个需要注意的情况是Sequence只负责生成序列号,而不管序列号如何使用,如果事务不成功或回滚,SequenceNumber仍然会继续向后生成序列号,如图7所示。

7

图7.Sequence仅仅负责生成序列号

 

我们还可以为Sequence指定缓存选项,使得减少IO,比如,我们指定Cache选项为4,则当前的Sequence由1增长过4后,SQL Server会再分配4个空间变为从5到8,当分配到9时,SQL Server继续这以循环,如果不指定Cache值,则值由SQL Server进行分配。一个简单的例子如图8所示。

8

图8.为Sequence设置Cache选项

 

 

总结

本文讲述了SequenceNumber的简单用法。Sequence是一个比较方便的功能,如果使用妥当,将会大大减少开发工作和提升性能。

 

参考资料:Sequence Numbers

CREATE SEQUENCE (Transact-SQL)

[转载]ASP.NET MVC是如何运行的(3): Controller的激活

mikel阅读(1046)

[转载]ASP.NET MVC是如何运行的(3): Controller的激活 – Artech – 博客园.

ASP.NET MVC的URL路由系统通过注册的路由表对HTTP请求进行解析从而得到一个用于封装路由数据的RouteData对象,而这个过程是通过自定义的 UrlRoutingModule对HttpApplication的PostResolveRequestCache事件进行注册实现的。 RouteData中已经包含了目标Controller的名称,现在我们来进一步分析真正的Controller对象是如何被激活的。我们首先需要了解 一个类型为MvcRouteHandler的类型。

一、MvcRouteHandler

通 过前面的介绍我们知道继承自RouteBase的Route类型具有一个类型为IRouteHandler接口的属性RouteHandler,它主要的 用途就是用于根据指定的请求上下文(通过一个RequestContext对象表示)来获取一个HttpHandler对象。当GetRouteData 方法被执行后,Route的RouteHandler属性值将反映在得到的RouteData的同名属性上。在默认的情况下,Route的 RouteHandler属性是一个MvcRouteHandler对象,如下的代码片断反映了这一点。

 1: public class Route : RouteBase
 2: {
 3:     //其他成员
 4:     public IRouteHandler RouteHandler { get; set; }
 5:     public Route()
 6:     {
 7:         //其他操作
 8:         this.RouteHandler = new MvcRouteHandler();
 9:     }
 10: }

对于我们这个“迷你版”的ASP.NET MVC框架来说,MvcRouteHandler是一个具有如下定义的类型。在实现的GetHttpHandler方法中,它直接返回一个MvcHandler对象。

 1: public class MvcRouteHandler: IRouteHandler
 2: {
 3:     public IHttpHandler GetHttpHandler(RequestContext requestContext)
 4:     {
 5:         return new MvcHandler(requestContext);
 6:     }
 7: }

 

二、MvcHandler

在前面的内容中我们已经提到整个ASP.NET MVC框架是通过自定义的HttpModule和HttpHandler对象ASP.NET进行扩展实现的。这个自定义HttpModule我们已经介绍 过了,就是UrlRoutingModule,而这个自定义的HttpHandler则是我们要重点介绍的MvcHandler。

UrlRoutingModule在通过路由表解析HTTP请求得到一个用于封装路由数据的RouteData后,或调用其 RouteHandler的GetHttpHandler方法得到HttpHandler对象并注册到当前的HTTP上下文。由于RouteData的 RouteHandler来源于对应Route对象的RouteHandler,而后者在默认的情况下是一个MvcRouteHandler对象,所以默 认情况下用于处理HTTP请求的就是这么一个MvcHandler对象。MvcHandler实现了对Controller对象的激活和对相应 Action方法的执行。

下面的的代码片断体现了MvcHandler的整个定义,它具有一个类型为RequestContext的属性表示被处理的当前请求上下文,该属性在构造函数指定。在实现的ProcessRequest中实现了对Controller对象的激活和执行。

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

 

三、Controller与ContrllerFactory

我们为Controller定义了一个接口IController,如下面的代码片断所示,该接口具有唯一的方法Execute在 MvcHandler的ProcessRequest方法中被执行,而传入该方法的参数时表示当前请求上下文的RequestContext对象。

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

从MvcHandler的定义我们可以看到Controller对象的激活是通过工厂模式实现的,我们为Controller工厂定义了一个具有如 下定义的IControllerFactory接口。IControllerFactory通过CreateController方法根据传入的请求上下 文和Controller的名称来激活相应的Controller对象。

 1: public interface IControllerFactory
 2: {
 3: IController CreateController(RequestContext requestContext, string controllerName);
 4: }

在MvcHandler的ProcessRequest方法中,它通过ControllerBuilder的静态属性Current得到当前的 ControllerBuilder对象,并调用GetControllerFactory方法获得当前的ControllerFactory。然后通过 从自己的RequestContext中提取的RouteData获得Controller的名称,最后将它连同RequestContext一起作为 ContollerFactory的CreateController方法的参数进而创建具体的Controller对象。

ControllerBuilder的整个定义如下面的代码片断所示,表示当前ControllerBuilder的静态只读属性的Current 在静态构造函数中被创建。SetControllerFactory和GetControllerFactory方法用于 ContorllerFactory的注册和获取。而类型为HashSet<string>的DefaultNamespaces属性表示默 认的命名空间列表,这是为了最终解析Controller类型的需要。

 1: public class ControllerBuilder
 2: {
 3:     private Func<IControllerFactory> factoryThunk;
 4:     static ControllerBuilder()
 5:     {
 6:         Current = new ControllerBuilder();
 7:     }
 8:     public ControllerBuilder()
 9:     {
 10:         this.DefaultNamespaces = new HashSet<string>();
 11:     }
 12:     public static ControllerBuilder Current { get; private set; }
 13:     public IControllerFactory GetControllerFactory()
 14:     {
 15:         return factoryThunk();
 16:     }
 17:     public void SetControllerFactory(IControllerFactory controllerFactory)
 18:     {
 19:         factoryThunk = () => controllerFactory;
 20:     }
 21:     public HashSet<string> DefaultNamespaces { get; private set; }
 22: }

在回头看看我们之前建立在我们自定义ASP.NET MVC框架的Web应用,我们就是通过当前的ControllerBuilder进行ControllerFactory的注册和默认命名空间的指定的。 如下面的代码片断所示,我们注册的ControllerFactory的类型为DefaultControllerFactory。

 1: public class Global : System.Web.HttpApplication
 2: {
 3:     protected void Application_Start(object sender, EventArgs e)
 4:     {
 5:         //其他操作
 6:         ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory());
 7:         ControllerBuilder.Current.DefaultNamespaces.Add("WebApp");
 8:     }
 9: }

作为默认ControllerFactory的DefualtControllerFactory类型定义如下。激活Controller类型的前 提是能够正确解析出Controller的真实类型。作为CreateController方法输入参数的controllerName仅仅表示 Controller的名称,我们需要加上Controller字符后缀作为类型名称。此外我们还需要得到类型的命名空间和程序集名称,前者具有两个来 源,即RouteData和当前ControllerBuilder,后者则是直接来源于当前应用程序域目前加载的程序集列表。照理说如果目标 Controller的类型定义在一个尚未加载的程序集中,我们需要手工对其进行加载,这没有反应在我们这个“迷你版”的ASP.NET MVC中。

 1: public class DefaultControllerFactory : IControllerFactory
 2: {
 3:     public IController CreateController(RequestContext requestContext, string controllerName)
 4:     {
 5:         string controllerType = controllerName + "Controller";
 6:         foreach (var ns in requestContext.RouteData.Namespaces)
 7:         {
 8:             string controllerTypeName = string.Format("{0}.{1}", ns, controllerType);
 9:             foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
 10:             {
 11:                 var controllerTypeQName = controllerTypeName + "," + assembly.FullName;
 12:                 var type = Type.GetType(controllerTypeQName);
 13:                 if (null != type)
 14:                 {
 15:                     return (IController)Activator.CreateInstance(type);
 16:                 }
 17:             }
 18:         }
 19:         foreach(var ns in ControllerBuilder.Current.DefaultNamespaces)
 20:         {
 21:             string controllerTypeName = string.Format("{0}.{1}", ns, controllerType);
 22:             foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
 23:             {
 24:                 var controllerTypeQName = controllerTypeName + "," + assembly.FullName;
 25:                 var type = Type.GetType(controllerTypeQName);
 26:                 if (null != type)
 27:                 {
 28:                     return (IController)Activator.CreateInstance(type);
 29:                 }
 30:             }
 31:         }
 32:         return null;
 33:     }
 34: }

上面我们详细地介绍了Controller的激活原理,我们现在讲关注点返回到Controller自身。通过实现IContrller接口,我们 为具有的Controller定义了一个具有如下定义的ControllerBase抽象基类。从中我们可以看到在实现的Execute方法 中,ControllerBase通过一个实现了接口IActionInvoker的对象完成了针对Action方法的执行。

 1: public abstract class ControllerBase: IController
 2: {
 3:     protected IActionInvoker ActionInvoker { get; set; }
 4:     public ControllerBase()
 5:     {
 6:         this.ActionInvoker = new ControllerActionInvoker();
 7:     }
 8:     public void Execute(RequestContext requestContext)
 9:     {
 10:         ControllerContext context = new ControllerContext { RequestContext = requestContext, Controller = this };
 11:         string actionName = requestContext.RouteData.ActionName;
 12:         this.ActionInvoker.InvokeAction(context, actionName);
 13:     }
 14: }

[转载]ASP.NET MVC是如何运行的[2]: URL路由

mikel阅读(1037)

[转载]ASP.NET MVC是如何运行的[2]: URL路由 – Artech – 博客园.

在一个ASP.NET MVC应用来说,针对HTTP请求的处理和相应定义Controller类型的某个Action方法中,每个HTTP请求的目标对象不再像ASP .NET Web Form应用一样是一个物理文件,而是某个Controller的某个Action。目标Controller和Action的名称包含在HTTP请求 中,而ASP.NET MVC的首要任务就是通过当前HTTP请求的解析得到正确的Controller和Action的名称。这个过程是通过ASP.NET MVC的URL路由机制来实现的。

一、RouteData

ASP.NET 定义了一个全局的路由表,路由表中的每个路由对象对应着一个将Controller和Action名称作为站位符的URL模板。对于每一个抵达的HTTP 请求,ASP.NET MVC会遍历路由表找到一个URL模板的模式与请求地址相匹配的路有对象,并最终解析出以Controller和Action名称为核心的路由数据。在我 们自定义的ASP.NET MVC框架中,路由数据通过具有如下定义的RouteData类型表示。

 1: public class RouteData
 2: {
 3:     public IDictionary<string, object> Values { get; private set; }
 4:     public IDictionary<string, object> DataTokens { get; private set; }
 5:     public IRouteHandler RouteHandler { get;  set; }
 6:     public RouteBase Route { get; set; }
 7:
 8:     public RouteData()
 9:     {
 10:         this.Values = new Dictionary<string, object>();
 11:         this.DataTokens = new Dictionary<string, object>();
 12:         this.DataTokens.Add("namespaces", new List<string>());
 13:     }
 14:     public string Controller
 15:     {
 16:         get
 17:         {
 18:             object controllerName = string.Empty;
 19:             this.Values.TryGetValue("controller", out controllerName);
 20:             return controllerName.ToString();
 21:         }
 22:     }
 23:     public string ActionName
 24:     {
 25:         get
 26:         {
 27:             object actionName = string.Empty;
 28:             this.Values.TryGetValue("action", out actionName);
 29:             return actionName.ToString();
 30:         }
 31:     }
 32:     public IEnumerable<string> Namespaces
 33:     {
 34:         get
 35:         {
 36:             return (IEnumerable<string>)this.DataTokens["namespaces"];
 37:         }
 38:     }
 39: }

从上面的代码片断所示,RouteData定义了两个字典类型的属性Values和DataTokens,前者代表直接从请求地址解析出来的变量, 后者代表其他类型的变量。表示Controller和Action名称的同名属性直接从Values字典中提取,对应的Key分别为controller 和action。属性Namespaces表示辅助Controller类型的解析而设置的命名空间列表,该属性值从DataTokens字典中提取,对 应的Key为namespaces。

我们之前已经提到过ASP.NET MVC本质上是两个自定义的ASP.NET组件来实现的,一个是自定义的HttpModule,另一个是自定义的HttpHandler,而后者从 RouteData的RouteHandler属性获得。RouteData的RouteHandler属性类型为IRouteHandler接口,如下 面的代码片断所示,该接口具有一个唯一的GetHttpHandler用于返回真正用于处理HTTP请求的HttpHandler对象。

 1: public interface IRouteHandler
 2: {
 3:     IHttpHandler GetHttpHandler(RequestContext requestContext);
 4: }

IRouteHandler接口的GetHttpHandler方法接受一个类型为RequestContext的参数。顾名思 义,RequestContext表示当前(HTTP)请求的上下文,其核心就是对当前HttpContext和RouteData的封装,这可以通过如 下的代码片断看出来。

 1: public class RequestContext
 2: {
 3:     public virtual HttpContextBase HttpContext { get; set; }
 4:     public virtual RouteData RouteData { get; set; }
 5: }

 

二、Route和RouteTable

RouteData具有一个类型为RouteBase的Route属性,表示当前路由表中与当前请求匹配的路由对象。换句话说,当前的 RouteData就是通过该路由对象针对当前HTTP请求进行解析获得的。RouteBase是一个抽象类,如下面的代码片断所示,它仅仅包含一个 GetRouteData方法,该方法通过对以HttpContextBase对象表示的当前HTTP上下文进行解析从而获取一个RouteData对 象。

 1: public abstract class RouteBase
 2: {
 3:     public abstract RouteData GetRouteData(HttpContextBase httpContext);
 4: }

ASP.NET MVC提供的基于URL模板的路由机制是通过具有如下定义的Route类型实现的。Route是RouteBase的子类,字符串类型的Url属性代表定 义的URL模板 。在实现的GetRouteData方法中,通过HttpContextBase获取相对请求地址,如果该地址与定义在模板中的URL模式相匹配则创建一 个RouteData返回;否则返回Null。对于返回的RouteData对象,其Values属性表示的字典包含直接通过地址解析出来的变量,而对于 DataTokens字典和RouteHandler属性,则直接取自Route对象的同名属性。

 1: public class Route : RouteBase
 2: {
 3:     public IRouteHandler RouteHandler { get; set; }
 4:     public Route()
 5:     {
 6:         this.DataTokens = new Dictionary<string, object>();
 7:         this.RouteHandler = new MvcRouteHandler();
 8:     }
 9:     public override RouteData GetRouteData(HttpContextBase httpContext)
 10:     {
 11:         IDictionary<string, object> variables;
 12:         if (this.Match(httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2), out variables))
 13:         {
 14:             RouteData routeData = new RouteData();
 15:             foreach (var item in variables)
 16:             {
 17:                 routeData.Values.Add(item.Key, item.Value);
 18:             }
 19:             foreach (var item in DataTokens)
 20:             {
 21:                 routeData.DataTokens.Add(item.Key, item.Value);
 22:             }
 23:             routeData.RouteHandler = this.RouteHandler;
 24:             return routeData;
 25:         }
 26:         return null;
 27:     }
 28:     public string Url { get; set; }
 29:     public IDictionary<string, object> DataTokens { get; set; }
 30:     protected bool Match(string requestUrl, out IDictionary<string,object> variables)
 31:     {
 32:         variables = new Dictionary<string,object>();
 33:         string[] strArray1 = requestUrl.Split('/');
 34:         string[] strArray2 = this.Url.Split('/');
 35:         if (strArray1.Length != strArray2.Length)
 36:         {
 37:             return false;
 38:         }
 39:
 40:         for (int i = 0; i < strArray2.Length; i++)
 41:         {
 42:             if(strArray2[i].StartsWith("{") && strArray2[i].EndsWith("}"))
 43:             {
 44:                 variables.Add(strArray2[i].Trim("{}".ToCharArray()),strArray1[i]);
 45:             }
 46:         }
 47:         return true;
 48:     }
 49: }

由于同一个Web应用可以采用多种不同的URL模式,所以也需要注册多个继承自RouteBase的路由对象对它们进行解析,多个路由对象组成了一 个路由表。在我们自定义ASP.NET MVC框架中,路由表通过类型RouteTable表示。如下面的代码片断所示,RouteTable仅仅具有一个类型为RouteDictionary 的Routes属性表示针对真个Web应用的全局路由表。

 1: public class RouteTable
 2: {
 3:     public static RouteDictionary Routes { get; private set; }
 4:     static RouteTable()
 5:     {
 6:         Routes = new RouteDictionary();
 7:     }
 8: }

RouteDictionary表示一个具名的路由对象的列表,我们直接让它继承自Dictionary<string, RouteBase>类型,其中的Key表示注册的路由对象的名称。在GetRouteData方法中,我们遍历集合找到与指定的 HttpContextBase对象匹配的路由对象,并得到对应的RouteData。

 1: public class RouteDictionary: Dictionary<string, RouteBase>
 2: {
 3:     public RouteData GetRouteData(HttpContextBase httpContext)
 4:     {
 5:         foreach (var route in this.Values)
 6:         {
 7:             RouteData routeData = route.GetRouteData(httpContext);
 8:             if (null != routeData)
 9:             {
 10:                 return routeData;
 11:             }
 12:         }
 13:         return null;
 14:     }
 15: }

在Global.asax中我们创建了一个基于指定URL模板的Route对象,并将其添加到通过RouteTable的静态只读属性Routes表示的全局路由表中。

三、UrlRoutingModule

路由表的目的在于对当前的HTTP请求进行解析从而获取一个以Controller和Action名称为核心的路由数据,即上面介绍的 RouteData,而整个解析工作是通过一个类型为UrlRoutingModule的自定义HttpModule来完成的。如下面的代码片断所示,在 实现了接口IHttpModule的UrlRoutingModule类型的Init方法中,我们注册了HttpApplicataion的 PostResolveRequestCache事件。

 1: public class UrlRoutingModule: IHttpModule
 2: {
 3:     public void Dispose()
 4:     {}
 5:     public void Init(HttpApplication context)
 6:     {
 7:         context.PostResolveRequestCache += OnPostResolveRequestCache;
 8:     }
 9:     protected virtual void OnPostResolveRequestCache(object sender, EventArgs e)
 10:     {
 11:         HttpContextWrapper httpContext = new HttpContextWrapper(HttpContext.Current);
 12:         RouteData routeData = RouteTable.Routes.GetRouteData(httpContext);
 13:         if (null == routeData)
 14:         {
 15:             return;
 16:         }
 17:         RequestContext requestContext = new RequestContext { RouteData = routeData, HttpContext = httpContext };
 18:         IHttpHandler handler = routeData.RouteHandler.GetHttpHandler(requestContext);
 19:         httpContext.RemapHandler(handler);
 20:     }
 21: }

当PostResolveRequestCache事件触发之后,UrlRoutingModule通过RouteTable的静态只读属性 Routes得到表示全局路由表的RouteDictionary对象,然后调用其GetRouteData方法并传入用于封装当前 HttpContext的HttpContextWrapper对象(HttpContextWrapper是HttpContextBase的子类)并 得到一个封装了路由数据的RouteData对象。如果得到的RouteData不为空,根据该对象本身和和之前得到的 HttpContextWrapper对象创建一个表示当前请求上下文的RequestContext对象,将其作为参数传入RouteData的 RouteHandler的GetHttpHandler方法得到一个HttpHandler对象。最后我们调用HttpContextWrapper对 象的RemapHandler方法对得到HttpHandler进行映射,这意味着该HttpHandler将最终用于处理当前的HTTP请求。

[转载]ASP.NET MVC是如何运行的[1] 建立在“伪”MVC框架上的Web应用

mikel阅读(960)

[转载]ASP.NET MVC是如何运行的[1]: 建立在“伪”MVC框架上的Web应用 – Artech – 博客园.

ASP.NET由于采用了管道式设计,具有很好的扩展性,而整个ASP.NET MVC应用框架就是通过扩展ASP.NET实现的。通过上面对ASP.NET管道设计的介绍,我们知道ASP.NET的扩展点只要体现在 HttpMoudle和HttpHandler这两个核心组建之上,实际上整个ASP.NET MVC框架就是通过自定义的HttpMoudle(UrlRoutingModule)和HttpHandler(MvcHandler) 实现的。为了上读者从整体上把握ASP.NET MVC的工作机制,接下来我按照其原理通过一些自定义组件来模拟ASP.NET MVC的运行原理,我们也可以将此视为一个“迷你版”的ASP.NET MVC。值得一提的是,为了让读者根据该实例从真正的ASP.NET MVC中找到对应的组件,我完全采用了与ASP.NET MVC一致的类型命名方式。[源代码从这里下载]

在 正式介绍我们自己创建 的“迷你版”ASP.NET MVC的实现原理之前,我们不妨来看看建立在该框架之上的Web应用如何实现。我们通过Visual Studio创建一个空的ASP.NET Web应用(注意不是ASP.NET MVC应用),我们不会引用System.Web.Mvc.dll这个程序集,所以你在接下来的程序中看到的定义在该程序集中的同名类型都是我们自行定义 的。

我们首先定义了如下一个SimpleModel类型,它表示最终需要绑定到View上的数据。简单起见,同时也为了验证针对Controller和Action的解析机制,SimpleModel定义的两个属性分别表示当前请求的目标Controller和Action。

public class SimpleModel
{
public string Controller { get; set; }
public string Action { get; set; }
}

像真正的ASP .NET MVC应用开发一样,我们需要定义Controller类型。如下面的代码片断所示,按照我们熟悉的命名方式(以字符Controller作为后缀),我们定义了如下一个HomeController。HomeController实现的抽象类型ControllerBase是我们自行定义的。以自定义的ActionResult作为返回类型的Index方法表示Controller的Action,它接受一个SimpleModel类型的对象作为参数。该Action方法返回的ActionResult是一个RawContentResult对象,顾名思义,RawContentResult就是将指定的内容进行原样显示。在这里我们将作为参数的SimpleModel对象的Controller和Action属性显示出来。

public class HomeController: ControllerBase

{

public ActionResult Index(SimpleModel model)

{

string content = string.Format("Controller: {0}
Action:{1}", model.Controller, model.Action);

return new RawContentResult(content);

}

}

ASP.NET MVC根据请求地址来解析出用于处理该请求的Controller的类型和Action方法名称。具体来说,我们预注册一些包含Controller和Action名称作为站位符的(相对)地址模板,如果请求地址符合相应地址模板的模式,Controller和Action名称就可以正确地解析出来。和ASP.NET MVC应用类似,我们在Global.asax中注册了如下一个地址模板({controller}/{action})。

public class Global : System.Web.HttpApplication

{

protected void Application_Start(object sender, EventArgs e)

{

RouteTable.Routes.Add("default",new Route{Url = "{controller}/{action}"});

ControllerBuilder.Current.SetControllerFactory(new DefaultControllerFactory());

ControllerBuilder.Current.DefaultNamespaces.Add("WebApp");

}

}

在如上所示的用于进行地址模板注册的Application_Start方法之中,我们还注册了一个用于创建Controller对象的工厂。前面定义的HomeController定义在命名空间WebApp下,由于请求地址中只能解析出Controller类型的名称,我们需要将该命名空间注册为当前ControllerBuilder的默认命名空间。RouteTable、ControllerBuilder和DefaultControllerFactory都是我们自定义的类型。

正如我上面所说,ASP.NET MVC是通过一个自定义的HttpModule实现的,在这个“迷你版”ASP.NET MVC框架中我们也将其起名为UrlRoutingModule。在运行Web应用之前,我们需要通过配置对该自定义HttpModule进行注册,下面是相关的配置。


&nbsp;

&nbsp;

&nbsp;

&nbsp;

&nbsp;

到目前为止,所有的编程和配置工作已经完成。为了 让定义在HomeController中的Action方法Index来处理针对该Web应用的访问请求,我们需要指定与之匹配的地址(符合定义在注册地址模板的URL模式)。如下图所示,由于在浏览器中输入地址(http://…/Home/Index)正好对应着HomeController的Index操作,所以对应的方法会被执行,而执行的结果就是将当前请求的目标Contrller和Action的名称显示出来。

image

上面我们我们演示了如何在我们自己创建的“迷你版”ASP.NET MVC框架中创建一个Web应用,从中我们可以看到和创建一个真正的ASP.NET MVC应用别无二致。接下来我们就来逐步地分析这个自定义的ASP.NET MVC框架是如何建立起来的,而它也代表了真正的ASP.NET MVC框架的工作原理。

[转载]c#使用foreach需要知道的

mikel阅读(1093)

[转载]c#使用foreach需要知道的 – smark – 博客园.

C#中通过foreach遍历一个列表是经常拿用的方法,使用起来也方便,性能上也和for没有多大的差别;那为什么还要注意呢?我们先下来看下以下这句话:分 配的内存数量和完成测试所需的时间之间有直接关系。当我们单独查看的时候,内存分配并不是非常昂贵。但是,当内存系统只是偶尔清理不使用的内存时,问题就 出现了,并且问题出现的频率和要分配的内存数量成正比。因此,你分配越多的内存,对内存进行垃圾回收的频率就越频繁,你的代码性能就会变得越差。

从上面那些话可以看到内存的回收是非常损耗资源,那我们再看下一些.net内部类型的实现。

Array:

// System.Array
public IEnumerator GetEnumerator()
{
int lowerBound = this.GetLowerBound(0);
if (this.Rank == 1 &amp;&amp; lowerBound == 0)
{
return new Array.SZArrayEnumerator(this);
}
return new Array.ArrayEnumerator(this, lowerBound, this.Length);
}

List:

// System.Collections.Generic.List
public List.Enumerator GetEnumerator()
{
return new List.Enumerator(this);
}

Dictionary:

// System.Collections.Generic.Dictionary
public Dictionary.Enumerator GetEnumerator()
{
return new Dictionary.Enumerator(this, 2);
}

 

从以上代码来看,我们再进行foreach操作以上对象的时候都会构建一个Enumerator;也许有人会认为这点东西不需要计较,不过的确 很多情况是不用关心;但如果通过内存分析到到的结果表明构建Enumerator的数量排在前几位,那就真的要关心一下了。很简单的一个应用假设你的应用 要处理几W的并发,而每次都存在几次foreach那你就能计算出有多少对象的产生和回收?看下一个简单的分析图,这里紧紧是存在一个List’1如果组 件内部每个并发多几个foreach又会怎样?

改成for的结果又怎样呢

[转载]ASP.NET页面优化性能提升8倍的方法

mikel阅读(1029)

[转载]ASP.NET页面优化,性能提升8倍的方法 – Fish Li – 博客园.

今天与大家分享:一种优化页面执行速度的方法。
采用这个方法,可以使用页面的执行速度获得【8倍】的提升效果。

为了让您对优化的效果有个直观的了解,我准备了下面的测试结果截图:

测试环境:
1. Windows Server 2003 SP2
2. Viaual Studio 2008,使用自带的WebDev.WebServer.EXE运行网站程序。
3. (ThinkPad SL510):Core2 T6670 2.2GHz, 4G内存

二个红框中的数字反映了优化前后的执行时间。
数字表明:优化前后,执行时间有了8倍多的差别。

本文的测试结果也仅仅只是一个参考数字,这个结果也只是根据我所设计的测试页面得出的。
优化的过程中,如果不使用服务器控件,那么给GC减少的压力其实也是无法测试到的。
在测试过程中,我还发现测试结果并不是很稳定,因此截图具有一定的偶然性。
测试页面或许在某些方面存在一些片面性,因此,结果仅供参考。

测试背景

看过了优化结果,再来介绍一下:这个测试到底是在测试什么东西?

现在有很多做ASP.NET的开发人员,应该都是从ASP.NET的WebForm编程模型开始学习的。 大家都很喜欢用服务器控件,不管输出什么,都会使用服务器控件。 有时候为了让页面呈现干净的HTML代码,有些人会选择使用Repeater,Literal这类简单的服务器控件。 或许有些人认为:我已不使用GridView这样强大复杂的控件,页面执行速度已经很快了。

真是这样吗?

今天测试的起点就从使用简单的服务器开始,我会分二次对它做一系列的性能优化。
最终就是上图中的3个结果,它们反映了二次优化的改进过程。

在继续介绍之前,有一点我想有必要说明一下:

优化的过程涉及到ASP.NET服务器控件的使用,测试结果也仅仅只是一个参考数字。
如果您认为您的开发工作非常依赖于服务器控件的使用,
那么测试结果对您来说其实是无意义的,请不要在意这个结果。

测试方法

在这次优化过程中,我并没有设计很复杂的测试页面,而是一个很简单的测试页面,页面显示效果如下:

这个页面其实就是显示了一堆超链接,它们来自于我的博客侧边栏的【推荐排行榜】,总共有20条记录, 我让页面重复5次输出,也就是生成了100个超链接。

测试的数据是这样获取的:
我复制了我的博客侧边栏的【推荐排行榜】的那段HTML代码,保存到一个文件中:

然后,网站在初始化时,从这段HTML代码提取链接地址以及显示文字,保存到一个BlogInfo的列表中,代码如下:

public class BlogInfo {
    public string Title;
    public string Href;
}

public static class XmlDb {
    public static List<BlogInfo> Blogs { get; private set; }

    public static void LoadBlogs()
    {
        string filePath = Path.Combine(HttpRuntime.AppDomainAppPath, @"App_Data\RecommendList.html");

        XElement html = XElement.Parse(System.IO.File.ReadAllText(filePath));

        Blogs = (from a in html.Elements("li").Elements("a")
             select new BlogInfo { Title = a.Value, Href = a.Attribute("href").Value }).ToList();
    }
}

测试时,就把XmlDb.Blogs的内容显示在网页中。
我想这个测试还是比较接近于现实开发的。

这里又有一个问题:我如何测试页面的执行速度?

虽然说创建HttpWebRequest访问页面是个很简单的方法,但我并不打算这样做。
因为从HttpWebRequest发起调用到获取结果,这其中除了有页面的执行时间,还混杂较多的额外调用开销。 最终,我选择了在一次HTTP请求中,循环调用Server.Execute来执行页面,并统计时间的方式。 其实如何选择测试方法,对于二个测试对象还说,都是公平的。 只是说:尽量减少一些额外的调用开销,会让测试结果的差异更大,也更明显。

说明:为了测试代码写起来简单,我使用了MyMVC框架

测试用例1:WebFromPage.aspx

前面介绍了测试背景以及测试方法。现在就来介绍第1个测试用例,它采用了WebForm编程模型中最经典的写法。

页面代码:

页面的CodeFile代码:

测试代码:

当我测试执行10000次时,耗时:00:00:07.5607229

测试用例2:InlinePage.aspx

与测试用例1不同,测试用例2则完全不使用服务器控件。

页面代码:

测试代码:

当我测试执行10000次时,耗时:00:00:01.2345842

分析优化结果1

测试用例1执行相同次数所花费的时间是测试用例2的6倍,为什么会这样呢?

为了回答这个问题,我们首先要知道前面二个页面在执行时,它们是如何运行的。
说到这里,就不得不谈ASP.NET的页面编译方式了。

ASP.NET的页面编译过程是个复杂的操作,其实我们可以不用关心页面是如何编译的,
但要知道:页面编译后是什么样的。

为了能直观地了解页面编译后的样子,我编译了整个网站,并生成到一个DLL文件中, 然后使用Reflector.exe来分析这个DLL的源代码。

将网站编译成一个DLL文件有二个方法:
1. 安装WebDeployment插件。
2. 使用我的工具:FishAspnetTool

本文将使用FishAspnetTool来编译测试网站获得编译后的DLL文件。

FishAspnetTool是什么?
FishAspnetTool是我在使用Visual Web Developer 2005时,为了方便编译网站而写的一个小工具。
下载地址:http://www.cnblogs.com/fish-li/archive/2011/10/30/2229497.html
注意:下载的是一个工具包,安装后,从开始菜单中运行FishTools\FishAspnetTool即可。
下面是工具的运行截图。

操作方法:
1. 点击粉色按钮,选择网站路径。
2. 单选按钮选择第2项。
3. 点击【发布网站】按钮。

在编译网站之后,我就可以知道网站在运行时如何运行页面了。

测试用例1的页面,最后被编译成这样了:

从这个编译结果我们可以看出:页面上的所有文字最后也被包装到LiteralControl中。
页面中呈现时,就是循环调用每个控件的Render方法来最终生成HTML结果。

测试用例2的页面被编译成这个样了:

请注意下面这段关键的代码:它们实在太重要了。

private void __BuildControlTree(testpage_inlinepage_aspx __ctrl)
{
   // ....... __ctrl.SetRenderMethodDelegate(new RenderMethod(this.__Render__control1));
}

private void __Render__control1(HtmlTextWriter __w, Control parameterContainer)
{

testpage_inlinepage_aspx与testpage_webfrompage_aspx的编译结果完全不同。
最大的差别在testpage_inlinepage_aspx有个方法:__Render__control1
在这个方法中,页面的内容将直接被写入到HtmlTextWriter对象中。
还有一点我要告诉您:每个Control的输出最后还是要将自己的显示代码写入到HtmlTextWriter对象中。
因此,从这里就可以明显地看出testpage_inlinepage_aspx的执行速度要快很多,
因为:
1. 它没有服务器控件。
2. 不再需要递归循环每个控件,每个控件的生命周期的调用开销也节省了。
3. 不用再创建那些服务器控件对象,GC的压力会小很多。
4. 输出方式更高效。

通过前面的分析,您现在明白了为什么二个页面的执行速度相差6倍了原因了吧。

好像还有一点没有解释:__Render__control1如何被调用?

我们都知道:以ASPX页面为代表的WebForm编程模型在执行时有一个特点:递归循环每个控件。
页面是在Render阶段输出的,页面的HTML代码也是在那个阶段输出到HtmlTextWriter对象中的。
可是,testpage_inlinepage_aspx没有任何控件,那又该如何递归呢?

的确,很多书籍以及技术资料都是说:在Render阶段会递归循环每个控件并调用控件的Render方法。

其实这种说法是不准确的。Control的Render方法在运行时,会调用下面这个方法:

这段代码中,有个重要的if…else…判断,简单说来,就是说要不要调用前面所说的__Render__control1方法。
从代码可以看出,如果是进入了if语句块,则不用递归循环每个控件并调用控件的Render方法。

那么如何能进入if语句块呢?
答案是:调用Control.SetRenderMethodDelegate方法。
testpage_inlinepage_aspx的编译生成代码中就有这个调用。
对于这个方法,MSDN的解释很含糊:

此 API 支持 .NET Framework 基础结构,不适合在代码中直接使用。

分配事件处理程序委托,以将服务器控件及其内容呈现到父控件中。

测试用例3:InlineUserControl.ascx

在测试用例3中,我将页面中用于输出的代码移到一个用户控件中。
用户控件的代码此处省略,与测试用例2的代码基本上一致。编译后的结果也基本差不多。

测试代码:

当我测试执行10000次时,耗时:00:00:00.9132738

又快了一点。

说明:为了这次的性能优化,MyMVC框架也做了一点调整。如果您以前下载过这个框架,请重新下载。

分析优化结果2

经过前面的分析,我们知道:不创建服务器控件对象以及不调用它们的生命周期,可以让页面的执行速度快很多。

有没有再想像一下:页面也有生命周期啊,而且生命周期的步骤更长,省略它,会不会更快呢?

不过,Render方法并不是个public方法,我们还不能直接调用,但可以调用RenderControl方法来实现这一过程。

由于跳过页面的生命周期,任何服务器控件都不能使用了,包括母板页。所以我选择将前面测试用的那段代码移到用户控件中, 然后将用户控件加载到Page中来测试。

测试用例3与测试用例2相比,在测试过程中,由于跳过了页面的生命周期,因此速度也就更快了。
注意:事实上,动态加载用户控件也会有一定的调用开销。这种方法也仅供参考,可以根据实际情况来选择。

嗯,基本上,就是这个简单的原因吧。

由于这种方法没有任何的控件生命周期,因此速度是最快的。

经过这一系列的优化,页面的执行时间最终由 00:00:07.5607229 减少到 00:00:00.9132738

再次申明:测试结果也仅仅只是一个参考数字。
事实上,使用服务器控件产生的对象涉及到GC的回收以及内存占用的影响也是不可忽视的。

点击此处下载示例代码

[转载]逃脱Asp.Net MVC框架/枷锁,使用Razor视图引擎

mikel阅读(957)

[转载]逃脱Asp.Net MVC框架/枷锁,使用Razor视图引擎 – 予沁安 – 博客园.

此文应该算Razor引擎浅析的续篇,或者说浅析是此文的前传。

为什么要这么做?
1.  ASP.NET MVC 其实也不是太好
2. 我有自己的敏捷Web框架, 仍然想用Razor引擎
3. 动态编译很有意思,这也是将来的一个趋势,如果有人有兴趣,我倒是很想写写这方面的内容.

可是也有这些想法的人并不多,找来找去,这方面的资料和论述极其之少。与其临渊羡鱼,不如退而结网。自己动手,丰衣足食。

如Razor引擎浅析所述,Razor的两大主要功能,模板文件和动态编译器。我们能不能单纯就要而且只要这俩主要特性? 其它那些智能查找视图文件等,虽不能说是垃圾,也是束缚手脚的条条框框,我完全可以自己做,失去的是一条绳索,获得是整个世界。
保持模板功能,特别是设计时智能语法支持,很简单,创建项目时选择MVC项目,然后把其它MVC相关的引用删掉只剩下Razor就好了。
编译? 你根本就找不到这一段代码,被淹没在大量的辅助代码中。幸好,我这里已经找到了,简化修改之后,就剩下了了几行,你就可以直接使用在甚至不必是Web项目中。

 

首先, 用到的Razor域名空间。

1
2
3
using System.Web.Razor;
using System.Web.Razor.Generator;
using System.Web.Razor.Parser;

第一步,动态编译:解析视图文件,生成代 码,是的,生成代码,先。Razor的语法可以说是私有语法,需要先生成标准代码,然后才编译,生成我们熟悉的C#类Type。需要注意的是,我下面代码 用的模板基类是我自己的TeamplateBase,后面会给出简单实现,当然,好处就是灵活性。你也可以直接用ASP.NET MVC的System.Web.Mvc.WebViewPage, 不过我没有试过,也许会有其他问题,不能保证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public static Type Compile<T>(string template_path)
      {
          //准备临时类名,读取模板文件和Razor代码生成器
          var class_name = "c" + Guid.NewGuid().ToString("N");
          var base_type = typeof(TemplateBase<>).MakeGenericType(typeof(T));
          var template = File.ReadAllText(template_path);
          var host = new RazorEngineHost(new CSharpRazorCodeLanguage(), () => new HtmlMarkupParser())
                         {
                             DefaultBaseClass = base_type.FullName,
                             DefaultClassName = class_name,
                             DefaultNamespace = "YourNameSpace.dynamic",
                             GeneratedClassContext =
                                 new GeneratedClassContext("Execute", "Write", "WriteLiteral", "WriteTo",
                                                           "WriteLiteralTo",
                                                           "YourNameSpace.TemplateBase")
                         };
          host.NamespaceImports.Add("System");
          host.NamespaceImports.Add("YourNameSpaces");
          
          //生成代码
          CodeCompileUnit code;
          using (var reader = new StringReader(template)) {
              var generatedCode = new RazorTemplateEngine(host).GenerateCode(reader);
              code = generatedCode.GeneratedCode;
          }
          //准备编译参数
          var @params = new CompilerParameters
          {
              IncludeDebugInformation = false,
              TempFiles = new TempFileCollection(AppDomain.CurrentDomain.DynamicDirectory),
              CompilerOptions = "/target:library /optimize",
              GenerateInMemory = false
          };
          var assemblies = AppDomain.CurrentDomain
             .GetAssemblies()
             .Where(a => !a.IsDynamic)
             .Select(a => a.Location)
             .ToArray();
          @params.ReferencedAssemblies.AddRange(assemblies);
          //编译
          var provider = new CSharpCodeProvider();
          var compiled = provider.CompileAssemblyFromDom(@params, code);
          if (compiled.Errors.Count > 0) {
              var compileErrors = string.Join("\r\n", compiled.Errors.Cast<object>().Select(o => o.ToString()));
              throw new ApplicationException("Failed to compile Razor:" + compileErrors);
          }
          //编译成功后, 返回编译后的动态Type
          return compiled.CompiledAssembly.GetType("Skight.Arch.Presentation.Web.Core.ViewEngins.Razor.dynamic." + class_name);
      }

第二步就简单多了,就和任何静态类一样,用反射创建实例,然后复制Model对象执行模板,最后输出结果是,就自动吧Model类的数据嵌入了。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static string Render<T>(T model,string template_path)
      {
          var type = Compile<T>(template_path);
          //创建视图实例
          var instance = (TemplateBase<T>)Activator.CreateInstance(type);
          //执行模板(把数据嵌入文件)
          instance.Model = model;
          instance.Execute();
          //输出最终结果
          var result = instance.Result;
          return result;
      }

最后,看看视图模板类,一个基类和一个泛型基类,后者用于前类型Model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public abstract class TemplateBase
    {
        public string Layout { get; set; }
        public UrlHelper Url { get; set; }
        public Func<string> RenderBody { get; set; }
        public string Path { get; internal set; }
        public string Result { get { return Writer.ToString(); } }
        protected TemplateBase()
        {
        }
        public TextWriter Writer
        {
            get
            {
                if(writer==null)
                {writer = new StringWriter();
                }
                return writer;
            }
            set {
                writer = value;
            }
        }
        private TextWriter writer;
        public void Clear() {
           Writer.Flush();
        }
        public virtual void Execute() { }
        public void Write(object @object) {
            if (@object == null) {
                return;
            }
            Writer.Write(@object);
        }
        public void WriteLiteral(string @string) {
            if (@string == null) {
                return;
            }
            Writer.Write(@string);
        }
        public static void WriteLiteralTo(TextWriter writer, string literal) {
            if (literal == null) {
                return;
            }
            writer.Write(literal);
        }
        public static void WriteTo(TextWriter writer, object obj) {
            if (obj == null) {
                return;
            }
            writer.Write(obj);
        }
    }
    public abstract class TemplateBase<T> :TemplateBase
    {
        public T Model { get; set; }            
    }

前传:视图引擎浅析Razor视图引擎浅析之二

[转载]SQL Server 2012中的ColumnStore Index尝试

mikel阅读(953)

[转载]SQL Server 2012中的ColumnStore Index尝试 – CareySon – 博客园.

简介

费了半天劲,今天终于装好SQL Server2012了。按照MSDN中的新特性资料(Columnstore Indexes for Fast DW QP SQL Server 11)。尝试了下ColumnStore Index。ColumnStore Index按照其字面意思所示。是基于列存储的索引。这个概念如图1所示。

1

图1.ColumnStoreIndex和基于行的Index比较

 

ColumnStoreIndex是按照列存入页当中,而不是按照传统的以行为单位存入页。因此带来的好处可以归结如下:

  •      以往的数据按照行存储,select哪怕只有一列,也会将整个行所在的页提取出来,而使用基于列的索引,仅仅需要提取select后面的列。提高了性能。
  •      压缩更容易
  •      缓存命中率大大提高,因为以列为存储单位,缓存中可以存储更多的页(缓存常用的列,而不是整个行)

微软号称自己是第一个支持“纯”列存储的主流数据库。其他数据库我不甚了解,有知道的同学可以反驳下……

 

使用ColumnStore Index不能像使用其它非聚集索引那样没有限制,使用ColumnStoreIndex的限制如下:

1.一个表只能有一个ColumnStore Index

2.不能使用过滤索引

3.索引必须是partition-aligned

4.被索引的表变成只读表

5.被索引的列不能是计算列

6.不能使用Include关键字

 

因此可以看出,中小型的OLTP环境基本和这个功能无缘。ColumnStore Index貌似适用于OLAP和读写分离用。

下面我们来看一些使用ColumnStore Index的实例

 

建立ColumnStore Index和对ColumnStore Index所在表数据进行更改

建立ColumnStore Index和建立普通的非聚集索引看起来基本没有区别,仅仅是多加了一个ColumnStore关键字,如图2所示。

2

图2.建立ColumnStore Index的表后对其插入数据失败

 

如果要对有ColumnStore Index的表进行数据更改,则需要在停用ColumnStore Index后,插入数据,完成后,重建ColumnStore Index,如图3所示。

3

图3.对有ColumnStore Index的表进行数据插入

ColumnStore Index查询性能测试

ColumnStore Index带来的最大好处是查询性能的增加。下面来进行测试。在刚才图1中所建的表中插入100万条从1到1000的随机数,如图4所示。

4

图4.插入100万条测试数据

 

然后在Data列上分别建立ColumnStore Index和普通的非聚集索引,如图5所示。

5

图5.分别建立两个索引

 

然后分别利用这两个索引做一次聚合查询,测试结果发现使用ColumnStore Index对IO的占用大大的减少了。如图6所示。

6

图6.使用两种索引的性能对比

 

所对应的执行计划如图7所示。

78

图7.两种索引的执行计划

 

可以看出,使用ColumnStore Index对性能的提升是巨大的。

 

总结

本文通过对ColumnStore Index做了简单的介绍后,做了简单的测试得出,使用ColumnStore Index对性能的提升是巨大的,但由于ColumnStore Index的使用受到诸多限制。目前只能在OLAP环境中使用。更多的使用场景未来再看吧。