接着昨天……

VS2003到VS2005的项目迁移远没有想象的“可怕”,如果你只是转化为VS2005SP1支持的Web应用程序,那几乎没有什么多余的工作可作,使用VS2005打开SLN项目文件,随着向导一路按下去就可以完成转化工作了。

我的项目中有2个Web项目,转化时会在VSS中签出工程中的所有文件——如果不是VSS那么应该需要事先手动签出,转化会自动识别隐藏代码文件中的控件声明,并转移到一个新建的.aspx.designer.cs文件中,以此来实现兼容。

出现的问题

  • 部分文件无法识别,导致控件声明重复、缺失。这个很简单,可以删除.aspx.designer.cs文件,仍然使用原有的代码隐藏声明模式,或把隐藏代码中的声明删除,迁移到.aspx.designer.cs文件中,都可以很方便的解决。
  • ASP.NET2.0会有一些新的推荐方法替代旧的方法,例如最常用的可能就是读取AppSettings的方法了,虽然旧的方法仍然可用,不过只需要通过简单的“查找/替换”操作就可以在解决方案内替换所有会被编译警告的方法,追求品质嘛。
  • 回发或回调参数无效,这是一个兼容异常,通常是由于页面存在多个form或有js在客户端修改了数据造成,在ASP.NET1.1中会兼容这个异常不会报错,但是2.0中无法运行,需要进行修改。

这些是我在迁移中遇到的异常,更多的可以参考我昨天的文章 http://linnchord.net/archives/10

ok 祝大家顺利迁移,奔向2005 …… 2007就快出来了啊……

旧有系统总在不停的维护中,这两天维护一个网上商城的系统,我试图把他迁移到ASP.NET2.0/VS2005环境下。

由于种种关于迁移失败导致崩溃的信息给我带来的恐惧感,我花了一个下午的时间查询相关资料,详细考察了ASP.NET2.0/VS2005的编译部署机制和相关迁移文档,以确保整个过程顺利实施。

网上相关文档

首先需要确定迁移到哪种项目模式下。根据文档的描述,在VS2005中的ASP.NET网站有下面几种编译部署方式。

  • Web网站项目:VS2005提供的新的Web项目模式,没有项目文件控制,直接把一个物理目录视为项目,目录中的所有文件都是项目文件。
    • 完全编译:通过“网站发布”功能,在发布对话界面上取消所有选项(包括默认选项),可以将网站所有.NET文件(aspx/asmx/ashx/cs…)全部编译为一个DLL程序集,留下的只有非.NET相关的其他图片、样式表、js等文件和aspx占位文件。你可以打开占位文件查看,里面什么内容都没有……呵呵,这真是一种有意思的部署方式,可惜,这绝不适用于大多数一般网站开发项目。
    • 可更新预编译:这是VS2005默认的编译模式,在“网站发布”中选中“允许更新此预编译站点”,这样会保留aspx等非后端隐藏代码的程序文件内容,使你可以方便修改HTML内容并即时生效。这种部署方式最大的问题在于aspx页面会在发布时自动指向程序集,而每次编译的程序集文件名会发生变化,这就导致如果你修改了任何一个程序逻辑并重新编译,就不得不上传所有网站内的程序文件……这在实际网站开发中几乎也是不可接受的,且不论重新上传所有页面的操作代价,这几乎就否定了网站上传后其他人员如设计师直接修改aspx页面的可能,除非将设计师的工作也置于代码控制之下,这是难以实现的。
    • 固定命名和单页程序集编译:这个选项会解决上面的随机程序集命名问题,这个模式下的编译会把每个ASPX文件都编译为一个单程序集,每个程序集后会添加随机数,不过这个随机数是固定的。我试图查找这个随机数的生成依据,不过没有找到,推测应该是根据解决方案中的GUID。把这个选项和前面“可更新预编译”相结合,可以得到较好的编译方案……好吧,我得承认我忽略了生成数十或者以百计的单页面dll的繁琐,不过这是一个优劣并存的方案,因为当你只更新一个页面的逻辑时,可以只上传一个较小的dll,且不影响整个网站的运行,这也是一个不错的优势了。
    • 完全源码部署:这是一个很省心的方案,不做任何编译,直接COPY部署就OK,不过这是另一个极端,估计很少情况会采用——不过可以用来做测试部署,很方便。
  • Web应用程序项目:MS认识到到了“Web网站项目”带来的种种问题,在VS2005SP1中正式提供了这个原有的VS2003的项目模式。这样可以指定目录中的文件为项目文件——必须承认这是一个不可放弃的优势,所以虽然新的“Web网站项目”也有种种优势,但是我还是不得不采用这个“老”方案了。可以想象,如果采用新方案,我就得面对项目运行维护时在项目目录中骤增的数以百计、千计的运行时文件,还不得不接收由此带来的超长时间的编译及发布copy,这会使人崩溃的……

