当前位置: 首页 > 编程日记 > 正文

对微软Web Deploy的一次艰难调试

2011年初开始做一个项目,开始体验使用微软网站发布工具来发布网站。在服务器端安装发布服务后,可以在Visual Studio界面中右键点击Web项目,再点发布,第一次填好发布设置,以后就可以实现一键发布,虽然还有不少高级功能没有用到,不过已经方便得不敢相信了。敏捷开发的一个要素不就是每日构建吗,开发过程中,每天下班前Check In代码(Visual Studio装了Anksvn插件),再发布到服务器上,连一分钟都不用。

具体步骤这里不介绍了,大家有兴趣可以看下Scott Guhire的博客。顺便说一下,那个WebPlatform Installer要比我当时逐个网上搜索下载方便多了,却要你先安装.Net 2.0,明显无理要求嘛,我只装了.Net 4.0。只要把安装包文件提取出来,再改下其config文件让其兼容4.0就可以了。

按计划过年前,要发布Beta版本,几名领导会来观看演示。可就在演示前,出现了麻烦,站点怎么也部署不上去了。出现下面的错误:

image

折腾了一个多小时,终于想到之前发布都是成功的,可能因为在上线前一天,改了很多东西。于是我在给我帮忙的实习生电脑上试了下,上面的代码还是旧的,结果她那边可以发布成功。我拿到旧代码,在本机同样成功。其实本来直接将站点手工复制到服务器上也没什么大不了,但我这个人比较爱钻牛角尖,既然排除了的发布工具或服务器端突然秀逗的原因,那就只能是代码的原因。于是采用折半排除大法,排除一部分文件在项目外,再尝试发布。直到最后,才找到罪魁祸首-正是web.config文件。有点出乎意料,原以为这个文件不用编译,直接复制就可以。

原因也找到了,是前一天将web.config的<appsettings>加了许多项,很多项中含有转义字符如“><&”之类,删掉这些项就可以了,然后把web.config手工拷到服务器网站根目录下。

演示进行得比较顺利,领导们接着又提出了几项新功能。过年回来后,尽管忙得不可开交,而我还一直纠结着这个令人摸不着头脑的错误。

随着站点部署上线,开发告一段落,我终于腾出手来,想把这个问题搞个水落石出。

首先要找到Microsoft.Web.Publishing.Tasks这个程序集,和一般.Net Framework程序集的不同,它是在C:\Program Files\MSBuild\Microsoft\VisualStudio\v10.0\Web目录下。根据错误信息,用Reflector翻出了ParameterizeTransformXml.Execute方法。

bool flag = true;
IXmlTransformationLogger logger = new TaskTransformationLogger(base.Log, this.StackTrace);
XmlTransformation transformation = null;
XmlTransformableDocument xmlTarget = null;
try
{logger.StartSection(SR.GetString("BUILDTASK_TransformXml_TransformationStart", new object[] { this.Source }), new object[0]);xmlTarget = OpenSourceFile(this.Source, this.sourceIsFile);logger.LogMessage(SR.GetString("BUILDTASK_TransformXml_TransformationApply", new object[] { this.Transform }), new object[0]);transformation = OpenTransformFile(this.Transform, this.transformIsFile, logger);this.storageDictionary.TokenFormat = this.TokenFormat;this.storageDictionary.UseXpathToFormParameter = this.UseXpathToFormParameter;transformation.AddTransformationService(this.storageDictionary.GetType(), this.storageDictionary);flag = transformation.Apply(xmlTarget);if (flag){logger.LogMessage(SR.GetString("BUILDTASK_TransformXml_TransformOutput", new object[] { this.Destination }), new object[0]);this.resultXml = SaveTransformedFile(xmlTarget, this.Destination, this.destinationIsFile);}
}
catch (XmlException exception)
{Uri uri = new Uri(exception.SourceUri);  //错误抛出源logger.LogError(uri.LocalPath, exception.LineNumber, exception.LinePosition, exception.Message, new object[0]);flag = false;
}

一目了然,这个方法处理异常的代码出现了的逻辑问题。项目已经上线,我目的已不是让远程发布顺利完成,而是找到真正的错误原因。XmlException是从哪里抛出的呢?对于我这种只用IDE调试的菜鸟来说,有点麻烦。

马上想到的,是用一个程序加载此程序集,通过抛出异常的InnerException属性的堆栈,应该能找到异常源。问题是怎么启动这个程序集,我乡下人,怕黑不喜欢研究命令行。这里我找到一个不错的借口,即使找到异常源,也没法去调试(这不是.Net Framework一部分,没有源代码下载的)。

