经过两天的VSTS培训,一个感觉就是VSTS提供的一系列方案结合在整个项目开发流程中(除了需求分析)确实是比较完美的。先前一直苦于没有足够的内存安装TFS,所以直到这两天才体会到在VS2005之上再配合TFS使用的感觉。
- 对个人:Work Item工作项无处不在。从Task到Bug都算是一个Work Item,也就是说开发人员可以把他具体的函数代码、某个测试用例和BUG等都结合到Work Item中,经理、架构师或测试人员也通过Work Item结合到这个流程中,一起完成一个简单的开发流程。当然,这里说的是按微软默认的MSF Agile方案来说这个开发流程的,如果你喜欢RUP、CMM等其他方案,也可以进行自定义。对于MSF Agile,Work Item是其理念的关键。正是通过Work Item,MSF中定义的开发人员、测试员、项目经理和架构师才有了明确的职责定义、顺畅有效的沟通管道、互动且可控的开发流程。
- 对团队:项目每个阶段的实时追踪。经理或架构师,甚至开发人员和测试人员本身都可以通过各种列表报表数据(BUG报表、任务工作项报表、各阶段文档报告、代码分析和测试报告等)得到自己所需要的当前项目信息。这点对于把握控制项目进度、适时进行变更和监管、日后的维护等都起到非常好的作用。其中,各种列表报表数据除了可以以Web报表显示外,还可以以Office常用文档格式输出,同时可结合WSS进行文档管理。
- 其他:几个细节。
- Source Control(源代码通过它保存在SQLServer2005中)的Check In/Out 策略。通过制定各种策略保证代码库中代码的可编译和质量。
- 可以通过提供的MSF Agile模版自定义其他开发过程模版。使用Process Template Manager进行导出后,修改相应的文档模版或XML定义数据,然后再导入即可。
- 工作项列表可导出到Excel文件或Project文档中进行修改。
- 充分利用Web Test和Load Test对WebApplication模拟各种网络、硬件和软件环境进行测试。
总之,新增的东西太多,每一个环节都足够让人体会上几天。还好有人来提点下,这样对TFS也有了一个总体的概念,不然自己摸索还真难办。第一次碰到安装微软的产品是这样心里没底的,呵呵。
BTW:神舟六号发射成功,庆祝下。
在前面发表的一篇〈GridView中显示数据库里的图片〉里提到ASP.NET 2.0里提供的几个Starter Kits fot VS2005,于是有人MSN问,几个Starter Kits应用程序均以“.VSI”扩展名的文件提供,这是个什么样的扩展名?为什么我们可以直接安装,并可以把Starter Kits应用程序项目直接嵌入VS2005中的新建项目模版里?
确实是个有趣的问题。VSI,如果我没理解错的话,应该是Visual Studio Installer的缩写。那么,.VSI文件到底如何实现在VS2005里嵌入项目模版的呢?这个要从VS2005提供的“Export Template”说起了。Export Template项在File菜单里。我们动手从一个例子看起吧。
1、新建一个ASP.NET Web Site。并编写你打算作为日后模版项目里固有功能的代码,也可以放置任何文件夹和文件,比如图片等。这个Web Site在后面将成为VS2005里的一个项目模版,类似Starter Kits里的那几个示例。下面是演示项目的一张图片,供一会与利用该模版新建的项目对比,看是否完全一致。

2、保存项目。然后在File菜单中选择“Export Template”,弹出如下对话框。我们选择第一项“Project template”。最下面的模版类型选择“Visual C#”,表示创建的是C#的ASP.NET Web Site模版。当然你可以依照你喜好选择VB.NET。

3、选择下一步Next。进一步设置模版图标、模版名称、说明等。这些基本属性信息将作在VS2005里显示给用户看。