编译部署的方案就是上面这些了,VS2005的网站项目应该说是有很多优势的,不过用一句老话讲,这些改进并不符合中国国情,或者至少不符合一大部分中国网络服务供应商的运行状况。这些改进都是需要在业务相对稳定,有较严格代码控制等情况下才能较好的实施,而中国目前估计除了企业开发外,在一般的网络运营下少有这样的环境。其实从这些改进和现状的冲突,就可以看出国内和国外行业水平的差异,呜呼哀哉……

写太多了,迁移稍后吧……

Url Rewrite 实现

星期二, 05月 22nd, 2007

http://blog.joycode.com/scottgu/archive/2007/03/01/94004.aspx

整个blog实现了url重定向

/blog/{id}.aspx
/blog/tag/{id}.aspx
/blog/archives/{id}.aspx

采用UrlRewriter.NET开源组件实现重写。

.Net Framework On ROR – ASP.NET ActionPack

星期四, 08月 24th, 2006

偶尔看到了这个东东,惊艳的感觉!Commerce Starter Kit 作者Rob Connery在微软的开源网站上http://www.codeplex.com推出的.net框架,基于.net2.0,依据ROR的思想构建,目前已经具备ActiveRecord和基本的scaffolding功能。

先看个视频
http://www.wekeroad.com/actionpackintro.html
提醒一下,这个视频很精彩,但是演示屏幕较大,时间较长,估计制作上有些问题,导致会占用很高的内存,看到后期大概有600-700M占用。

网站
http://www.codeplex.com/Wiki/View.aspx?ProjectName=actionpack

实在是很强大的功能,虽然目前可能还不完善,scaffolding部分还很简陋,不过ActiveRecord已经可用,相信看了视频演示后就会了解,会给工作带来很大便利,强烈推荐!

下载了源代码看,代码量出乎意料的小,大量的泛型应用,看来这个语言特性确实非常强大。

因为简单,所以困难。

星期三, 08月 9th, 2006

在网上回复了一个帖子,讨论为什么asp.net开发的网站应用经常表现出不稳定和性能低下,我是这么认为的。

asp.net的页面模型,其实是业界公认非常优秀的开发模型,这种模型简化了原来页面逻辑的烦琐流程,并且有机的和常规的系统模型和面向对象思想结合起来,提高了整个系统的抽象层次,降低了底层开发上手的难度,有利于从更高的层次上组织构架系统,提高开发效率。

但是也正是由于屏蔽了服务的底层,开发上手快、效率高,所以很多人没有或者不能深入理解其机制,只知道简单拖拉然后调用使用控件,只图方便,也不知道什么 地方该用什么地方不该用。现在很多asp.net新入门的人,不知道根本的页面交互模型,不知道http基本机制,甚至不知道HTML,拿着控件到处放, 反正功能出来就ok,这样的东西是什么结果可想而知。

这是目前国内asp.net做网站非常典型的情况,我了解过好几个公司网站都是.net做出来千疮百孔,没办法维护,最后干脆推倒,全部改了java或者php。

不过据我了解,在国外似乎这种情况较少。我思前想后,觉得归根到底,还是市场原因…市场决定一切,需求决定一切…后面就没什么可说了,大家都见多了 呵呵

在Web系统开发中,我们经常需要读取和设置一些系统配置项,常见的例如数据库连接字符串、上传路径等等。

在最初的ASP系统中,比较常用的方法是将值保存为Application或Session变量;在Asp.net系统中,目前比较常见的简单方法是把相应的配置项写入Web.Config中,例如

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
...
</system.web>