要想调试代码,还得求助于Reflector。不过这个程序集估计有几万行代码,不能指望代码导出来,想把它改得编译通过有点悬。所以争取只导出最少的,与错误相关的代码。既要定位到异常源,还要获悉源方法的上下文变量。束手无策的看了两天源代码,终于决定从IL入手。在园子里,看过多位朋友写过如何改造VSPaste插件,既然同是.Net程序集,我应该也可以在ParameterizeTransformXml.Execute方法中塞一点东西,曝光其一些运行时的真相。

临渊羡鱼,不如退而结网。耐下性子,学了几天IL语法基础,练了几个示例,然后准备开刀了。由于reflector导出的IL也有问题,所以手术刀还是用ildasm工具,直接转储就可以了。会生成三个文件,扩展名为res和resources的两个不用管,只打开扩展名il的那个文件,有4兆多,所以还是用一个给力点的文本编辑器吧,我用的是NotePad++。

找到Execute方法,我选了几个可能的关键点,分别插入了一段IL代码,来将下一个函数调用的参数值保存到日志文件中。代码可以先用C#写好,在Reflector中查看编译的程序集,把IL复制过去,再根据上下文修改下如何获取要保存的参数即可。比如这行代码:xmlTarget = OpenSourceFile(this.Source, this.sourceIsFile),对应的IL是:

        IL_0042:  ldarg.0
        IL_0043:  call       instance string Microsoft.Web.Publishing.Tasks.ParameterizeTransformXml::get_Source()
        IL_0048:  ldarg.0
        IL_0049:  ldfld      bool Microsoft.Web.Publishing.Tasks.ParameterizeTransformXml::sourceIsFile
        IL_004e:  call       class Microsoft.Web.Publishing.Tasks.XmlTransformableDocument Microsoft.Web.Publishing.Tasks.ParameterizeTransformXml::OpenSourceFile(string,bool)

在其前面插入:

        ldstr "D:\\pub.log"     //将字符串加载到栈上
        ldarg.0                   
//加载自己(this)的引用到栈上                 

   call       instance string Microsoft.Web.Publishing.Tasks.ParameterizeTransformXml::get_Source() //读取属性到栈上
        ldstr "[Source]\r\n"
        call string [mscorlib]System.String::Concat(string, string)  
//将栈顶的两个字符串合并成一个(原来栈有三个变量,现为两个)
        call void [mscorlib]System.IO.File::AppendAllText(string, string) //记录日志,现在栈被清空
        ldstr "D:\\pub.log"
        ldarg.0
        ldfld      bool Microsoft.Web.Publishing.Tasks.ParameterizeTransformXml::sourceIsFile
//读取字段
        box bool  //装箱
        ldstr "[sourceIsFile]\r\n"
        call string [mscorlib]System.String::Concat(object, object)
        call void [mscorlib]System.IO.File::AppendAllText(string, string)

小心冀冀花了半天,修改完保存,用ilasm命令进行编译,又修正一些错误,基本都复制多或少一块造成的。编译成功后,将原位置的Microsoft.Web.Publishing.Tasks.dll文件备份后替换掉,在Visual Studio中发布,却又报错了,说“签名不匹配”,无法加载dll。

赶紧又一顿搜索,将程序集IL中.hash语句删除,再编译,替换,重启VS,发布,果然成功了!还是显示原来的错误,不过刚才嵌入的IL代码,如同打入敌人堡垒内部的同志,通过log文件中,成功地送出了致命的情报。

运气非常好,因为日志文件中只多了两行,说明还就是OpenSourceFile方法出错了。Source属性正是网站项目web.config文件的绝对路径,sourceIsFile值为True。在Reflector中进入OpenSourceFile方法,更简单,只有聊聊几行:

private static XmlTransformableDocument OpenSourceFile(string sourceFile, bool isSourceFile)
{XmlTransformableDocument document2;try{XmlTransformableDocument document = new XmlTransformableDocument{PreserveWhitespace = true};if (isSourceFile){document.Load(sourceFile);}else{document.LoadXml(sourceFile);}document2 = document;}catch (XmlException exception){throw exception;}catch (Exception exception2){throw new Exception(SR.GetString("BUILDTASK_TransformXml_SourceLoadFailed", new object[] { exception2.Message }), exception2);}return document2;
}

