Small Business解读

  
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.csSmallBusinessDataProvidersSection.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 &copy; 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深入解读》来,到时候一定拜读,顺便说一句欢迎加入云南程序员群,谈软件、股票还有女人。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值