<appSettings>
<add key="ConnectionString" value="server=(local);database=dbname;uid=user;pwd=pass"/>
</appSettings>
</configuration>

然后在程序中通过如下方式读取

string connString = ConfigurationSettings.AppSettings["ConnectionString"];

这种方法在系统规模较小复杂度较低的时候也不失为一种简单明了的轻量级方法,不过如果系统较复杂,配置项会较多,同时我们需要按不同模块对配置进行划分,并且还希望能以面向对象方法来对其进行封装,那么如果仍然采用这种过于简化方式就不太合时宜了。


下面,我讲述一下通过XML类序列化解决系统配置问题的方法。

关于XML类序列化和反序列化(另外一种描述是串行化和并行化)的技术细节,大家可以查看MSDN了解;这里简单讲两句,XML序列化是把一个对象序列化到XML文档的过程,反序列化则是从XML输出中重新创建原始状态的对象。

直观表现就是如下图模式

看了这个图,就很清楚了,通过序列化,可以采用面向对象的方法,非常自然和方便的读取和设置系统配置;.NET Framework承担了对象和XML文件映射工作,我们只需要简单的使用就OK。下面我们讲一下具体内容。

上面图示已经表明,首先需要一个XML配置文件,格式内容如上图,具体配置项可以自行增减。

然后我们需要编写一个类,如上图所示;特殊的一点,为了使类能够实现XML序列化,需要在类的所有属性声明前添加属性信息XmlElement,如下所示。

[XmlElement]
public string ConnectionString
{
   get { return connectionString; }
   set { connectionString = value; }
}

由于AppConfig类本身没有实现方法,因此我们需要一个配置类AppConfigSetting.cs。类的结构很简单,只需要两个静态方法,Get()获取AppConfig对象,Save()保存AppConfig对象。

另外,我们需要在 Web.Config中添加该XML配置文件的地址。

<appSettings>
  <add key="AppConfigPath" value="/FilePath/file.config"/>
</appSettings>

public class AppConfigSetting
{
   //获取配置对象
   public static AppConfig Get()
   {
      //尝试获取缓存中的对象
      AppConfig config = (AppConfig)HttpContext.Current.Cache["AppConfig"];

      //如果缓存中没有该配置对象,则直接获取对象
      if (config == null)
      {
         //新建序列化对象并指定其类型
         XmlSerializer serial = new XmlSerializer(typeof(AppConfig));

         try
         {
            string file = HttpContext.Current.Server.MapPath(GetFile());

            //读取文件流
            FileStream fs = new FileStream(file, FileMode.Open);

            //文件流反序列化为对象
            config = (AppConfig)serial.Deserialize(fs);

            fs.Close();

            //将对象加入到缓存中
            HttpContext.Current.Cache.Insert("AppConfig",
                        config, new CacheDependency(file));
         }
         catch (System.IO.FileNotFoundException)
         {
            config = new AppConfig();
         }
      }

      return config;
   }

   //保存配置对象
   public static void Save(AppConfig config)
   {
      string file = HttpContext.Current.Server.MapPath(GetFile());
      XmlSerializer serial = new XmlSerializer (typeof(AppConfig));

      FileStream fs = new FileStream(file, FileMode.Create);

      //对象序列化为文件
      serial.Serialize(fs, config);
      fs.Close();
   }

   //获取配置文件路径
   private static string GetFile()
   {
      string path = (string)HttpContext.Current.Cache["FilePath"];

      if (path == null)
      {
         path=ConfigurationSettings.AppSettings["AppConfigPath"];
         HttpContext.Current.Cache["FilePath"] = path;
      }
      return path;
   }

}

类的使用非常简单,基本方式如下

//代码仅为使用演示
AppConfig config = AppConfigSetting.Get();

string connString = config.ConnectionString;

...

config.ConnectionString = connString;
AppConfigSetting.Save(config);

看到这样的代码,不禁有令人赏心悦目之感;相对于原来的直接读取AppSetting,可谓解脱矣! :)

OK,就此结束了。这篇文章只涉及对象序列化的非常简单的应用,没有涉及太多的技术原理和细节,大家要深入了解请参考MSDN。

