Small Business
解读
云南程序员群
齐 林
2007年7月11日
作为云南软件行业的一条老狗(穷困潦倒,除了搞技术以外成天还要耸着鼻子在市场中嗅探商机),我加入了一个群“云南程序员”,里面有连续跳级读书的大拿,也有十多岁就获宝兰公司编程奖的神童,还有云南知名公司的技术核心、策划高手。面对这么多青年才俊让我觉得脸上有点挂不住,决定写点东西撑撑面子,让大家见笑了。
最早学编程的时候有两个误区,第一、谁用
C++
谁是高手,见面总是问“您
C++
了吗?”,这些年改成“您家挖了吗?”。第二、谁知道的
API
函数、控件多谁是高手,估计是受早些年哪个“人无我有”理论影响。
后来才发现衡量高手的标准根本不是这样的。打个不太恰当的比方,
C++
就像草书,
VB
就像宋体,草书搞个艺术展览没问题,但写份公文效果如何?。再说说
API
函数之类的东西,你
GOOGLE
一下连例子代码都有
,
Ctrl+C
就搞定了。用友用
VB
做了个可以支撑公司上市的软件,某公司用
PB
做了个软件卖了最少几百套覆盖云南全境。开发这些软件的人才是高手啊。我心目中的高手是有全局观的、懂软件设计模式并能合理运用的、能以最快速度完成任务的、有持续激情的程序员。
Small Business
是一个非常漂亮而且实用的网站范例,稍微修改一下可以用于大多数小型公司的网站建设。
抱着上述观点,我觉得学习一下
Small Business
很有好处,因为它具备了下面的特点:
1
、漂亮,漂亮总是能让人产生激情,不像那些老学究写的例子,一个丑陋的灰色
Grid
加一些抄来的代码。
2
、实用,稍加改动就是一个不的公司网站。
3
、简单,区区几个表几行代码把高手风范表现得淋漓尽致。
4
、时髦,涉及到了.net 2.0的众多新特性。
5
、最重要的是架构合理,能让我们领略到经典的三层架构。
开始以前我要说明一点,这不是一篇讲述代码技巧的文章,所以文章里面几乎没有任何对代码语句的解释(如果有,估计不是这么几页的篇幅了,而且大家也懒得看)。
下载地址:
http://www.asp.net/downloads/starterkits/default.aspx?tabid=62
一、网站页面结构
'>1
文件说明如下:
1
、Default.aspx:系统的主页文件。
2
、About.aspx:公司介绍。
3
、Contact.aspx::联系方式介绍。
4
、Items.aspx:产品(条目)介绍。它包括一个下级页面ItemDetail.aspx 用于显示产品细节。
5
、
LegalNotice.aspx
:法律声明文件。
6
、News.aspx:新闻报道。
7
、People.aspx:员工介绍。
8
、Testimonials.aspx:客户的评价,作用类似那个哈尔滨的钙片广告“一片抵五片”。
二、网站的构架
典型三层结构:数据访问层(数据库层)、业务逻辑层、表现层。
1
在三层系统中,用户通过表示层是不能直接访问数据库的。三层之间通过接口通信,所以只要保证接口不变,每层内部内容甚至结构的更改并不影响到其它层。
具体到本系统其三层结构可以表示为:
1
通过这个系统的代码分析我们可以轻松发现
.net 2.0
在三层结构中的特色。
下面将介绍其各个层:
〈一〉、数据库结构
数据表关系如下(比较枯燥,我写的时候差点睡着了):
1
各个表的数据字典列表如下:
1、
Categorization
标 识
|
字 段 含 义
|
字 段 类 型
|
属 性 说 明
|
itemId
|
条目ID
|
nvarchar(32)
|
PK
。FK 受到Itme_id 约束
|
parentCategoryId
|
条目父类ID
|
nvarchar(32)
|
PK
。FK 受到Category_id约束
|
2、
Category
标 识
|
字 段 含 义
|
字 段 类 型
|
属 性 说 明
|
id
|
类别ID
|
nvarchar(32)
|
PK
|
visible
|
可见性
|
bit
|
|
title
|
标题
|
nvarchar(256)
|
|
description
|
文字描述
|
nvarchar(1024)
|
|
imageUrl
|
图标地址
|
nvarchar(256)
|
|
imageAltText
|
图标文字
|
nvarchar(256)
|
|
parentCategoryId
|
父类ID
|
nvarchar(32)
|
FK
,受到本id表约束
|
displayOrder
|
显示顺序
|
int
|
|
3、
Item
标 识
|
字 段 含 义
|
字 段 类 型
|
属 性 说 明
|
id
|
条目ID
|
nvarchar(32)
|
PK
|
visible
|
可见性
|
bit
|
|
title
|
标题
|
nvarchar(256)
|
|
description
|
文字描述
|
nvarchar(1024)
|
|
price
|
价格
|
float
|
|
inStock
|
库存情况
|
bit
|
|
imageUrl
|
图标地址
|
nvarchar(256)
|
|
imageAltText
|
图标文字
|
nvarchar(256)
|
|
displayOrder
|
显示顺序
|
int
|
|
4、
News
标 识
|
字 段 含 义
|
字 段 类 型
|
属 性 说 明
|
id
|
新闻ID
|
nvarchar(32)
|
PK
|
visible
|
可见性
|
bit
|
|
title
|
标题
|
nvarchar(256)
|
|
date
|
日期
|
datetime
|
|
content
|
文字内容
|
nvarchar(4000)
|
|
imageUrl
|
图标地址
|
nvarchar(256)
|
|
imageAltText
|
图标文字
|
nvarchar(256)
|
|
displayOrder
|
显示顺序
|
int
|
|
5、
People
标 识
|
字 段 含 义
|
字 段 类 型
|
属 性 说 明
|
id
|
员工ID
|
nvarchar(32)
|
PK
|
visible
|
可见性
|
bit
|
|
title
|
标题
|
nvarchar(256)
|
|
firstName
|
人名字
|
nvarchar(64)
|
|
middleName
|
人名字
|
nvarchar(64)
|
|
lastName
|
人名字
|
nvarchar(64)
|
|
description
|
文字描述
|
nvarchar(2048)
|
|
email
|
电邮地址
|
nvarchar(256)
|
|
phone
|
电话号码
|
nvarchar(64)
|
|
fax
|
传真
|
nvarchar(64)
|
|
streetAddress
|
街道
|
nvarchar(128)
|
|
city
|
城市
|
nvarchar(128)
|
|
state
|
州
|
nvarchar(64)
|
|
postalcode
|
邮编
|
nvarchar(64)
|
|
country
|
国家
|
nvarchar(64)
|
|
imageUrl
|
图标地址
|
nvarchar(256)
|
|
imageAltText
|
图标文字
|
nvarchar(256)
|
|
displayOrder
|
显示顺序
|
int
|
|
6、
Testimonials
标 识
|
字 段 含 义
|
字 段 类 型
|
属 性 说 明
|
id
|
评价ID
|
nvarchar(32)
|
PK
|
visible
|
可见性
|
bit
|
|
title
|
标题
|
nvarchar(256)
|
|
date
|
日期
|
datetime
|
|
content
|
文字内容
|
nvarchar(1024)
|
|
testifier
|
证人
|
nvarchar(256)
|
|
testifierTile
|
证实标题
|
nvarchar(256)
|
|
testifierCompany
|
证实公司
|
nvarchar(256)
|
|
imageUrl
|
图标地址
|
nvarchar(256)
|
|
imageAltText
|
图标文字
|
nvarchar(256)
|
|
displayOrder
|
显示顺序
|
int
|
|
系统数据表很少,也很简单,可是简单并不意味着没有水平。我看过一些MIS、CMS (不乏知名产品),里面类别处理也大都采用Category表的方式,对比一下但看看其FK的设置合理吗?嘿嘿!
〈二〉、数据访问层
1
、数据连结字符串
系统的数据连结字符串被存放在
web.config
中:
<connectionStrings>
<remove name="SQLConnectionString"/>
<add name="SQLConnectionString" connectionString="Data Source=./SQLExpress;integrated security=true;attachdbfilename=|DataDirectory|SmallCompanyDB.mdf;user instance=true"/>
</connectionStrings>
在此解释一下remove 的作用。Asp.net的配置是多级的,
通过XML格式的文件Machine.Config和Web.Config来完成对网站和网站目录的配置。对于一个网站整体而言,整个服务器的配置信息保存在Machine.config文件中。当建立一个新的WEB项目的时候,VS.NET 会自动建立一个Web.config文件,Web.config包含了各种专门针对一个具体应用的一些特殊的配置,比如Session的管理、错误捕捉等配置。一个Web.config可以从Machine.config继承和重写部分备置信息。很明显
remove
消除了这种继承关系。
由于
系统采用的是
SQL Server 2005 Express Edition
用户很可能不知道SQL Server正在被用作数据库,换句话说,有点像access,只要提供一个文件路径和文件名就可以附加并使用数据库了。
DataDirectory
可以理解为一个宏,它的作用是给出了存放数据库的文件夹App_Data的路径。
2
、数据访问组件
下面我们将以访问People表为例分析一下它是如何完成DAL工作的。
打开抽象超类PeopleProvider可以看到下面的代码:
public abstract class PeopleProvider : ProviderBase
{
public abstract List<Person> GetAllPersons();
}
基类
ProviderBase
中值得关注的是ProviderBase类的方法
Initialize()
,在这个方法里面通常可以放置代码读取Web.config的配置项,尽管本系统没有这么做。该抽象类本身只提供了一个读取Person类型List的抽象方法
GetAllPersons()
,其目的在于获得所有人的列表,使用抽象类
PeopleProvider
的目的在于为其继承类sqlPeopleProvider和xmlPeopleProvider中的
GetAllPersons()
预留位置
(当然也是为工厂模式打下伏笔)
。
SqlPropleProvider
和XmlPropleProvider非常类似,目的都是从数据源读取数据,只不过一个是从数据库另外一个是从XML文件而已。下面我们看一下SqlPropleProvider是怎么写的。
SqlPeopleProvider
继承了SqlPeopleProvider从而
覆盖和实现
了
GetAllPersons()
。
SqlPeopleProvider
中包含了一个方法
connectionString()
其返回值是一个连结字符串。
private string connectionString()
{
SmallBusinessDataProvidersSection sec = (ConfigurationManager.GetSection("SmallBusinessDataProviders")) as SmallBusinessDataProvidersSection;
string connectionStringName = sec.PeopleProviders[sec.PeopleProviderName].Parameters["connectionStringName"];
return WebConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;
}
这个连结字符串被存放在Web.config之中:
<
PeopleProviders>
<add name="sqlProvider" type="SqlPeopleProvider" connectionStringName="SQLConnectionString"/>
<add name="xmlProvider" type="XmlPeopleProvider" schemaFile="People.xsd" dataFile="People.xml"/>
</PeopleProviders>
<connectionStrings>
<remove name="SQLConnectionString"/>
<add name="SQLConnectionString" connectionString="Data Source=./SQLExpress;integrated security=true;attachdbfilename=|DataDirectory|SmallCompanyDB.mdf;user instance=true"/>
</connectionStrings>
SqlPeopleProvider
中
GetAllPersons()
是这样写的:
List<Person> personList = new List<Person>();
using (SqlConnection con = new SqlConnection(connectionString()))
{
con.Open();
SqlCommand cmd = new SqlCommand("GetPeople", con);
SqlDataReader r = cmd.ExecuteReader();
Person curr;
while (r.Read())
{
if (r["id"] is DBNull || r["visible"] is DBNull || r["firstName"] is DBNull || r["lastName"] is DBNull)
throw new InvalidOperationException(Messages.PersonRequiredAttributesMissing);
curr = new Person((string)r["id"],(bool)r["visible"],(string)r["firstName"],(string)r["lastName"]);
curr.MiddleName = (r["middleName"] is DBNull) ? String.Empty : (string)r["middleName"];
curr.Title = (r["title"] is DBNull) ? String.Empty : (string)r["title"];
curr.Description = (r["description"] is DBNull)? String.Empty : (string)r["description"];
curr.Email = (r["email"] is DBNull) ? String.Empty : (string)r["email"];
curr.Phone = (r["phone"] is DBNull) ? String.Empty : (string)r["phone"];
curr.Fax = (r["fax"] is DBNull) ? String.Empty : (string)r["fax"];
curr.StreetAddress = (r["streetAddress"] is DBNull) ? String.Empty : (string)r["streetAddress"];
curr.City = (r["city"] is DBNull) ? String.Empty : (string)r["city"];
curr.State = (r["state"] is DBNull) ? String.Empty : (string)r["state"];
curr.PostalCode = (r["postalCode"] is DBNull) ? String.Empty : (string)r["postalCode"];
curr.Country = (r["country"] is DBNull) ? String.Empty : (string)r["country"];
curr.ImageUrl = (r["imageUrl"] is DBNull) ? String.Empty : (string)r["imageUrl"];
curr.ImageAltText = (r["imageAltText"] is DBNull) ? String.Empty : (string)r["imageAltText"];
personList.Add(curr);
}
这段代码主要目的在于执行存储过程GetPeople 并返回一个泛型List。这段代码中
throw new InvalidOperationException(Messages.PersonRequiredAttributesMissing);
用于某些必填字段为空的时候抛出一个异常"Some of the required attributes of Person are NULL in the Persons table"。
〈三〉、业务逻辑层
根据前面所述的三层结构可以发现系统的业务逻辑层是和
ObjectDataSource
联系的,其连结可以见下图:
这表明业务对象之一是
Catalog.cs
,当然
People.cs
、
News.cs
等也都是业务对象。下面我们以
Catalog.cs
为例来学习一下高手们是如何写业务层的。
Catalog
类作为介于
WEB
页面和数据提供层之间的抽象层,它提供了访问条目和类别的静态函数。
Initializ
()的目的是初始化
provider
(
CatalogProvider
静态类),在
InstantiateProvide r
中
第一个参数提供了ProviderSettingsCollection,第二个参数提供了provider的类型。下图表现了
Initializ
()是如何调用Web.config。
1_providersSection.CatalogProviders[_providersSection.CatalogProviderName]不难看他们初始化的CatalogProvider的时候使用了catalogProviderName="xmlProvider",参照Web.config可以发现下面的语句:
<SmallBusinessDataProviders peopleProviderName="xmlProvider" testimonialsProviderName="xmlProvider" catalogProviderName="xmlProvider" newsProviderName="xmlProvider">
所以系统默认情况并没有使用SQL SERVER,而是使用XML数据源,这种做法的好处在于两点,首先如果要使用SQL SERVER 只要把
"xmlProvider"
改为"sqlProvider",非常简单就切换了数据源,这就是.net 2.0之中"Provider"的威力了。其次现在很多托管空间如果支持SQL SERVER 费用是很高的,对于small公司来说数据量并不是很大,使用XML不需要支付额外的数据库租赁费,何乐不为呢?从设计模式的角度看这种手法实际上可以理解为类工厂的惯用技法(自己胡思乱想),下图以Cotalog说明了这种类工厂的技法:
1
在此使用工厂模式的好处上面已经介绍了。当然在这个系统中类工厂的调用被放到了Catalog.cs之中,而类工厂里面决定其产品是SqlCatalogProvider还是XmlCatalogProvider则是由Web.config里面的
catalogProviderName
来决定的。谈了工厂类我们就不得不说一下反射问题了。在本系统中
为了支持运行时确定加载哪一个具体类,需要借助工厂类,它从配置文件中读入一个值以确定应该使用反射加载哪一个类。也许大家要骂我了“那么简单的程序怎么说得那么复杂?”,我也有这种感觉,其实什么工厂类、反射等等的东西大家不搞也罢,程序照样写,天塌不下来。
从表面上看Catalog.cs是业务对象,但事实上业务层涉及到了一系列文件,例如:ProviderSettingsValidation.cs、SmallBusinessDataProvidersSection.cs等等。下面我们以Catalog为例梳理一下业务对象的调用流程。 1
另外
SmallBusinessDataProvidersSection
类也很有.net 2.0的特色
(
.net 1.x
版中,配置节处理程序必须实现接口但现在已经大大简化了
),我们来看一下。这个类明显的比较明显的特点是继承了
ConfigurationSection
故可以进行配置项数据读写操作,请看下面的代码:
[ConfigurationProperty("CatalogProviders")]
[ConfigurationValidatorAttribute(typeof(ProviderSettingsValidation))]
public ProviderSettingsCollection CatalogProviders
{
get { return (ProviderSettingsCollection)this["CatalogProviders"]; }
}
这些代码起到了两个作用:
1
、把属性CatalogProviders绑定到了配置文件Web.config。在没有显式的读写操作的情况下轻松读取和写入配置文件。
2
、绑定了属性验证程序。如果你要查找ProviderSettingsValidation在何处被显式地触发,显然是找不到的,实际上就是在此了。
〈四〉、表现层
以前我们做网页的时候总是整个表格,然后把控件放进去来排版的。不知道从什么时候开始有种说法,DIV+CSS排版网页浏览速度更快、更容易被搜索引擎发现而且更灵活,搜索引擎发现和更灵活是自然的了,更快我就没有验证过了,不管它就算是赶时髦吧,我们也来学学DIV排版吧,当然表现层技巧多多,不是“排版”这样一个简单的说法可以概括的。
1
、也谈主题、皮肤、母版页
应该说,关于主题、皮肤、母版页的文章太多了,我说的远不及作家写的文章。不过还是说一下自己理解吧。从前在.net中制定控件的式样可以把控件的Style指向一个样式表,在.net 2.0中炒作了出了主题和皮肤的概念,主题把一种样式应用到了站点的所有表现对象,而且不用挨个设置。皮肤是主题的子项是主题文件的内容核心,皮肤文件是.skin的文件,只能够应用到服务器控件(似乎没有CSS灵活)。下面我们看看这个系统是怎么组织主题和皮肤的:
显然系统并没有设置任何皮肤文件,估计作者和我一样觉得皮肤怪怪的而且比较死板,不愿意写吧。让我们打开css-content.css文件看看:
#content-main-two-column
{
width:540px;
float:left;
}
到处都是带#号ID选择器,斗胆说一句高手的坏话,这样做似乎不妥,和XHTML与时俱进的思路不吻合。下面还是看看主题(实际上就是CSS)怎么被应用到页面中,打开Web.config可以看到:
<system.web>
<customErrors mode="RemoteOnly" defaultRedirect="CustomError.aspx"/>
<pages theme="Standard"/>
<compilation debug="true"/></system.web>
看来是在Web.config已经指定了。所以在html代码中我们看不到:
link href="css-content.css"
母版页我的理解就是页面模板,一个开了若干个“ContentPlaceHolder”天窗的模板。母版页的ContentPlaceHolder和内容页的 content有着严格的对应关系。
初始化母版页控件;(Init)
2)
初始化内容版页控件;(Init)
3)
初始化母版页;(Init)
4)
初始化内容页;(Init)
5)
加载内容页;(Load)
6)
加载母版页;(Load)
7)
加载母版页控件;(PreRender)
8)
加载内容页控件;(PreRender)
本质看就是三种事件Init、Load、PreRender。但在写程序的时候知道事件的先后顺序却是必须的。
2
、页面布局
系统采用了现在比较流行的DIV+CSS布局方式,下面我们看一下母版页是如何布局的。母版页中设置了三个DIV即TOP INFORMTION、TOP NAV、FOOTER
<div id="top-information">
<div id="logo">
<a href="Default.aspx">Fabrikam, Inc.</a></div>
<div id="top-information-home">
<a href="Default.aspx">Home</a></div>
<div id="top-information-phone">
888-555-0100</div>
</div>
<div id="nav-main">
<asp:Menu ID="Menu1" runat="server" DataSourceID="SiteMapDataSource1" Orientation="Horizontal"
Width="400px" CssClass="menu-main" MaximumDynamicDisplayLevels="0" StaticSelectedStyle-CssClass="StaticSelectedStyle" />
</div>
<div id="footer">
Copyright © 2006 Fabrikam, Inc. <a href="LegalNotice.aspx">Terms of Use</a>
</div>
这些ID在CSS文件中对应了ID选择器,在设计状态由于没有加载CSS所以感觉是块 “大白板”:
打开css-content.css可以看到如下代码:
#top-information {
height:4.5em;
margin-left:auto;
margin-right:auto;
margin-bottom:3px;
color:#999999;
font-size:.85em;
position:relative;
width:760px;
}
#nav-main {
background:#608fc8 url(../../images/bg-nav.png) no-repeat;
margin-left:auto;
margin-right:auto;
width:740px;
padding:.8em 0 1em 20px;
position:relative;
font-weight:bold;
}
#footer {
background:#608fc8 url(../../images/bg-nav.png) bottom left no-repeat;
margin-top:3px;
margin-left:auto;
margin-right:auto;
width:740px;
padding:.8em 0 1em 20px;
position:relative;
color:#a9c0db;
font-size:.8em;
}
ID
选择器定义了DIV的布局和外观,运行一下程序,CSS加载后 “大白板”变“大美人”了。
另外.net 2.0的站点地图也很有特色,鼓吹一下。母版页中有个控件叫SiteMapDataSource1,它是和Web.sitemap关联的,而SiteMapDataSource1又成为menu的数据源。导航菜单就从Web.sitemap被自动生成了。
3
、页面事件代码
下面我们以People.aspx来看看页面代码。先看看这个页面运行时刻的样子。
左侧导航条
其代码如下:
<div id="content-side1-three-column">
<ul class="list-of-links">
<li><a href="About.aspx">About Us</a></li><li class="current"><a href="People.aspx">Our Employees</a></li><li><a href="About.aspx">Our History</a></li><li><a href="Contact.aspx">Contact Us</a></li><li><a href="About.aspx">Locations</a></li></ul>
</div>
这么漂亮的左侧导航是在
css-content.css
文件里面格式化的:
#content-side1-three-column {
width:160px;
font-size:.9em;
}
ul.list-of-links {
border-bottom:1px dotted #B2B2B2;
}
#content-side1-three-column ul.list-of-links,
#content-side2-three-column ul.list-of-links {
margin: 0 0 30px 0px;
}
ul.list-of-links li{
border-top:1px dotted #B2B2B2;
background-image:url(../../images/list-bullet-01-link.gif);
}
ul.list-of-links li.current {
background-color:#E5E5E5;
}
中间内容如下:
这个内容是使用GridView实现的,代码如下:
<asp:GridView ID="GridViewAllNews" runat="server" AutoGenerateColumns="False" DataSourceID="ObjectDataSource1"
AllowPaging="True" PageSize="5" OnRowCreated="GridViewAllNews_RowCreated" BorderWidth="0px"
BorderColor="White">
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:Image ID="Image1" runat="server" ImageUrl='<%# "images/" + (string)Eval("ImageUrl") %>'
AlternateText='<%# (string)Eval("ImageAltText")%>' CssClass="photo-float-left photo-border" />
<h2>
<%# Eval("FirstName").ToString() + " " + Eval("MiddleName").ToString() + " " + Eval("LastName").ToString() %>
</h2>
<h3>
<i>
<%# Eval("Title")%>
</i>
</h3>
<p>
<%# Eval("Description")%>
</p>
<ul>
<li>Phone:
<%#Eval("Phone")%>
</li>
<li>Fax:
<%#Eval("Fax")%>
</li>
<li>Email:
<%# Eval("Email")%>
</li>
</ul>
<p />
<hr />
</ItemTemplate>
</asp:TemplateField>
</Columns>
<PagerSettings Mode="NumericFirstLast" Position="TopAndBottom" />
</asp:GridView>
代码虽多但不复杂,主要是定制了数据源和ItemTemplate,分页也是“自动化”
了。
右侧内容如下:
一段被格式化文本,没啥好说的。下面谈谈这个页面中的一个方法:
protected void GridViewAllNews_RowCreated(object source, GridViewRowEventArgs e)
{
if (e.Row == null) return;//
如果非GridViewRowEvent调用,则不予执行
//
当行能绑定数据(DataControlRowType.DataRow才能绑定数据)并且被绑定数据也不为空的时候
if (e.Row.RowType == DataControlRowType.DataRow && e.Row.DataItem != null)
{
bool visible = (bool)DataBinder.Eval(e.Row.DataItem, "Visible");
e.Row.Visible = visible;//
获得Visible布尔值
}
//
如果指定了图像源则显示图像
string imageUrl = (string)DataBinder.Eval(e.Row.DataItem, "ImageUrl");
if (String.Empty.Equals(imageUrl))
{
//
“Image2”是错误的是个BUG
Image newsImage = (Image)e.Row.FindControl("Image2");
//
预防性异常保护,当控件存在的时候才对其进行属性赋值
if (newsImage != null)
newsImage.Visible = false;
}
}
几乎可以肯定这是作者的一个BUG,因为
Image newsImage = (Image)e.Row.FindControl("Image2");
if (newsImage != null)
newsImage.Visible = false;
是.net的一个惯用伎俩,目的在于当控件不能实
例
化的时候避免触发一个异常,然而Image2是在News里面的这里应该是Image1,看来Ctrl+C 、Ctrl+V人人喜欢高手也不例外。当然一个BUG说明不了什么瑕不掩瑜嘛!
这个方法在GridView RowCreated的时候被调用的。
至此我们几乎解读了
Small Business
大部份精髓,对于一个程序员来说如果能够像这个案例一样:
1、
合理设计数据模型。
2、
合理使用三层架构。
3、
使用反射及类工厂方式提高程序可维护性和适应性。
4、
采用DIV+CSS设计漂亮的页面。
我认为已经合格了。由于我水平有限所以只能为本文取个名字叫《Small Business解读》,我希望大家多拍砖头并写出篇《Small Business深入解读》来,到时候一定拜读,顺便说一句欢迎加入云南程序员群,谈软件、股票还有女人。