追踪到了XmlTransformableDocument的Load方法,目标精确已够,可以收网抓捕喽。接着可以建一个测试工程,就把这个类,以及与其相关的类代码,从Reflector中拷贝出来。看上去代码量也不少,不过有些比如说是用来写Xml,可以直接去掉。编译通过后,F5调试运行。终于,剥开重重迷雾后,这次看到异常的庐山真面目:XmlAttributePreservationDict类ReadPreservationInfo(string elementStartTag)方法抛出的XmlException-“有未闭合的字符串。 第 3 行,位置 47。”

异常源找到了,接着要找原因。由于在调试状态,直接可以看到方法参数传进的值出了问题:虽然还不明白这个方法的目的,但elementStartTag不应该一个被截断的Xml节点字符串,而这个节点,正是那天导致发布失败的修改中加入的<appsettings>下的一个<add>节点。

仔细一看web.config文件中那个出错的节点,顿时让我气得不打一处来。原来对节点中属性中的Html标签中的一个尖括号,没有作转义处理。如今实习生实在是太靠不住了,差点被害死,我还特别强调过要小心转义符号啊。

当然,微软的代码肯定也有毛病,虽然转义特殊字符是标准做法,但尖括号是居于属性引号中,没理由不能正确解析。实际上,XmlTransformableDocument类的基类XmlDocument(大家都很熟悉了吧),就不存在这种问题。

ReadPreservationInfo不是最终元凶,真正的元凶是调用它的幕后黑手,我揪它出来,给大家示众:

internal class XmlAttributePreservationProvider
{... ....public XmlAttributePreservationDict GetDictAtPosition(int lineNumber, int linePosition){if (this.reader.ReadToPosition(lineNumber, linePosition)){int num;StringBuilder builder = new StringBuilder();do{num = this.reader.Read();builder.Append((char)num);}while ((num > 0) && (((ushort)num) != 0x3e));if (num > 0){XmlAttributePreservationDict dict = new XmlAttributePreservationDict();dict.ReadPreservationInfo(builder.ToString());return dict;}}return null;}
}

这段代码不长,从个人角度说,我倾向于用一个全局的而不是局部的StringBuilder变量。导致本文问题出现在while语句的判断条件上,0x3e正是右尖括号的ASC码,以尖括号的出现作为节点结束标志,显然没有做到完全的严谨。不知道是微软的开发人员偷工减料,还是对XML的理解有点小偏差。其实,个人觉得发布网站有必重写这么多东西吗?

其实,在.Net Framework中对XML操作的核心类-XmlReader,几乎是滴水不漏。看上去命名空间一个是Microsoft,一个是System;我看到了一个是程序员,一个是大师,你感觉到了吗?

转载于:https://www.cnblogs.com/XmNotes/archive/2011/05/31/2063889.html

相关文章:

vue总结 08状态管理vuex

状态管理 类 Flux 状态管理的官方实现 由于状态零散地分布在许多组件和组件之间的交互中&#xff0c;大型应用复杂度也经常逐渐增长。为了解决这个问题&#xff0c;Vue 提供 vuex&#xff1a;我们有受到 Elm 启发的状态管理库。vuex 甚至集成到 vue-devtools&#xff0c;无需配…

java8 lambda 表达式详解

lambada 表达式实质上是一个匿名方法&#xff0c;但该方法并非独立执行&#xff0c;而是用于实现由函数式接口定义的唯一抽象方法使用 lambda 表达式时&#xff0c;会创建实现了函数式接口的一个匿名类实例可以将 lambda 表达式视为一个对象&#xff0c;可以将其作为参数传递1.…

【基础知识】win10常用快捷键