希望文章能对大家有所补益和启发。 :)

测试源代码工程文件下载(Visual Studio.net 2003)

CSS设计器代码参考.3

星期一, 05月 17th, 2004

界面交互

在XML中一共定义了 select/ input_ColorSelect / input_SizeSelect / input_BorderSelect(后3种为颜色/大小/边框输入模式)共4种输入模式,除select为直接选择外,其他在对应控件初始化的时候作为class属性赋值到控件中,类似class代码如下

/* 颜色输入模式input框的样式类 */
.input_ColorSelect
{
	width:100px;
	font-family:Tahoma;
	behavior:url(htc/effColorSelect.htc);
}

通过behavior属性,把该输入控件和相应的组件相关联,该组件effColorSelect.htc代码如下

<public:ATTACH EVENT="onfocus" ONEVENT="getShow()"/>
<public:METHOD NAME="getChange"/>

<script LANGUAGE="JScript">
function getShow()
{
element.blur();

//记录当前交互控件的ID 
effElement=element.id;

//在页面中加载输入控件
ShowControl ("SelectColor");
}

function getChange()
{
//当值发生变化时,对可视化样式元素赋值
SetAttribute(element.id,element.value);
}
</script>

其他

设计器中的值输入模式框只是页面中的几个层,通过上面的htc组件触发显示出来,输入后再把值传入到样式属性控件中,同时也会设置可视化样式元素。

另外还需要注意的是,XML文档是可以自行扩展或缩减的,但是在实际应用中,不能完全依据CSS标准来定义,因为可视化元素的style属性会自动格式化。例如如果你在XML中定义border-bottom-width属性,在将值取出时会自动格式化为border-bottom,这样会造成设计器中控件不能匹配。我在MSDN没有查到相关文档,所以只有经过实际测试来验证。

OK,比较关键的代码已经差不多了……希望能对大家有所帮助。
参考

另外再列出部分技术参考,如果大家对其中的技术细节如HTC和XmlDom等有所疑问,可以再详细研究一下,也欢迎大家来和我交流。

MSDN关于JS操作XmlDom的文档

这是英文文档,网上没有看到比较详细的中文文档,好在不复杂,大家将就一下吧 :)

(最近MSDN不知道什么毛病,经常访问有问题,如果无法访问,请先登录msdn,再输入地址浏览)

蓝色理想的HTC教程

网上也没看见比较全面的讲述,这个简单易学,基本概念清楚了。

CSS设计器代码参考.2

星期三, 05月 12th, 2004

设计器初始化

Js脚本读取解析样式元素的style属性值,然后为设计器中构建的控件赋值

//设计器初始化
function Init()
{
   //获得由服务器端赋值的样式属性值
   var txt=document.all("DemoShow").style.cssText;

   if (txt.length>0)
   {
      var strClassName;

      //解析字符串
      var aryClass = txt.split(/[:;]/);

      for( i in aryClass)
      {
         var str = aryClass[i].replace(/(^s*)|(s*$)/g, "");
         if(!(i%2==1))
         {
            //当i为奇数时,解析的字符串应该为样式属性名称
            strClassName=str;
         }
         else
         {
            //当i为偶数时,获得属性值

              //属性名称即控件ID
            //判断该属性对应的控件是输入框还是选择列表
            if(document.all(strClassName).type=="select-one")
            {

               //如果是选择列表通过setIndexOfValue函数设定选择项
               setIndexOfValue(strClassName,str);

            }
            else
            {
               document.all(strClassName).value=str;
            }
         }
         i++;
      }
   }
}

CSS设计器代码参考.1

星期一, 05月 10th, 2004

这里对前面讲的CSS设计器系统关键代码作一些小结,如果没有看过前面文章请先参看前文
解析CSS样式文件