4、按“Finish”结束导出过程。同时如果你选择“自动嵌入Vs2005”,按Finish后,你在新建WebSite项目的窗口里,就可以看到你刚才所创建的项目模版,如下图。如果你选择最下面的“Display an explorer......”还会自动帮你打开输出目录。在选择利用这个WebSite1项目模版创建的项目,将自动包含第一张图里所列的所有文件夹和文件和所有代码等,即完全是第一张图项目的克隆版本。

至此,完成了Starter Kits里最终一样的效果。但是.VSI的疑问还是没有解开。别急,其实我们已经快解开这个VSI疑团了。现在,进一步再看刚才我们导出的项目模版文件WebSite1.zip,默认在C:\Documents and Settings\Administrator\My Documents\Visual Studio 2005\My Exported Templates目录下。我们发现刚才导出的文件居然是个.ZIP的压缩文件,于是很自然想到解压缩看下里面有些什么东西。解压缩后发现除了原本项目里所有的文件夹和文件外,还多了一个模版图标__TemplateIcon.ico和一个名为MyTemplate.vstemplate的文件。利用记事本打开MyTemplate.vstemplate,里面内容为一份描述这个项目模版信息的XML数据文档。(文档内容略)。
正是这份XML文档让我有了一个想法,即利用UltraEdit打开StarterKits里的TimeTracker.VSI文件察看内部结构,发现果然是个ZIP压缩包文件。于是将StarterKits里的TimeTracker.vsi改名为TimeTracker.ZIP,顺利对TimeTracker.ZIP进行解压缩。TimerTracker压缩包里除了一个跟我们上面制作过程一样格式的ZIP文件外,还包含了一个TimeTracker.vscontent的文件。再用记事本打开这个vscontent文件,真相大白:
<?xml version="1.0" encoding="utf-8" ?>
<VSContent xmlns="http://schemas.microsoft.com/developer/vscontent/2005">
<Content>
<FileName>TimeTrackerCS.zip</FileName>
<DisplayName>Time Tracker Starter Kit (C#)</DisplayName>
<Description>Starter Kit for Web project (General category)</Description>
<FileContentType>VSTemplate</FileContentType>
<ContentVersion>1.0</ContentVersion>
<Attributes>
<Attribute name="TemplateType" value="Project"></Attribute>
<Attribute name="ProjectType" value="Visual Web Developer"></Attribute>
<Attribute name="ProjectSubType" value="CSharp"></Attribute>
</Attributes>
</Content>
</VSContent>
原来.VSI文件内部只有两个文件:一份用于描述文件信息的XML文件和一个利用Export Template导出的ZIP压缩包。
了解了VSI内部结构后,我把TimeTracker.vscontent里的这份XML文件信息更改为描述WebSite1的信息,主要把FileName改为WebSite1.zip,然后顺便把TimeTracker.vscontent也更名为WebSite1.vscontent,然后压缩WebSite1.vscontent和WebSite1.zip文件为一个新的WebSite1.zip(注意只有ZIP压缩文件才被接受),最后把新生成的WebSite1.zip更名为WebSite1.vsi。然后双击WebSite1.vsi,其安装过程和结果完全与StarterKits里的示例一致。至此,VSI疑团告破。
认真想一下这个模版输出功能,确实大有用处。程序员之间可以利用VSI文件进行真正便捷、可配置的代码交流;也可以利用项目模版创建一系列公用的应用程序模版。相信,不久的将来,大部分.NET社区或网站提供的.NET代码示例都会采用类似Starter Kits的做法,也相信大家的代码程序交流将会更加倾向于这种VSI文件之间的交换。
再补充一下:呵呵,发表了以后,再去google查了相关资料。才发现.vsi扩展名文件的安装,其实质是调用C:\Program Files\Common Files\Microsoft Shared\MSEnv目录下的VSContentInstaller.exe进行安装的。所以如果你出现不能直接双击安装.vsi的错误时,可以利用这个命令行文件进行安装。但是我还没有找到可以用来打包生成vsi的工具程序。但根据查阅的资料显示,利用Microsoft.VisualStudio.VSContentInstaller.dll(与VSContentInstaller.exe同目录)里提供的接口似乎可以自己编写程序创建一个vsi打包工具,但尚未经过实际验证,有空再试试。
比较长时间没有更新了,主要最近换了个工作,来到了上海。现在已经在上海安定下来了。所以以后争取经常更新,要是上海有什么聚会也通知通知我下
。时值Visual Studio2005 beta2发布,同时相对于ASP.NET 1.x来说,2.0确实也有不小的变化。所以打算先把所有新增加的控件写写(先写了两个),属于入门级的文章,同时也给自己温故一下ASP.NET。毕竟之前在福州做项目偏重管理,有段时间疏忽了编程技术了,现在来上海再次重回技术路线,是该好好温习温习
。
前阵子有机会接触了MCE2005,利用其SDK做了一个符合TV客户端浏览规格的Web应用,部署后在实际TV里浏览,用遥控器操作界面,感觉还不错。其中发生了“愚蠢”的小插曲让我郁闷了一个下午,就是在部署浏览时,我将Application Url 写成了 ".\Default.aspx",由于是用ASP.NET做的,导致浏览失败,后来才发现要写成 http://192.168.1.1/Default.aspx 方可。
不知道日后随着网络电视的推广,微软会不会做一个类似Mobile Web服务器控件的TV Web服务器控件呢?先期盼着。
在.NET 2.0 PDC或Beta1中,可以看到SqlCommand对象新增了个ExecutePageReader方法,该方法实现了分页读取数据的功能。对于分页读取数据,在ADO.NET1.1中(当然2.0也适合)一般常用动态构造SQL语句实现:
SqlDataReader GetPage(int pageNumber, int pageSize)
{
//pageNumber: 从 0 开始计数的页码
//pageSize: 每页的记录数
String command = String.Format("SELECT * FROM (SELECT TOP {0} * FROM " +
"Products ORDER BY ProductID) AS t1 WHERE ProductID NOT IN " +
"(SELECT TOP {1} ProductID FROM Products ORDER BY ProductID) ",
pageSize * (pageNumber + 1), pageSize * pageNumber);
SqlConnection conn = new SqlConnection("server=.;database=Northwind;Trusted_Connection=yes");
SqlCommand cmd = new SqlCommand(command, conn);
conn.Open();
SqlDataReader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
return dr;
}
有时为更好实现分页性能效果,也可以采用存储过程建立临时表的方式进行数据分页,但原理基本都差不多。在ADO.NET 2.0 PDC/Beta1中,用SqlCommand.ExecutePageReader进行数据分页:
SqlDataReader GetPageReader(int pageNumber, int pageSize)
{
int startIndex = (pageNumber - 1) * pageSize;
String command = "SELECT * FROM Products";
SqlConnection conn = new SqlConnection("server=.;database=Northwind;Trusted_Connection=yes");
SqlCommand cmd = new SqlCommand(command, conn);
conn.Open();
SqlDataReader dr = cmd.ExecutePageReader(CommandBehavior.CloseConnection, startIndex, pageSize);
return dr;
}
最后绑定第2页的5行数据(ProductID从6到10)到GridView1上:
GridView1.DataSource = GetPage(1, 5);
GridView1.DataBind();
从上可以看到,用了ExecutePageReader确实简单了很多。但是,很“不幸”告诉大家,ExecutePageReader在未来版本中将不会出现,也就是说被cut掉了。至于cut的真正原因,还真不知道。不过,我试图用Reflector去看其内部,发现这样一个调用顺序:ExecutePageReader()—》SqlResultSet.CompleteOpenForPageReader()—》SqlResultSet.FetchInternal()—》执行存储过程sp_cursorfetch,也就是实际ExecutePageReader使用了SQLServer的服务器游标进行数据分页读取,但这跟它被cut是否有关?这么好用的一个功能被cut是不是有其他考虑?或许ADO.NET开发小组在鱼和熊掌取舍之间已经做了抉择,只是我还是觉得ExecutePageReader(据说SqlResultSet也被cut,然后连同ExecutePageReader被cut)被cut很可惜。:'(
微软的Web平台开发组最近发布了一个新消息,据称在Visual Studio 2005 Beta2中,ASP.NET2.0中的一些特殊文件夹的名称将有所改变。
ASP.NET 2.0 (Beta2)中最新的特殊文件夹的名称改变如下:
- /Bin 改变为 /Application_Assemblies
- /Code
改变为 /Application_Code
- /Resources
改变为 /Application_Resources
- /WebReferences
改变为 /Application_WebReferences
- /Data
改变为 /Application_Data
- /Browsers
改变为 /Application_Browsers
- /Resources
改变为 /Application_GlobalResources
- /LocalResources
改变为 /Application_LocalResources
- /Themes
改变为 /Application_Themes
上面这些改变还没加入目前最新版本的Beta1 Refresh中,只有在未来的Beta 2中才被实现。
[来源:http://weblogs.asp.net/ksharkey/archive/2004/09/01/224333.aspx]
.NET 2.0中的SqlConnection多了一个StatisticsEnabled属性和ResetStatistics()、RetrieveStatistics()两个方法,用于获取SQLServer的连接统计数据。当然,这样做是以性能损耗为代价的,但作为监控数据库状态的一种快捷实用手段未尝不可。
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ page language="C#" %>
<script runat="server">
void Page_Load(object sender, EventArgs e)
{
string connString = "Northwind的连接串";
SqlConnection conn = new SqlConnection(connString);
conn.StatisticsEnabled = true;
conn.ResetStatistics();
conn.Open();
SqlCommand cmd = new SqlCommand("SELECT * FROM Orders", conn);
SqlDataReader reader = cmd.ExecuteReader();
reader.Close();
conn.Close();
Hashtable ht = (Hashtable)conn.RetrieveStatistics();
foreach (string key in ht.Keys)
{
Label1.Text += "Key: " + key + " = " + ht[key] + "<BR />";
}
}
</script>
<html>
<head id="Head1" runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="Form1" runat="server" autocomplete="on">
<asp:Label ID="Label1" Runat="server" Text=""></asp:Label>
</form>
</body>
</html>
<%@ Import Namespace="System.Data" %>
运行后的结果就是SQLServer连接统计数据结果:
Key: NetworkServerTime = 0
Key: BytesReceived = 156913
Key: UnpreparedExecs = 1
Key: SumResultSets = 1
Key: SelectCount = 1
Key: PreparedExecs = 0
Key: ConnectionTime = 30
Key: ExecutionTime = 30
Key: Prepares = 0
Key: BuffersSent = 1
Key: SelectRows = 830
Key: ServerRoundtrips = 1
Key: CursorOpens = 0
Key: Transactions = 0
Key: BytesSent = 48
Key: BuffersReceived = 20
Key: IduRows = 0
Key: IduCount = 0
- 新增的页面事件:
在ASP.NET 2.0中,一个ASP.NET页面的生命周期主要为(红色字体表示ASP.NET 2.0新增加的阶段页面事件):客户端请求页面—》预初始化(OnPreInit)—》初始化(OnInit)—》完成初始化(OnInitComplete)—》载入ViewState(LoadViewState)—》处理回送数据(IPostBackDataHandler)—》Page_OnPreLoad—》Page_OnLoad—》回发更改通知(RaisePostDataChangedEvent)—》处理回发事件(RaisePostBackEvent)—》Page_OnLoadComplete—》预呈现(OnPreRender)—》完成预呈现(OnPreRenderComplete)—》保存ControlState(SaveControlState)—》保存ViewState(SaveViewState)—》呈现(Render)—》Page_UnLoad。
- OnPreInit:在初始化页面OnInit事件前触发。在这个阶段里,可以进行定义站点主题(Theme)或加载站点个性化所需要的数据信息等操作。
- OnInitComplete:完成初始化页面OnInit事件后触发。
- OnPreLoad:在加载页面OnLoad事件前触发。
- OnLoadComplete:完成页面加载OnLoad事件后触发。
- OnPreRenderComplete:在完成预呈现OnPreRender事件后触发。这是完成页面呈现的最后一道关卡,在此之后,页面将无法再进行任何呈现上的改动。
- SaveControlState:保存控件状态ControlState。ControlState是ASP.NET2.0控件新增的一个属性,类似ViewState作用,但它们区别在于ControlState用于保存更加重要的控件状态信息,以保证在禁用ViewState的情况下还可以对控件状态进行读写操作。
- 增加对页面Header的控制:
System.Web.UI.Page类新增加了Header属性,用于对HTML页面头区域里数据的操作。通过对Header属性的跟踪,可以发现,Header属性保存着一个实现IPageHeader接口的对象(该对象有LinkedStyleSheets、Metadata、StyleSheet和Title四个属性),实际上正是通过这个对象实现对HTML页面头区域里数据的操作的。例如:
<script runat="server">
void Page_Load(object sender, System.EventArgs e)
{
this.Header.Metadata.Add("author", "brooks");
}
</script>
其运行结果为:
<html>
<head> <title>Untitled Page</title>
<meta name="author" content="brooks" />
</head>
- 定义表单中的默认按钮:
在ASP.NET1.0中,我就为了设置表单中的默认按钮而一筹莫展。幸好ASP.NET2.0把这个功能补上了,现在可以非常方便的设置表单中的默认按钮了。
<%@ page language="C#" %>
<script runat="server">
void Button1_Click(object sender, System.EventArgs e)
{
this.LB_Message.Text = "You clicked button1";
}
</script>
<html>
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form runat="server" defaultbutton="Button1">
<asp:textbox id="Textbox1" runat="server"></asp:textbox>
<asp:button id="Button1" runat="server" text="Button" onclick="Button1_Click" />
<asp:label id="LB_Message" runat="server"></asp:label>
</form>
</body>
</html>
- 设置焦点:
现在假设为TextBox1控件设置焦点,在ASP.NET 2.0中可以这样实现:
this.Textbox1.Focus(); 或 this.SetFocus(this.Textbox1); 即可为TextBox1控件设置焦点。
如果打算也为表单设置个默认焦点控件,让光标默认停留在TextBox1上:
<form runat="server" defaultfocus="TextBox1">
- 跨页面数据发送:
如果你需要多个页面发送数据到同一个表单程序进行处理,或者数据在多个页面之间传输处理的话,你就可以使用ASP.NET 2.0这个新特性。例如,我打算把Default.aspx页里TextBox1里的文本数据发送到Default2.aspx页面进行处理:
Default.aspx页:
<%@ Page Language="C#" %>
<script runat="server">
void Button2_Click(object sender, EventArgs e)
{
Label1.Text = "Hi," + TextBox1.Text + ". This is Default.aspx";
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<asp:TextBox ID="TextBox1" Runat="server"></asp:TextBox>
<asp:Button ID="Button1" Runat="server" Text="PostToAnotherPage" PostBackUrl="~/Default2.aspx" />
<asp:Button ID="Button2" Runat="server" Text="PostToSelf" OnClick="Button2_Click" />
<br />
<asp:Label ID="Label1" Runat="server" Text="Label"></asp:Label>
</form>
</body>
</html>
Default2.aspx页:
<%@ Page Language="C#" %>
<script runat="server">
void Page_Load(object sender, System.EventArgs e)
{
TextBox textBox1 = (TextBox)PreviousPage.FindControl("TextBox1");
this.Label1.Text = "Hi," + textBox1.Text + ". This is Default2.aspx!";
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<asp:label id="Label1" runat="server"></asp:label>
</form>
</body>
</html>