1、winctrlD 创建虚拟桌面 2、winctrl左右箭头 切换桌面 winctrlF4 关闭当前虚拟桌面 (按wintab 可以通过鼠标操作虚拟桌面的创建、切换与删除) 3、winM 全部窗口最小化 (winD 可在最小化全部窗口与恢复全部窗口状态 间自由切换 4、win数字 打开任务栏中从左到右的应用&#xf…

ASP.NET2.0-防止同一用户同时登陆

要防止同一用户同时登陆,首页应该记录在线用户的信息(这里与用户名为例),然后判断正在登陆的用户里面是否已存在。在这里使用一个cache存放已经登陆的用户名&#xff0c;但是还有一个问题就是要知道用户是什么时候离开系统的呢&#xff1f;这就要定期清除cache中的内容了&#…

如何选择真正的万兆防火墙?

http://www.cnw.com.cn/security-10g-firewall/htm2011/20110526_225444.shtml 随着“三网合一”、 “P2P视频”、“高清宽带”、“云时代”等逐渐成为人们关注的焦点&#xff0c;网络带宽的需求产生了几何级别的增长。目前&#xff0c;“千兆到桌面、万兆做骨干”对于交换机和…

Salesforce Lightning开发学习(二)Component组件开发实践

lightning的组件区分标准组件、自定义组件和AppExchange组件。标准组件由SF提供&#xff0c;自定义组件由developer自行开发&#xff0c;AppExchange组件由合作伙伴建立。下面我们写一个简单的列表demo来学习下自定义开发组件Component。 第一步&#xff1a;新建一个对象&#…

转 java c++互传arraylist

Java JNI由浅入深(包括&#xff1a;Java和C互传ArrayList泛型对象参数) 2010-11-25 09:57 1694人阅读 评论(1) 收藏 举报我们知道Java是一个运行在虚拟机里面的高级的编程语言&#xff0c;如果要调用系统的动态链接库的话&#xff0c;就要先声明native修饰的方法(类似接口里面的…

GoAccess安装及分析nginx实时日志

GoAccess是一个基于终端的快速日志分析器。其核心思想是实时快速分析和查看Web服务器统计信息&#xff0c;而无需使用您的浏览器&#xff08;如果您希望通过SSH快速分析访问日志&#xff0c;或者只是喜欢在终端中工作&#xff09;&#xff0c;终端输出是默认输出&#xff0c;但…

在写游戏时钟类时,应确保时钟的计算是以某个固定的CPU为标准的

在写游戏时钟类时&#xff0c;应确保时钟的计算是以某个固定的CPU为标准的。下面通过一个例子来阐述如何实现这一思想&#xff1a; // 记录特定进程的affinity maskunsigned long lProcessAffinityMask;// 记录系统的affinity maskunsigned long lSystemAffinityMask;::GetProc…

学习Spring Boot

Spring boot 是什么 ? 简单说, spring boot 是一个构建项目的工具, 一个脚手架. Spring boot 能干什么? spring boot 做非常少的配置就可以构建生产级别的单体应用. Spring boot 怎么干的? 下面让我们来用spring boot 做一个hello world. 环境准备, 需要保证你的机器上已经有…

Linux添加用户组和添加用户

1.用户组 添加组&#xff1a;groupadd 组名 [rootServer-n93yom ~]# groupadd dev [rootServer-n93yom ~]# cat /etc/group | grep dev dev:x:10011: [rootServer-n93yom ~]# 删除组&#xff1a;groupdel 组名 [rootServer-n93yom ~]# groupdel dev [rootServer-n93yom ~]# cat…

用javascript实现仿163的js广告向下挤压页面的效果

本次实现一个js小小特效&#xff0c;效果就是广告从页面的上方出来将基本页面挤压下去。 实现的思路是将两个div&#xff08;广告div1和div2&#xff09;。将div1的高度不断增加&#xff0c;增加的同时div2的y轴坐标变大&#xff0c;div1的高度增加多少相对应的y轴坐标加大多少…

【eclipse】快速调整eclipse背景和格式的方法

第一步 第二步 第三步 &#xff1a;选择相应的格式 效果如图

对做C#自定义控件的一点心得

近期在做DSOFramer这个控件&#xff0c;打算自己弄一个自定义控件来封装这个COM组件&#xff0c;中间遇到很多曲折&#xff0c;研究了一个星期&#xff0c;终于完成了 下面总结一下我做DSOFramer这个自定义控件的注意地方&#xff1a; 1、在创建一个Windows窗体控件库的时候&am…

numpy知识点

1、nonzero 对于一维数据来说&#xff0c;将返回符合条件的 下标 >>> b1 np.array([True, False, True, False]) >>> np.nonzero(b1)(array([0, 2]),)对于二维数据来说&#xff0c;将返回两维 元组&#xff0c; 第一维是符合条件的 x的索引&#xff0c;第二…

Excel导入异常Cannot get a text value from a numeric cell解决

POI操作Excel时偶尔会出现Cannot get a text value from a numeric cell的异常错误。 异常原因&#xff1a;Excel数据Cell有不同的类型&#xff0c;当我们试图从一个数字类型的Cell读取出一个字符串并写入数据库时&#xff0c;就会出现Cannot get a text value from a numeric …

【蓝桥java】递归基础之反向输出字符串

题目&#xff1a;输入一个字符串&#xff0c;要求将该字符串反向输出 代码实现&#xff1a; package xn.zzunit.recurrence;/*** 递归方法反向输出字符串* author tyrantForever**/ public class Project1 {public static void main(String[] args) {String testString "…

SQL Server中灾难时备份结尾日志(Tail of log)的两种方法

简介 在数据库数据文件因各种原因发生损坏时&#xff0c;如果日志文件没有损坏。可以通过备份结尾日志&#xff08;Tail of log)使得数据库可以恢复到灾难发生时的状态。 例如: 上图中。在DB_1中做了完整备份&#xff0c;在Log_1,Log_2处做了日志备份。在Log_2备份之后不久&…

C examples

最近在看David R. Hanson 的《C Interfaces and Implementations》&#xff0c;文中第一章提到了Literate Programming 作者举了一个例子&#xff1a; 功能&#xff1a;用于检测输入中相邻且相同的单词 #include<stdio.h> #include<math.h> #include<errno.h&g…

特征工程:特征生成,特征选择(三)

转自&#xff1a;https://blog.csdn.net/cymy001/article/details/79169862 特征生成 特征工程中引入的新特征&#xff0c;需要验证它确实能提高预测得准确度&#xff0c;而不是加入一个无用的特征增加算法运算的复杂度。 1. 时间戳处理 时间戳属性通常需要分离成多个维度比如年…

JAVA设计模式之不变模式

在阎宏博士的《JAVA与模式》一书中开头是这样描述不变&#xff08;Immutable&#xff09;模式的&#xff1a; 一个对象的状态在对象被创建之后就不再变化&#xff0c;这就是所谓的不变模式。 不变模式的结构 不变模式可增强对象的强壮型(robustness)。不变模式允许多个对象共享…

【蓝桥java】递归基础之智力训练

题目&#xff1a; 匪警请拨110,即使手机欠费也可拨通&#xff01; 为了保障社会秩序&#xff0c;保护人民群众生命财产安全&#xff0c;警察叔叔需要与罪犯斗智斗勇&#xff0c;因而需要经常性地进行体力训练和智力训练&#xff01; 某批警察叔叔正在进行智力训练&#xff1a; …

EF-Entity Framework 相关技术点收集贴

不定期、偶尔、添加更新 在网络上看到或者自己开发过程中碰到的EF&#xff0d;Entity Framework相关技术点 本文地址:http://www.cnblogs.com/vnii/archive/2012/02/28/2371736.html 1.数据表字段有默认值&#xff0c;比如DateTime类型的字段CreateTime默认值为数据新增的时间g…

[翻译]LightRacer游戏架构

1.0版本的Light Racer架构可说的不多。仅有一个单一的Activity&#xff0c;进行按钮的处理&#xff0c;显示游戏相关数据和显示GameView。我将在另一篇文章中说明游戏的画面是如何工作的&#xff0c;但是现在我先声明一下的就是&#xff1a;在Android中&#xff0c;单个Activit…

case when里的like功能 ////// 截取(substr)

case when里的like功能 假如要用到case when又要用到like这样的功能&#xff0c;即如果字符串包含‘语文’就怎么怎么样&#xff0c;包含‘数学’就怎么怎么样&#xff0c;包含‘英语’就怎么怎么样&#xff0c;like是用于where中的&#xff0c;放在case when里面是无效的&…

在Asp.Net MVC中设定site路径所对应的默认action

设置路由的default的Controller和Action可以达到我们预期的效果&#xff0c;代码如下所示&#xff1a; public class RouteConfig {public static void RegisterRoutes(RouteCollection routes){routes.IgnoreRoute("{resource}.axd/{*pathInfo}");routes.MapRoute(n…

【蓝桥java】递归基础之输出连续数字

题目&#xff1a;使用递归方法输出连续的数字 代码实现&#xff1a; package xn.zzunit.recurrence;/*** 递归方法输出连续的数字* author tyrantForever**/ public class Project2 {public static void main(String[] args) {printNumber(2, 9);}public static void printNu…

慢慢学Linux驱动开发,第十章,GNU C的扩展

内核开发者使用的C语言涵盖了ISO C99标准和GNU C扩展特性。这里简单介绍一下GNU C的扩展特性。 1.内联&#xff08;inline&#xff09;函数 GNU的C编译器支持内联函数&#xff0c;也是C的一个特性之一。就是函数会在所调用的位置上展开&#xff0c;这样做虽然会导致代码量的增加…

vMA学习笔记之一:将vMA加入域

目的&#xff1a; 将vMA加入域的方法 操作步骤&#xff1a; 1、 开启vMA 2、 按住AltF2切换到虚拟终端界面&#xff0c;使用vi-admin用户登录 2 3、 已经登录进来了 4、 在进行加域操作之前&#xff0c;必须确保DNS配置正确&#xff08;在初始化安装的时候会提示你设置DNS&…