这段代码主要作用是把CSS文件分解为多个样式类,并按名称/文本属性生产ClassItem对象,并保存在一个ArrayList(cssList)中(C#代码)

//读取文件
 FileInfo theSource= new FileInfo (@m_filePath);
 StreamReader reader = theSource.OpenText();

 //将文件流转化为文本
 m_cssText = reader.ReadToEnd();
 reader.Close();

 //定义CSS文本分割符
 char[] delimiters = new char[] { '{','}'};
 int iCheck = 1;string className = null;

 //将文本转化为ArrayList
 foreach ( string substring in m_cssText.Split(delimiters))
 {
     if (iCheck%2==0)
         //当iCHeck为偶数时,字符串为样式属性内容 
         //将解析的样式名和属性作为ClassItem对象存入cssList        
         cssList.Add( new ClassItem ( className, substring.Trim() ) );

     else
         //当iCHeck为奇数时,字符串为样式名,暂存 
         className = substring.Trim(); iCheck++;
 }
 

交互界面构建

交互界面由Javascript通过XmlDocument读取Xml文件动态生成。

首先要读取XML文件,然后遍历整个XML文件,先遍历样式分类,再对每个分类遍历其下的所有样式属性。比较关键的代码是对XML的遍历,下面是对样式分类的遍历代码。

//LoadXML是XML文件读取函数
var dom = LoadXML("css.xml");

//通过XPath和selectNodes方法返回一个XMLDOMNodeList对象
var oNode = dom.selectNodes("//Category/Name");

//获取该对象长度,即XML文档中该路径节点的数量
var intCategory = oNodes.length;
for (i=0; i&lt;intCategory; i++)
{
//获取集合中的节点
oNode = oNodes.nextNode;

if (oNode != null)
{
//样式分类界面构建代码-略 ……
}
}

样式输入控件构建函数,该函数作用是根据XPath路径查询XML定义,生成交互控件

function BuildInput ( path )
{
var str="";
var aNode=null;
var attValue=null;

//通过selectSingleNode返回符合条件的第一个节点 
var actNode = dom.selectSingleNode(path+"ActionType");
var nameNode = dom.selectSingleNode(path+"CssName");

//如果属性为选择输入,则读取SelectItems,并构建select控件 
if (actNode.text=="select")
{
str += "<select id='"+nameNode.text+"' name='"+nameNode.text+"'class='eSelect'>";

//查询该项的所有选择列表项 
var itemsNodes = dom.selectNodes (path+"SelectItems/Item");
str += "<option value='-1'>未设置</option>";
for (ii=0;ii<itemsNodes.length;ii++){
aNode = dom.selectSingleNode (path+"SelectItems/Item["+ii+"]");

//如果该项含有Name属性则在列表中显示Name属性值
attValue = aNode.getAttribute("Name")
var txtNode = dom.selectSingleNode (path+"SelectItems/Item["+ii+"]");
if (attValue==null)
str += "<option value='"+txtNode.text+"'>"+txtNode.text+"</option>";
else
str += "<option value='"+txtNode.text+"'>"+attValue+"</option>";
}
str += "</select>";
}
else
//如果属性为其他模式,则构建input输入,设置class属性为ActionType
{
str = "<input name='"+nameNode.text+"' id='"+nameNode.text+"' class='"+actNode.text+"'>";
}

return(str);
}

asp.net开发技巧

星期四, 05月 6th, 2004

不断学习的过程中,经常遇到一些代码编写技巧性的东西,虽然通常不是什么特别困难的技术问题,不过也十分重要。

为避免遗忘并方便查证,特记录于此。

在页面中直接发文件到客户端

说明: 将下面代码加入干净的aspx页或隐藏代码页即可(此处假设页面为download.aspx);
效果: 链接到地址[download.aspx?file=filename]直接产生下载;
目的: 与直接链接文件地址下载方式比较,可以在下载前加入代码,统计下载数;另外,根据需要可以改写参数(例如改为数据库中该下载文件记录的id),做简单的代码处理,就可以在一定程度上隐藏下载地址,防止盗链……

private void Page_Load(object sender, System.EventArgs e)
{
string path = Server.MapPath(Request.Params["File"]);
System.IO.FileInfo file = new System.IO.FileInfo(path);

Response.Clear();
Response.AddHeader(“Content-Disposition”, “attachment; filename=” + file.Name);

Response.AddHeader(“Content-Length”, file.Length.ToString());
Response.ContentType = “application/octet-stream”;
Response.WriteFile(file.FullName);
Response.End();
}

1 of 212