[教程]ASP.Net MVC入门教程

mikel阅读(766)

Introduction

MVC应该算是一个古老的Design Pattern了,无论是在win form程序还是web程序中,它的应用都是比较广泛的。MVC也是我在学校中学习到的第一个设计模式。终于,可以在ASP.NET中应用了。本文的例子 所用的是ASP.NET MVC Preview 2,可以在这里下载

Create a new MVC project

菜单File->New Project ->ASP.NET Web MVC Application
2.jpg
新创建的项目是一个完整的可以运行的Sample程序。
新创建的MVC Project和传统的Asp.net web application不同,MVC Project包含有如下四个文件夹:

  1. Content Folder : 这个文件夹中放一些支持文件,如CSS等。
  2. Controller Folder :这个文件夹中放所以的Controller文件
  3. Models folder : 这个文件夹存放所有的data model文件,包括:LINQ to SQL DBML文件,Entity文件
  4. Views folder : 存放所有的页面文件,包括master文件。Master等需要被共享访问的需要被放在一个Shared子文件夹中。

Advantages of an MVC-Based Web Application

  1. 把程序分为Model, View和Controller之后,更容易控制程序的复杂性
  2. 没有了传统的Asp.Net中的viewstate和server端的form,使得开发人员可以实现对页面的完全控制。当然也失去了viewstate和server端form带来的各种好处
  3. 支持测试驱动开发

Features of the ASP.NET MVC Framework

  1. 应用程序的业务分离,支持测试驱动开发
  2. 可扩展和支持插件的Framework。开发人员都可以根据自己的需要修改甚至替换ASP.NET MVC Framework的各个component,也可以以插件的形式开发自己的View Engine,URL Routing Policy等各种component。ASP.NET MVC Framework甚至支持依赖注入(Dependency Injection)和控制反转(Inversion of Control)等容器模式。
  3. 强大的URL-Mapping功能。使得URL地址更有意义(REST)。URL中不再包括文件扩展名。
  4. 对很多传统Asp.Net特性的支持。如<%=%>, user control等。

     

The MVC Framework and Postbacks

Asp.Net MVC 不再使用传统的Asp.Net Web Application的postback模式。取而代之的是,所有的客户端发回服务器端的request都会被映射到某一个controller类中, 这使UI logic和business logic得以分离,从而有助于提高程序的可测试性。

Understanding the MVC Project Execution Process

Request被发回服务器端之后,首先都由UrlRoutingModule对象来解析这个Request,并根据URL找到一个匹配的Router对象,之后由这个Router对象来处理这个Request。 MVC Application的处理流程:

  1. Initial Request: routers在Global.ascx中被添加到RouteTable中。
  2. Routing: UrlRoutingModule找到匹配的Router对象,决定使用哪个controller,调用哪个action。
  3. Map to controller: MvcRouteHandler会尝试通过routedata来创建controller的type name.
  4. Create Controller
  5. Execute Controller

[代码]Object/DataSet Relational Mapping(对象/数据集关系映射)补

mikel阅读(688)

  1using System;
  2using System.Data;
  3using System.Configuration;
  4using System.Collections;
  5using System.Web;
  6using System.Web.Security;
  7using System.Web.UI;
  8using System.Web.UI.WebControls;
  9using System.Web.UI.WebControls.WebParts;
 10using System.Web.UI.HtmlControls;
 11
 12using IBatisNet.DataMapper;
 13using System.Reflection;
 14
 15/// <summary>
 16/// ODRM为结合ORM与DataSet,并自动根据O和DataSet生成对象,以便业务层处理
 17/// </summary>

 18public partial class ODRM_test : PageBase
 19{
 20    protected void Page_Load(object sender, EventArgs e)
 21    {
 22        if (!IsPostBack)
 23        {
 24            DataSet set11 = Mapper.Instance().QueryForDataSet("SelectXTM_UserByKey_Test",UIhashtable);
 25            DataTable table1 = ConvertDataTable(set11, "");
 26            //这里为自己定义的序列化类
 27            cXTM_User[] objModel = new cXTM_User[table1.Rows.Count];
 28            //DataTable转化为序列化类数组
 29            for (int y = 0; y < table1.Rows.Count; y++)
 30            {
 31                objModel[y] = new cXTM_User();
 32                DataTableConvertObject(table1.Rows[y], objModel[y]); 
 33            }

 34            //以DataSet模式绑定
 35            ExDataGrid1.DataSource = table1;
 36            //以序列化对象模式绑定
 37            //ExDataGrid1.DataSource = objModel;
 38            ExDataGrid1.DataBind();
 39        }

 40    }

 41
 42    protected void ExDataGrid1_ItemDataBound(object sender, DataGridItemEventArgs e)
 43    {
 44        /*
 45         * 该部分应用范围
 46         * 查询一条数据的修改,可以用objModel.UserName
 47         * 而不必再使用DataTable[0].Rows[0]["UserName"]的模式
 48         * 提高面向对象的程度,并减少业务流程部分编码
 49         */

 50
 51        if (e.Item.ItemIndex != 1)
 52        {
 53            cXTM_User objModel = new cXTM_User();
 54            
 55            //如果为DataSet填充的DataGrid
 56            if (e.Item.DataItem.GetType().FullName == "System.Data.DataRowView")
 57            {
 58                DataTableConvertObject((DataRow)((DataRowView)e.Item.DataItem).Row, objModel);      
 59            }

 60            //否则认为为序列化对象填充
 61            else 
 62            {
 63                objModel = (cXTM_User)e.Item.DataItem; 
 64                   
 65            }

 66        }

 67    }

 68
 69    #region 指定对象函数
 70    /// <summary>
 71    /// 数据集中一行DataRow转换为指定对象,并填充数据
 72    /// </summary>
 73    /// <param name="row">数据集中一行</param>
 74    /// <param name="objModel">指定对象</param>

 75    private void DataTableConvertObject(DataRow row, cXTM_User objModel)
 76    {
 77        Hashtable hTable = new Hashtable();
 78        hTable = DataRowConvertHashtable(row);
 79        Type entitytype = Type.GetType(objModel.GetType().AssemblyQualifiedName);
 80
 81        for (int j = 0; j < objModel.Propertylist.Length; j++)
 82        {
 83            PropertyInfo propertyinfo = entitytype.GetProperty(objModel.Propertylist[j]);
 84            propertyinfo.SetValue(objModel, hTable[objModel.Propertylist[j]], null);
 85        }

 86    }

 87
 88    /// <summary>
 89    /// 对象转换为哈希表
 90    /// </summary>
 91    /// <param name="objModel">有数据的对象</param>
 92    /// <returns>填充数据后的哈希表</returns>

 93    public Hashtable ObjectConvertHashtable(cXTM_User objModel)
 94    {
 95        Hashtable hTable = new Hashtable();
 96        Type entitytype = Type.GetType(objModel.GetType().AssemblyQualifiedName);
 97        for (int j = 0; j < objModel.Propertylist.Length; j++)
 98        {
 99            PropertyInfo propertyinfo = entitytype.GetProperty(objModel.Propertylist[j]);
100            hTable.Add(objModel.Propertylist[j], propertyinfo.GetValue(objModel, null));
101        }

102        return hTable;
103    }

104
105    /// <summary>
106    /// 对象转换为DataTable,并有单行DataRow
107    /// </summary>
108    /// <param name="objModel">有数据的对象</param>
109    /// <returns></returns>

110    public DataTable ObjectConvertDataTableWidthRow(cXTM_User objModel)
111    {
112        return ObjectConvertDataTableWidthRow(objModel, "");
113    }

114
115    /// <summary>
116    /// 对象转换为DataTable,并有单行DataRow
117    /// </summary>
118    /// <param name="objModel">有数据的对象</param>
119    /// <returns></returns>

120    public DataTable ObjectConvertDataTableWidthRow(cXTM_User objModel, string DataMapper)
121    {
122        Type entitytype = Type.GetType(objModel.GetType().AssemblyQualifiedName);
123        DataTable dt = new DataTable();
124        if (DataMapper != "")
125        {
126            dt = new DataTable(DataMapper);
127        }

128        dt.Columns.Clear();
129        for (int j = 0; j < objModel.Propertylist.Length; j++)
130        {
131            PropertyInfo propertyinfo = entitytype.GetProperty(objModel.Propertylist[j]);
132            dt.Columns.Add(new DataColumn(objModel.Propertylist[j], propertyinfo.GetType()));
133        }

134        DataRow row = dt.NewRow();
135        for (int j = 0; j < objModel.Propertylist.Length; j++)
136        {
137            PropertyInfo propertyinfo = entitytype.GetProperty(objModel.Propertylist[j]);
138            row[objModel.Propertylist[j]] = propertyinfo.GetValue(objModel, null);
139        }

140        dt.Rows.Add(row);
141
142        return dt;
143    }

144
145    /// <summary>
146    /// 对象转换为DataTable,并有多行DataRow
147    /// </summary>
148    /// <param name="objModel">有数据的对象</param>
149    /// <returns></returns>

150    public DataTable ObjectConvertDataTableWidthRows(cXTM_User[] objModel)
151    {
152        return ObjectConvertDataTableWidthRows(objModel, "");
153    }

154
155    /// <summary>
156    /// 对象转换为DataTable,并有多行DataRow
157    /// </summary>
158    /// <param name="objModel">有数据的对象</param>
159    /// <returns></returns>

160    public DataTable ObjectConvertDataTableWidthRows(cXTM_User[] objModel, string DataMapper)
161    {
162        Type entitytype = Type.GetType(objModel.GetType().AssemblyQualifiedName);
163        DataTable dt = new DataTable();
164        if (DataMapper != "")
165        {
166            dt = new DataTable(DataMapper);
167        }

168        if (objModel.Length == 0)
169        {
170            return dt;
171        }

172        dt.Columns.Clear();
173        for (int j = 0; j < objModel[0].Propertylist.Length; j++)
174        {
175            PropertyInfo propertyinfo = entitytype.GetProperty(objModel[0].Propertylist[j]);
176            dt.Columns.Add(new DataColumn(objModel[0].Propertylist[j], propertyinfo.GetType()));
177        }

178
179        for (int i = 0; i < objModel.Length; i++)
180        {
181            DataRow row = dt.NewRow();
182            for (int j = 0; j < objModel[i].Propertylist.Length; j++)
183            {
184                PropertyInfo propertyinfo = entitytype.GetProperty(objModel[i].Propertylist[j]);
185                row[objModel[i].Propertylist[j]] = propertyinfo.GetValue(objModel[i], null);
186            }

187            dt.Rows.Add(row);
188        }

189        return dt;
190    }

191    #endregion

192
193    #region 通用函数
194
195    /// <summary>
196    /// 转换为DataTable
197    /// </summary>
198    /// <param name="Source">数据源</param>
199    /// <param name="DataMember">数据表名称</param>

200    public static DataTable ConvertDataTable(object Source, string DataMember)
201    {
202        DataTable baseTable = new DataTable();
203        if (Source is DataTable)
204        {
205            baseTable = (DataTable)Source;
206            return baseTable;
207        }

208        if (Source is DataSet)
209        {
210
211            DataSet set1 = (DataSet)Source;
212            if ((set1.Tables.Count > 1&& ((DataMember == null|| (DataMember == "")))
213            {
214                throw new Exception("If there is more than one table in your dataset, you must define the DataMember property to specify which table to use.");
215            }

216            if (set1.Tables.Count < 1)
217            {
218                throw new Exception("There are no tables in the datasource.");
219            }

220            if ((DataMember != null&& (DataMember != ""))
221            {
222                baseTable = set1.Tables[DataMember];
223                return baseTable;
224            }

225            else
226            {
227                baseTable = set1.Tables[0];
228                return baseTable;
229            }

230
231        }

232        return baseTable;
233    }

234
235    /// <summary>
236    /// 返回DataTable为哈希表键值对
237    /// </summary>
238    /// <param name="SourceTable">数据行对象</param>
239    /// <returns>填充后哈希表</returns>

240    public static Hashtable DataRowConvertHashtable(DataRow SourceRow)
241    {
242        Hashtable hTable = new Hashtable();
243        IList list = SourceRow.ItemArray;
244        object[] tObj = new object[SourceRow.Table.Columns.Count];
245
246        for (int i = 0; i < SourceRow.Table.Columns.Count; i++)
247        {
248            tObj[SourceRow.Table.Columns.IndexOf(SourceRow.Table.Columns[i].ColumnName)] = SourceRow.Table.Columns[i].ColumnName;
249        }

250
251        for (int x = 0; x < list.Count; x++)
252        {
253            hTable.Add(tObj[x].ToString(), list[x]);
254        }

255        return hTable;
256    }

257
258    #endregion

259
260
261
262
263}

264

[问题]插入SQLServer2005中文字符乱码问题

mikel阅读(711)

插入条记录
  发现回显的全部都是 ?????….
  乱码….
  仔细查看了半天
  发现数据库的排序规则是德语…
  修改方法:数据库属性->选项->排序规则
  设置成 CHINESE_PRC_CI_AI 就可以了

[教程]log4net简明手册

mikel阅读(922)

常见面,却不怎么用,究其原因还是觉得太复杂了点。不过,这东西出现次数越来越频繁,也只好写点东西,以备后用。本文仅对 Log4net 的使用做个简要说明,所有涉及到扩展和开发的部分一概忽略。
使用 Log4net,需要熟悉的东东有 Logger、Appender 以及 Layout。Logger 是日志记录器,我们使用其相关方法来完成日志记录;Appender 用于设置日志的存储方式和位置,Logger 的配置中会绑定一个或多个 Appender;Layout 关联具体的 Appender,用于设置日志字符串的格式。
1. Logger
所有的记录器都必须实现 ILog 接口,该接口提供日志记录所需的大量方法。
public interface ILog : ILoggerWrapper
{
void Debug(…);
void Error(…);
void Fatal(…);
void Info(…);
void Warn(…);
bool IsDebugEnabled { get; }
bool IsErrorEnabled { get; }
bool IsFatalEnabled { get; }
bool IsInfoEnabled { get; }
bool IsWarnEnabled { get; }
}
通常情况下,我们通过 LogManager.GetLogger() 来获取一个记录器。LogManager 内部维护一个 hashtable,保存新创建 Logger 引用,下次需要时直接从 hashtable 获取其实例。
ILog log = LogManager.GetLogger(this.GetType());
log.Debug(“aaaaaaaaaaaaaaa”);
所有 Logger 的参数设置都直接或间接继承自 root,其继承关系类似 namespace。比如,名为 “MyLogger.X.Y” 参数设置继承自 “MyLogger.X”。当我们创建 “MyLooger.X.Y” 记录器时,会在配置文件找该名称的记录器设置,如果没找到,则按继承关系向上查找,直到 root。因此,在创建 Logger 时,我们通常使用类型名称做为记录器的名字,缺省情况下,它会使用 root 或某一个父配置,但在需要的时候,我们随时可以为具体的类型添加一个更加 “详细” 的配置。















在创建 Logger 设置时,需要注意 “level” 参数。Log4net 允许我们通过该参数调整日志记录级别,只有高于或等于该级别的日志才会被记录下来。比如在代码调试阶段,我们可能希望记录所有的信息,而在部署阶段,我们只希望记录级别更高的错误信息。这个参数的好处是允许我们在不修改代码的前提下,随时调整记录级别。
(高) OFF > FATAL > ERROR > WARN > INFO > DEBUG > ALL (低)
“appender-ref” 参数用于绑定一个或多个具体的 Appender。












2. Appender / Layout
Log4net 提供了大量的 Appender,最常用的包括 AdoNetAppender、AspNetTraceAppender、ConsoleAppender、FileAppender、 OutputDebugStringAppender。每种 Appender 都有特定一些参数,使用时直接从 《Log4net 手册》的示例中拷贝过去,就OK了。(代码摘自 Log4net 手册)
(1) AspNetTraceAppender





(2) ConsoleAppender





(3) OutputDebugStringAppender





(4) FileAppender







有关 Layout 详细信息,请参考 Log4net 相关文档,本文不做详述。
3. Configuration
Log4net 的配置方式十分灵活,即可以写到应用程序配置文件中,也可以使用独立配置文件。同时它还提供了监测配置文件变化的功能,这样我们随时可以调整配置,而无须重启应用程序。
(1) 使用 app.config / web.config
app.config / web.config















使用代码初始化配置。
log4net.Config.XmlConfigurator.Configure();
(2) 使用自定义配置文件
test.log4net












使用代码初始化配置。
log4net.Config.XmlConfigurator.Configure(new FileInfo(“test.log4net”));
使用 XmlConfigurator.ConfigureAndWatch() 方法除了初始化配置外,还会监测配置文件的变化,一旦发生修改,将自动刷新配置。
(3) XmlConfiguratorAttribute
我们还可以使用 XmlConfiguratorAttribute 代替 XmlConfigurator.Config()/ConfigureAndWatch(),ConfiguratorAttribute 用于定义与 Assembly 相关联的配置文件名。
方式1: 关联到 test.log4net,并监测变化。
[assembly: log4net.Config.XmlConfigurator(ConfigFile=”test.log4net”, Watch=true)]
方式2: 关联到 test.exe.log4net (或 test.dll.log4net,文件名前缀为当前程序集名称),并监测变化。
[assembly: log4net.Config.XmlConfigurator(ConfigFileExtension=”log4net”, Watch=true)]

[教程]log4net配置与应用

mikel阅读(841)

log4net 配置与应用

log4net是apache组织开发的日志组件, 同其姐妹log4j一样, 是一个开源项目. 可以以插件的形式应用在你的系统中. 下面仅说明如何应用在web forms项目中. 做为主要的日志输出组件.
1. 首先你应该下载log4net.dll并引入到你的项目References中.
2. 需要修改你的global.asa.cs. 配置application对象启动的时候加载log4net配置. 这一步是不可以缺少的.

protected void Application_Start(Object sender, EventArgs e)
{
   log4net.Config.DOMConfigurator.Configure();
}

3. 可以看到上面的代码没有参数. 可见是载入了缺省配置. 该配置必须设置于web.config中.
在web.cofig根节点 configuration 中加入如下section:

<configSections>
  <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>

4.该 config section 声明了名为 log4net 的另外一个config section. 后者必须位于web.config根节点 configuration 下: 以下是一个sample:

<log4net debug="false">
    <appender name="LogFileAppender" type="log4net.Appender.FileAppender" >
        <param name="File" value="XxxxApplication.log.txt" />
        <param name="datePattern" value="MM-dd HH:mm" />
        <param name="AppendToFile" value="true" />
        <layout type="log4net.Layout.PatternLayout">
            <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] - %m%n" />
        </layout>
    </appender>
    <appender name="HttpTraceAppender" type="log4net.Appender.ASPNetTraceAppender" >
        <layout type="log4net.Layout.PatternLayout">
            <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] - %m%n" />
        </layout>
    </appender>
    <appender name="EventLogAppender" type="log4net.Appender.EventLogAppender" >
        <layout type="log4net.Layout.PatternLayout">
            <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] - %m%n" />
        </layout>
    </appender>
    <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
        <param name="File" value="_LogData\Log.txt" />
        <param name="AppendToFile" value="true" />
        <param name="MaxSizeRollBackups" value="10" />
        <param name="MaximumFileSize" value="5MB" />
        <param name="RollingStyle" value="Size" />
        <param name="StaticLogFileName" value="true" />
        <layout type="log4net.Layout.PatternLayout">
            <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] - %m%n" />
        </layout>
    </appender>
    <root>
        <level value="DEBUG" />
        <appender-ref ref="LogFileAppender" />
    </root>
</log4net>

5. 以上定义了多个appender. 简单来说, 每一个 appender 都是一种输出介质.
6. root节点指定了选用的 appender. 本例选用了LogFileAppender. (文本文件输出). 在Appender定义中定义了输出的格式. 和目标文本文件所在位置. (起始位置是应用程序根目录. (web.config所在目录).
7. 到目前位置就配置好了log4net. 可以在我们的应用中直接使用了.
8. 以下说明应用方法:
要输出日志, 必须首先得到带有一个别名的logger.
使用以下命令
(C#):
log4net.ILog Logger logger = log4net.LogManager.GetLogger(this.GetType());
(可以直接使用GetType得到当前类名)
之后调用
logger.Info(string message);
logger.Error(string message);
logger.Debug(string message);
即可输出日志.
调试后可查找应用程序根目录下是否已经自动创建XxxxxApplication.log.txt文本文件.以及是否正确输出了日志.
log4net是一个非常完善的日志组件. 有着强大的可配置性. 有助于提高开发效率.
关于log4net的配置. 可参考apache组织的官方文档位于
http://logging.apache.org/log4net

[问题]未将对象引用设置到对象的实例

mikel阅读(685)

最近编写程序进行单元测试时发现提示:
“未将对象引用设置到对象的实例”的错误
搜索了一下网上的答案,还是没有好的结果,不过我将提示的string变量进行了赋值,问题解决了,大概是因为没有对变量进行初始化,造成使用时空对象引用的问题。

[原创]翻译:Dropthings.com的Web Portal开发学习笔记

mikel阅读(1156)

最近一直在研究Dropthings的架构以及源码,先暂且写个日志,后续内容不断更新
准备翻译Dropthings项目架构的作者写的这本书,不过翻译书比看书难度大多了,看书只需要领会精神就行了,但是翻译书就需要措词和表达能力同时需要让读者能够看懂就不是简单的英语水平能力了,希望不要误人子弟,呵呵…..:

数据库文件下载:
点击下载此文件
目录:
1.简介:
2.Web Portal和Widgets(部件)架构
整个应用程序有清晰的三层架构组成,分别是UI层、业务逻辑层和数据访问层:

Web表示层:
包含Web页面、Web服务、资源(图片,CSS,JavaScript和resx文件)和配置文件。
业务逻辑层:
提供实体类、业务逻辑和中间层数据缓存
数据访问层:
对数据库和数据源的数据库访问和连接封装成了一个接口。同时实现了对实体类与数据库行的映射的工厂类。
Dropthings使用了.NET3.0和.NET3.5的新功能,Web层使用ASP.NET AJAX的RIA技术、业务逻辑层使用新的WF(WorkFlow)工作流实现复杂的业务逻辑的处理。整体架构使用Linq进行数据持久操作。
C#3.0语言的新特性和Linq使得操作集合、数据库行和XML变得容易。WF使得操作赋值的业务流程变得简单。Linq to SQL 应用在业务逻辑层和数据访问层。尽管简单的增、删、改功能在数据访问层进行了封装,更多的请求要求业务逻辑层的响应速度快。这就是为什么Linq to SQL贯彻整个业务层和数据访问层的原因。
2.Web Portal和Widgets(部件)架构
任何Web Protal的核心是它支持Widgets(部件)并且支持用户自定义起始页面,意思就是用户可以提供自定制的服务,不管是公司的一个部门还是第三方。
书中所说的Defaut.asp就是我们所说的一个ASP.NET实现的用于显示widgets和允许用户添加、删除、移动、自定义并且这些操作不会刷新的主页。应用程序记录下用户自定义的页面的样式。Web Protals允许非注册用户使用添加widgets、编辑、删除、创建页面和设置参数。
一个Dropthings的Widgets就是一个ASP.NET的Web Control。可以是一个用户控件也可以是服务器端控件并且符合ASP.net的生命周期规则。Widgets支持PostBacks、ViewState、Sessions和Caches。唯一不同的地方是所有Widgets都实现了IWidget接口来调用核心框架提供的各种服务。一个自定义的AJAX Control实现了drag-and-drop功能。
每个Widget都包含在框架和容器中,容器(container)提供具有标题、编辑链接、最大、最小化和关闭按钮的标题栏。Widget被加载在标题栏下方的主体部分。事件包括改变标题、编辑链接的click事件、最大、最小和关闭按钮事件调用IWidget接口。
Web Portal实现了异步的请求和异步数据处理,使得用户很少刷新页面。Widgets是一种支持ASP.NET请求机制的控件。因此Dropthings的核心就是Widget,不久你就会感觉到所有Widget都是包含在UpdatePanel中并且全部支持异步请求。虽然你可以不用注册就可以使用自定义Widget,哪怕你在不同的计算机上再次登录也会显示你先前自定义的页面布局。ASP.NET的membership(会员管理)和profile(配置)允许匿名用户有持久状态知道注册用户后进行转换。页面和Widget状态存储在数据库表中。
Object Model 对象模型
ASP.NET的membership包含User和Role。如果用户创建了一个或多个页面,每个页面包含一个或多个Widget实例。Widget实例和Widget不同之处类似于面向对象中的类和对象,例如,Flickr图片Widget是一个用于加载Flickr图片的Widget,当一个用户添加它到一个页面后,也就是实例化了一个Widget成为了一个实例。本书中所说的widget实际上都是widget实例。
如图所示:

Application Components 应用程序组件
Dropthings使用门面模式来提供一个统一的业务逻辑接口。实现对子系统的访问处理用户、页面、widgets和etc。门面命名为DashboardFacade,结构如图:

Web层Default.aspx调用DashboardFacade执行添加Tab或Widget、存储Widget状态。DashboardFacade调用具体工作流的操作进行处理。工作流利用Window Workflow Foundation进行实际的处理。每个工作流都包含多个活动(Activities)。每个活动类似于只实现一个功能的类。活动使用DatabaseHelper和DashboardDataContext类操作数据库。DatabaseHelper用于执行通用的数据库操作。DashboardDataContext是用于Linq to SQL的类到数据库表之间的映射。
Data Model 数据模型

数据模型中应用了ASP.NET的Membership的数据库表aspnet_Users包含所有用户帐户。
具体各表的内容如下:

  • aspnet_Users:是ASP.NET的membership默认的表。不过这个表只包含未注册用户信息,注册用户信息存储在aspnet_membership表。图中没有显示,因为它和其他表没什么关系。
  • Page:通过UserId与aspnet_users建立主外键关系。
  • Widget:存储Widget的详细内容和信息。存储每个widget的标题和是否动态加载。也存储用户第一次访问时创建的默认设置。
  • WidgetInstance:通过WidgetId和PageID分别与Page和Widget表建立关系。
  • UserSetting:同过UserId与aspnet_Users表建立关系。

具体表的描述如下:

Table 索引类型 描述
Page UserID Nondusted 用户页面利用Where UserID=<ID>进行加载
Page ID Clusted 页面的主键,编辑页面用于查询
 Widget ID Clusted 部件的主键,当一个部件实例创建后关联到部件ID
 Widget IsDefault Nondusted 第一次访问,默认部件被创建到页面上。IsDefault设定哪些部件被创建。
 WidgetInstance ID Clusted 部件实例的主键,用于更新删除部件实例标示实例。
 WidgetInstance UserID Nondusted 用户ID,用于按用户读取部件实例。

备注:

  • 簇集索引使用的是自增加的整形字段。因为SQL Server本身的数据库文件管理就是基于簇集索引的。如果不选择自动增加的主键当Insert和Delete时候比较麻烦。
  • 外键值不是子增加的字段是因为他们不需要增加。

文件结构:
Dropthings是基于C#语言的ASP.NET项目。源码可以从http://www.codeplex.com/dropthings处下载。

Default.aspx:
    控制所有Widget的起始页面。
WidgetService.asmx
   声明通过起始页面对Widget所有操作的Web Service。
Proxy.asmx
   允许Widget引用其他第三方的扩展资源和Web service的接口。
WidgetContainer.ascx
   所有Widget的容器,起到核心框架和实际Widget之间的桥梁作用。
所有Widget在Widget文件夹下,每个Widget都是一个Web Control,用到的图片、css、js文件都保存在Widget中的子文件夹下。
Update Panels
UpdatePanel允许整个站点的任何部分都可以进行AJAX异步请求操作。不过,UpdatePanel最重要的是支持拖拽。使用多个UpdatePanel时,多个异步请求同时更新页面会很慢。当你嵌套使用UpdatePanel会更加复杂。所以设计时一定要注意布局。


Dropthings中,所有Widget包含在一个UpdatePanel(ID=UpdatePanelLayout)中,因为当用户切换Tab时需要整体加载页面。并且每个Tab创建和修改后的页面都包含在另一个UpdatePanel(ID=TabUpdatePanel),因为Tab可以动态增加。从Widget列表中添加Widget也是一个UpdatePanel(ID=AddContentUpdatePanel)。如图所示。
代码如下:
       <!–添加WidgetUpdatePanel–>
        <asp:UpdatePanel ID="AddContentUpdatePanel" runat="server" UpdateMode="conditional">
        <ContentTemplate>
                ….略
        </ContentTemplate>
        </asp:UpdatePanel>
        <!–Tab和内容UpdatePanel–>
        <asp:UpdatePanel ID="UpdatePanelTabAndLayout" runat="server" UpdateMode="conditional">
        <ContentTemplate>
             <!–Tab面板 –>
            <asp:UpdatePanel ID="TabUpdatePanel" runat="server" UpdateMode="conditional">
            <ContentTemplate>          
                <div id="tabs">
                    <!– 组件容器UpdatePanel–>
                    <asp:UpdatePanel ID="UpdatePanelLayout" runat="server" UpdateMode="conditional">
                        <ContentTemplate>
                            <!–自定义的三列UpdatePanel –>
                            <uc3:WidgetPanels ID="WidgetPanelsLayout" runat="server" />
                        </ContentTemplate>
                    </asp:UpdatePanel>
                    </li>
                </ul>           
                </div>             
            </ContentTemplate>
        </asp:UpdatePanel>
   
         </ContentTemplate>
    </asp:UpdatePanel>
这样将所有的Widget都放在一个UpdatePanel中的结果是更新或添加Widget时需要更新整体UpdatePanel。当以不更新时要产生大量的异步请求HTML和JavaScript。比较好的解决办法是没列包含一个UpdatePanel,不是整个Widget区域。当你拖拽一个widget跨列时,只是JavaScript进行页面间的处理,不需要更新updatePanel,只需要告诉服务器哪个widget被移动了。服务器端可以像客户端一样计算widget的坐标,这样除了增加和删除修改新的widget需要进行异步请求,其他时候都不要发送请求。

拖拽操作
有两种方法实现拖拽:Free Form 和Columnwise,Protopage 用FreeForm实现的拖拽,这样的应用依赖绝对坐标,widget可以拖拽到任意位置。iGoogleLive.com 是使用Column-wise实现的拖拽,允许你在列间进行widget的拖拽。Column-wise方式清晰布局widget,大部分web protal都使用这种方式。

实现多列间的拖拽操作,整个页面被分成三列,每列包含一个ASP.NET的Panel控件。Widget可以添加到任意panel中。拖拽功能是自定义的功能。拖动存在两种方式:一种是本列中的排列顺序拖动,一种是列间的拖动。
如果我们在ASP.NET的AJAX框架中实现IDropTarget接口实现每列的拖拽区,每个widget作为一个IDrogSource在列间拖动。更有意思的是能够记录下widget的坐标值。例如,拖拽一个widget下移,widget会自动向上填充空出的地方。同样你移动一个widget到另一个上方,widget会自动下移到移动的widget下方。这些功能都是类似Extenders的实现的,所以你可以简单的实现一个panel的extender,它将实现类似IDropTarget的功能,并记录坐标。
当拖拽完成我们怎么实现异步存储坐标到服务器呢?当我们拖拽完毕,UI界面进行了更新,但是服务器不知道。任何请求操作都会触发服务器端响应因为需要刷新页面或列。服务器需要异步获得坐标值并存储,但是客户端拖拽完毕后根本体会不到坐标已经发送到服务器。另外一个问题是整个form实现拖拽只基于一个extender。Extenders需要依附于Column Panel并作为一个拖拽目标,也就是widget的拖拽句柄允许widget被拖拽到任何目的。
下面的内容我们将分析如何处理添加widget和起始页面的容器。
使用Widget框架

Dropthings使用一个weidget框架提供了widget相关的功能:验证、权限、配置、存储。Widget从这个框架获得这些功能。如图所示。
而且,你不要依赖任何对象就能创建widget。在你本地开发的计算机上不需要整个web portal的源代码就能创建widget。你只需要创建一个ASP.NET2.0的普通站点,创建一个用户控件,让它实现一个标准的请求响应处理,实现一个接口,仅次而已。
不必要担心基于Dropthings框架中的AJAX和javascript。这个框架允许你使用任何ASP.NET2.0的AJAX组件和Ajax Control Toolkit控件。服务器端支持.NET2.0,3.0,3.5利用View state存储临时状态信息。ASP.NET缓存用来缓存widget数据。这种方式比用javascript进行缓存要好得多。
Dropthings框架中使用了ASP.net的membership实现了验证和权限管理。允许widgets加载时获得当前用的配置信息。核心持久化了保存了用户的状态也就是用户的操作,例如对widget的展开、关闭装提以及增加、删除、修改操作。Widget和核心通过一个widget容器进行交互。widget container包含一个widget起到中介的作用。widget container对widget实例提供类似持久化服务和事件响应。page 包含多个wiget container,但是每个widget container只包含一个widget实例。如图

Widget很简单,就是一个标准的web Control,你可以在page_load事件中编写代码。也可以从ASP.net用户控件定义事件。Widgets类似与SharePoint Web Parts,但是比Web Part好的地方是你可以使用ASP.net控件到自定义控件。用户控件用Visual Studio编辑,你不用自定义控件。你也可以创建widget到.ascx文件中,不要编辑成dll或者发布dll到服务器,只需要拷贝ascx文件到web文件夹中。
比如你想显示fickr的照片。可以创建一个web control作为widget来实现。下面的代码实现了当页面加载时调用fickr的service功能:
protected void Page_Load(object sender, EventArgs e)
{
if( !base.IsPostBack )
this.ShowPictures(0);
else
this.ShowPictures(PageIndex);
}
在这个widget中加入linkbutton来用于切换图片,编写linkbutton的onclick时间用来浏览图片,代码如下:
protected void LinkButton1_Click(object sender, EventArgs e)
{
if( this.PageIndex > 0 ) this.PageIndex –;
this.ShowPictures(this.PageIndex);
}
protected void LinkButton2_Click(object sender, EventArgs e)
{
this.PageIndex ++;
this.ShowPictures(this.PageIndex);
}
ASP.NET的页面生命周期类似于普通页面处理。在widget中你可以使用ASP.NET的组件和编写这些组件的事件响应代码。
Container提供了widget容器和框架并且定义了header和body区域。实际上widget是运行时被widget container加载到body区域的。页面上的每个widget都是首先创建一个widget container然后widget container在动态加载widget实例到它的body区域。widget container是框架的一部分,你只需要编写一次。widget开发者不需要编写container因为他们只需要编写widget就行了,container已经定义好了。

widget container是web control当加载页面时候用于动态创建每个widget实例。widget也是一个web control在widget container的Page.LoadControl(.,.)事件中动态加载。实际上widget是被加载到UpdatePanel控件中的。因此,不管什么时候都是widget实例都是发送请求的,widget container没有进行任何请求处理操作。
设计Widget Container
设计好Container的关键问题是如何准确获得UpdatePanels。如何布局ASP.NET到UpatePanel中是个问题。将每个widget container放在一个UpdatePanel中足够处理。但是有个问题就是UpdatePanel中需要动态添加HTML代码。当UpdatePanel更新时将删除现有的HTML代码重新读取ASP.NET组件并重新创建。结果,所有extenders的之前生成的HTML内容将被清除,除非extenders一直在UpatePanel。将extenders放在UpdatePanel中就意味着每次刷新UpdatePanel将初始化并创建一个extenders实例。每次请求处理后UI界面将刷新慢,特别是在页面上使用widget时候。
需要将Header和Body分别放在两个Updatepanel中,这样修改Widget时只刷新Body区域不必刷新Header区域,但是所有的Extender需要加载在Header区域,这样就不必每次都加载Extender来减少处理时间提高响应速度。如图所示。

不过,最好是将UpdatePanel包含在HeaderPanel中,只将标题和LinkButton放在UpdatePanel中,这样不用刷新UpdatePanel时(比如单击LinkButton后)重新创建HeaderPanel区域,仅仅需要更新title和LinkButton就可以了.下面的图显示了将HeaderPanel放在UpdatePanel外的设计:

WidgetContainer 实现很简单。Header区只包含标题、最小化、最大化、隐藏按钮和一个body区域用于动
态加载widget。图2.4所示的Dropthings文件结构中所示的WidgetContainer.ascx定义了WidgetContainer
,代码如下:
Example 2-1. The .ascx content for the WidgetContainer
<asp:Panel ID="Widget" CssClass="widget" runat="server">
<asp:Panel id="WidgetHeader" CssClass="widget_header" runat="server">
<asp:UpdatePanel ID="WidgetHeaderUpdatePanel" runat="server"
UpdateMode="Conditional">
<ContentTemplate>
<table class="widget_header_table" cellspacing="0"
cellpadding="0">
<tbody>
<tr>
<td class="widget_title"><asp:LinkButton ID="WidgetTitle"
runat="Server" Text="Widget Title" /></td>
<td class="widget_edit"><asp:LinkButton ID="EditWidget"
runat="Server" Text="edit" OnClick="EditWidget_Click" /></td>
<td class="widget_button"><asp:LinkButton ID="CollapseWidget"
runat="Server" Text="" OnClick="CollapseWidget_Click"
CssClass="widget_min widget_box" />
<asp:LinkButton ID="ExpandWidget" runat="Server" Text=""
CssClass="widget_max widget_box" OnClick="ExpandWidget_Click"/>
</td>
<td class="widget_button"><asp:LinkButton ID="CloseWidget"
runat="Server" Text="" CssClass="widget_close widget_box"
OnClick="CloseWidget_Click" /></td>
</tr>
</tbody>
</table>
</ContentTemplate>
</asp:UpdatePanel>
</asp:Panel>
<asp:UpdatePanel ID="WidgetBodyUpdatePanel" runat="server"
UpdateMode="Conditional" >
<ContentTemplate><asp:Panel ID="WidgetBodyPanel" runat="Server">
</asp:Panel>
</ContentTemplate>
</asp:UpdatePanel>
</asp:Panel>
<cdd:CustomFloatingBehaviorExtender ID="WidgetFloatingBehavior"
DragHandleID="WidgetHeader" TargetControlID="Widget" runat="server" />
整个widgetContainer定义在命名为:Widget的Panel中。首先headerPanel包含Header区域和
WidgetHeaderUpdatePanel的UpdatePanel。它里面包含标题和最小化、最大化的LinkButton用来改变编辑
区域。 WidgetBodyUpdatePanel的UpdatePanel包含一个运行时创建的Widget,并且包含在一个Panel中,
加载widget 是在Page.LoadControl(..)事件中处理的,并添加到Body panel中。
CustomFloatingBehaviorExtender 用于添加widget的Header并且是整个widget支持拖拽。
3.使用ASP.NET AJAX 构建表示层
4.使用.Net3.5构建持久层和业务逻辑层
5.构建客户端Widgets(部件)
6.优化ASP.NET AJAX
7.创建异步,事务和WebService缓存
8.调试和优化服务器端性能
9.优化客户端性能
10.一般部署、发布问题解答

[教程]人工神经网络入门(0) —— 关于人工神经网络的FAQ

mikel阅读(658)

今天早上发了一篇关于ANN的文章,给大家演示了一个简单的学习AND运算和OR运算的程序,但是发现大家有很多疑问。
所以通过这篇文章,我希望能对大家的一些常见的疑问给予解答
这里的 训练 一词怎么解释?
学会?

大家通过使用这个程序可以发现:ANN在通过训练以后,可以计算出正确的结果,如1 AND 1 = 1,等等。
如果您阅读了代码会发现,程序中并没有给出如何计算1 AND 1的结果,而是将1,1这两个参数传递给经过训练后ANN,然后由ANN自己计算出结果。
在整个过程中,完全是ANN自己通过一定数量的训练从而达到咱们计算AND运算的结果。

貌似很深奥,不知道能够最终达到甚么效果,实现甚么功能,机器人??
如果您能够深入地了解ANN,最终会达到非常好的效果,特别是做AI这块。
比如RoboCup的仿真机器人比赛中就有应用,
还有一个非常有名的游戏《Bug Brain》,它就是一款通过给一个小虫子设计神经网络而可以在复杂的环境中生存的游戏。有兴趣的朋友可以去了解一下:)
关于这个游戏的玩法和攻略,可以参考http://hi.baidu.com/szk8888/blog/item/eb1d033b282ac7ea14cecb42.html
楼主,你要介绍哪种神经网络?我没看出来。。囧。。。
计划在这一系列文章中,介绍单层和多层(BP)神经网络。
人工神经网络入门(1) —— 单层人工神经网络应用示例 这个属于最简单明了的单层神经网络。介绍一个网络的主要原因就是帮助咱们先有一个感性的认识。
让我们知道ANN是什么,如果使用。
神经网络还真的不是很熟悉,楼主能否解释下这里,我看的不是很懂:
“计算结果”显示为“1.74E-10”,说明 0 AND 0 = 0.
在我的程序中,实际输出的结果只是接近于0,这是正常的。
神经网络计算出来的结果只是近似值。所以你可以在实际的应用中对这个近似值根据实际情况来处理。
你这个学习网络 来运算54 AND 0 结果为什么是1?
经过学习的ANN并不一定能计算出所有有效的输入的结果。
造成这样的原因很多,输入节点的个数,隐含层的层数,激活函数,网络类型,训练集合的选取等等。
而且您的54并不是有效输入,所以说结果不会理想。

[教程]人工神经网络入门(1) —— 单层人工神经网络应用示例

mikel阅读(717)

人工神经网络入门(1) —— 单层人工神经网络应用示例

范例程序下载:http://www.cnblogs.com/Files/gpcuster/ANN1.rar
如果您有疑问,可以先参考 FAQ
如果您未找到满意的答案,可以在下面留言:)
1 介绍

还记得在2年前刚刚接触RoboCup的时候,从学长口中听说了ANN(人工神经网络),这个东西可神奇了,他能通过学会从而对一些问题进行足够好处理。就像咱们人一样,可以通过学习,了解新的知识。
但是2年了,一直想学习ANN,但是一直没有成功。原因很多,其中主要的原因是咱们国内的教程中关于这个技术的介绍过于理论化,以至于我们基础差和自学者望而却步。
现在,我希望通过一个简单的示例,让大家先有一个感性的认识,然后再了解一些相应的理论,从而达到更好的学习效果。
2 范例程序的使用和说明

本程序示例2个简单的运算:
1 AND运算: 就是咱们常用的求和运算,如:1 AND 0 = 1 
2 or运算: 就是咱们常用的求并运算,如:1 or 0 = 1 
启动程序后,你将会看到如下的界面:

点击“开始训练AND”按钮后,程序就开始训练 AND 运算,并在训练结束后提示咱们。
同时界面变成如下:

你只需要点击“0 0”按钮,就会在“计算结果”下面显示经过训练以后的ANN计算出来的结果。
如下所示:

“计算结果”显示为“1.74E-10”,说明 0 AND 0 = 0.
这个结果就是我们想要的。训练成功
其他的按钮使用方法类似:)
3 计算过程
咱们可以参考一下AND计算的总体运行过程:

            //初始化训练集合
            TrainSet[] sets = new TrainSet[]{new TrainSet(000), new TrainSet(010), 
                                                                
new TrainSet(100), new TrainSet(111)}
;

            
//构造单层神经网络 2 个输入节点 1个输出节点
            NeuralNetwork nn = new NeuralNetwork(21);
            slnn 
= new SingleLayerNeuralNetworks(nn, sets);

            
//训练
            slnn.Train();

            MessageBox.Show(
"AND运算训练结束");
            
this.button2.Enabled = true;
            
this.button3.Enabled = true;
            
this.button4.Enabled = true;
            
this.button1.Enabled = true;
            
this.Text = "AND运算";

OK,通过上面的代码可以看出,咱们的神经网络有2个输入节点,用于输入AND运算的2个参数。1个输出节点,用于输出AND运算的1个结果。
接下来,咱们的单层神经网络通过一个训练集(有一组输入和相应的希望输出数据)开始训练。训练结束后,咱们就可以用相应的数据对训练结果进行测试了(通过“0 0 ”等按钮)。

4 预告
在下一篇文章中,我将进行ANN基本概率的介绍和本示例实现的原理:)

5 总结
在本文中,咱们介绍了1个基于单层神经网络的简单易懂的程序示例,可以让大家先有一个感性的认识。

[理论]将看板管理应用到敏捷开发

mikel阅读(939)

摘要

看板1是丰田生产方式(Toyota Production System,TPS)中用来支持非集中“拉动式”生产控制(non-centralized "pull" production control) 而使用的卡片。作为精益生产的工具,它现在已经应用于世界各地的制造企业之中。如今在敏捷软件开发中,项目的可视化(例如在墙壁上放置任务卡片就是常见的 实践)往往被叫做“软件看板”,或者“任务看板”。我们甚至可以看到一些产品维护团队在类似瀑布过程模型中使用看板系统。那么,看板到底是什么呢?为什么 它会被用于软件开发环境之中呢?

在本文中,我首先解释一下在精益生产中,尤其是TPS中的看板是什么样子的,来理解下这个成熟行业中的实践和法则,并圈定可以应用 于软件开发的概念。其次,我将环顾我们的软件开发项目并指出看板应用的例子。然后,我会分析生产环境中的看板系统与软件开发中的看板系统有何异同,并尝试 提出观点来有效地在软件开发中应用看板系统,其中还介绍了新近在kanbandev2讨论列表中萌芽的“KSSE——持续工程的看板系统(Kanban System for Sustaining Engineering)”运动。最后,我给出TPS的一个全景视图,也就是看板这种工具的原始背景,软件开发仍可从中有所借鉴。

TPS中的看板

在“拉动式”生产系统中,看板是指示移动或者制造零部件的信号装置(通常是放在透明塑料封套中的卡片),它是在丰田生产系统(TPS)中发明和发展起来的。在介绍软件开发中的看板之前,我来详细的介绍下看板最初的用法,也就是TPS中的看板。

看板的目的是通过确保只有当下游工序需要时上游工序才生产零部件,进而最大限度地减少工序(process)之间的在制品(Work-In-Process,WIP)或者库存。“拉动式”是指下游工人从他们的上游工序中领取或者“拉”出所需要的零部件。

图1 看板和拉动式生产

图1是看板系统的抽象模型。图中以两个工序,上游工序和下游工序为例,其中上游工序为下游工序供应零部件。为了给最终用户提供产品,这一工序需要生 产零部件并将其流向下游,但是不能生产太多,因为生产过剩被认为是最糟糕的浪费。为了避免生产过剩,上游工序不是将成品零部件“推”给下游工序,反而是下 游工序主动地从上游工序中拉出(拿)零部件。零部件存放的地方叫做“仓库”(或者“超市3”——Taiichi Ohno首 次提出看板的思想,是在他参观了某个美国超市之后,在那里不是由商店售货员而是由顾客自己去获取他们想要的东西)。仓库位于上游工序,作为在制品的“缓冲 区”或者“队列”。当一名来自下游工序的工人——叫做“物料管理员”——来到仓库并拿到新近完成的零部件,同时他也反馈一个生产信号——也就是,下游从上 游中获取东西并在同时通过看板卡将信息推给上游。这是必须的,因为没有来自下游工序的信号上游工序决不会生产零部件。

那么在图1中有两种类型的看板一同工作:

  • 领取看板(Withdraw Kanban)——是由物料管理员递交给仓库的购物清单。
  • 生产看板(Production Kanban)——指示上游工序为其下游工序生产零部件。

如图1所示,领取看板循环于两道工序之间,而生产看板循环于工序内部,并且两者在仓库内发生交换。让我们稍微仔细的了解下这个交换细节。图2显示了在仓库中是如何进行“看板交换”。

图2 仓库中的看板交换

  1. 位于下游的物料管理员收到领取零部件的信号。此信号由下游工序定义,为下面两种情况之一:
    (a) 由收集到的领取看板的数量触发信号
    (b) 由定时时间间隔触发信号
    物料管理员带着空托盘(pallet)和收集到的领取看板访问上游仓库,并将他收集的领取看板当作购物清单——其中标明了下游工序需要什么,需要多少。
  2. 由上游工序完成的零部件用托盘装着,并附上生产看板放入仓库。(这些发生在一个单独的线程中,和(1)是独立的。)
  3. 物料管理员拿取领取看板(购物清单)中指定的零部件,检查是否与附在零部件上的生产看板相匹配,然后交换两个看板。
  4. 他将生产看板放入“生产板(Production Board)”中——稍后当看板累计到一定的阀值时,它将直观地触发上游生产。
  5. 物料管理员将所需的零部件附带着领取看板一同从仓库搬运至下游车间。

你可以看到仓库是由一个单独的线程控制的、位于两个工序之间的队列(queue),它通过看板来交换物品和信息。看板卡上面写明了像零部件号码、名称、数量、托盘类型、仓库位置这样的信息,这样物料管理员拿到卡片就知道去做什么了。

对于看板的运转有着严格的规定——被称作“看板六准则”:

  1. 客户(下游)工序精确按照看板上指示的数量领取产品。
  2. 供应者(上游)精确按照看板上指定的数量和顺序生产产品。
  3. 没有任何产品在看板之外制造或者流动。
  4. 每件产品每时每刻都要与看板相伴。
  5. 质量残次和数量有误的产品决不能发往下游工序。
  6. 谨慎地减少看板的数量来降低库存并揭露问题。

正如我们所了解到的,仓库用作零部件的队列,托盘用作零部件的载体,而看板卡用作客户之所需的信息载体。通过维持“连续流通(continuous flow)”(消除等待带来的浪费)和“最小化在制品(minizing WIP)”(消除生产过剩带来的浪费)之间的平衡,它们共同形成了“拉动式”系统。在超市中也是用同样的机制来把从采购到销售之间的流程中的在制品维持在 “适当”数量,做好这一步是提高商店盈利率的关键。

目前为止,我已经讲述了看板是如何在制造业中工作的。注意以上是对真实看板系统的简化模型。这里没有明确提及的另一件事是看板形象地向每一个工人展示了信息和产品的流程,并激励在现场5(Gemba,指工作场所)的改善4(Kaizen,指工序改进)。改善源于对现场中发生事件的关注。通过看板,每个工人(不是管理人员)都可以看到生产流程,进而有机会发现其中的浪费,并建议改进他们所在的工序。

看板的特性

根据前一节的详细介绍,这里列出了从TPS最初的看板概念中总结的特性和作用。

  1. 实体的:看板是实体的卡片。可以拿在手中,可以移动,可以放在某些东西上面或者里面。
  2. 限制在制品数量:看板限制在制品的数量,也就是防止生产过剩。
  3. 连续流通:它会在仓库耗尽库存之前通知生产的需求。
  4. 拉动式:下游工序从上游工序中抽取零部件。
  5. 自导向(Self-Directing):它有要完成的工作的所有信息,能以一种非集中的方式实行生产自治,并且不需要微管理(micro-management)。
  6. 可视化:堆放或者张贴着的看板直观地展示了当前的状态和进度。
  7. 信号:看板的可视化状态为下一步的领取操作或者生产操作作出指示。
  8. 改善(Kaizen):可视化的流程触发并刺激改善。
  9. 附着的(Attached):看板附在所供给的零部件之上并随其一同移动。

图3为以上9个特性之间的相互影响,它显示了如何将这些组成一个因果效应网络。你从中可以看到看板的两种含义,一是“在维持连续流通的同时限制在制品数量”,而另一种是“改善”。

图3 看板的特性和作用

图表右侧说明了如何在维持连续流通的同时,最大限度地减少在制品。如果仓库中的在制品太少的话,下游工序不得不等待所需的零部件准备就绪,但是在制品还应该保证最小化以防止生产过剩。这样看来这两个目标是相矛盾的,而看板正被看作是解决这个难题的策略。

看板附着于零部件,并且可以被收集和重用,因此看板的数量是固定的。而且看板还可以直观地指示下游工序仅当需要时才获取零部件。这里有两种限定在制品数量的机制。

第一个机制“附着的看板”工作机制同“能量守恒定律”类似。一旦根据产品市场销售的速度和当前工序的内在变化规律确定了看板的数量,那么不管零部件的流入 和流出如何,在制品的数量都被限制为看板数量的一定比例。在任何时候,看板(相当于系统中的“能量”)的最大数量都与在制品的上限保持守恒。在图4中,你 可以看到“系统”指的是上游工序和下游工序之间的库存,也就是“仓库”中的在制品。

图4 限定在制品数量的看板机制

第二个机制——“拉动式”——通过依据下游消耗速度来确定上游工序的生产速度,这种机制也限制了在制品的数量。第一个机制仅仅涉及到在制品的数量,而第二个则涉及到流程——流程的方向和速度。

“方向”——仅由下游工序来驱动生产。
“速度”——通过看板传达下次生产的时机和数量。
通过确保上游工序的生产以下游工序首次衍生订单中的消耗为依据,“拉动式”限制了在制品的数量。通过在仓库中交换看板,将生产控制信息从下游推到上游,这种依赖性便得以实现。

回到图3:图表左侧说明了如何促使工作自导向并促进改善。通过查看张贴在面板上的看板卡,每个人都可以了解到发生了什么事,以及工序运转的健康状 态。改善起始于对现场(Gemba)工作流的观测。放置于面板之上的看板卡直观地帮助工作在没有中央控制管理之下自导向。为了支持改善,这种自治的工序向 外提供其性能数据,并将管理重点从对具体工作的指派或者调度上转移到改善活动。

图3中的箭头最终都指向了三个结果,如其所示,看板的终极目标可以表示为“限制在制品数量”、“连续流通”和“改善”。看板系统在维持“连续流通”的同时“限制在制品数量”。它缓冲由普通变因引起的变化情况,并暴露特殊变因引起的变化情况,以备改善。

软件开发中的看板

现在,让我们将视线回到我们自己的工作领域——软件开发。在敏捷软件开发中,通过在项目工作场所的墙上张贴卡片来呈现和分享项目状态已经成为一种常见的实践。我已经在我的上一篇InfoQ文章《用“看板图”实现敏捷项目的可视化》[Hiranabe07]给出了很多例子。特别是,贴在墙上用来展示当前项目状态的任务卡片有时也被称作“任务看板”或者“软件看板”[Poppendieck03]。图5是Change Vision公司的JUDE6开发团队所用的任务看板。

图5 敏捷看板

在面板上,工程任务用卡片(即时贴)来代表,并通过把卡片贴在在面板中的不同区域来象征任务的状态,这些区域被标注为“ToDo”、“Doing” 和“Done”(标注的名称可能因地而异,比如“进行中(In Progress)”、“已测试(Tested)”、“已验收(Accepted)”、“停滞中(Blocking)”等等。)。这样的看板面板有利于可 视化地通知任务并限制在制品(处理中的任务)数量。不过在这里并没有出现“工序”(上游或者下游),新出现的概念是“迭代”。对于每一次迭代,通过分解用 户故事识别出任务,并且将其张贴在面板的ToDo区域中。

这是一个拉动式系统吗?在制造业中,零部件由上游工序传递至下游工序。而在图5所示的敏捷开发中,并没有看到“移交物”。一个看板卡片对应一个任 务,上面写明了如下信息:任务编号、任务名称、估计时间以及任务领取人的名字。任务有状态,可以是“ToDo”、“Doing”或者“Done”,状态信 息被分享给整个团队。敏捷开发重视在一起工作,并趋向于减少团队内部的移交物。我称此为“敏捷看板”。

图6是另一个看板面板实例,由Yamaha Motor Solution有限公司7所采用。

图6 持续看板(Sustaining Kanban)

在这里,看板系统被用于带有流程的传统瀑布开发模型。项目被分解成“设计”、“开发”、“验证”等连续的工序,而看板卡就在这些工序之间移动。每张 卡片代表需要修改或者添加的系统需求,也代表给下游工序的移交物。注意这不是一个标准的瀑布流程——标准瀑布流程中所有的需求在同一时间内完成“设计”, 而“开发”和“验证”则在另一时间,这将使得所有的卡片作为一个整体进行移动。与标准的瀑布流程不同的是,这个项目中的卡片是一个接一个地移动,就像制造 业中的单件流(one-piece-flow)一样。这里表现的是产品生命周期里稳定的“持续(sustaining)”阶段,处在带有流程的瀑布状态转 换模型的管理之下。在这里,你可以清楚地看到“工作流程”的概念,而不同于敏捷中的“迭代”概念。它比敏捷看板看起来更像工厂中的看板,而且通过制定规则 只允许下游工序移动卡片8,可以使其成为拉动式系统。我称其为“持续看板”,这与稍后章节中讨论的David Anderson的“持续工程的看板系统”是类似的。

图7显示的是另外一个例子——在整个产品开发流程的价值流中使用看板的思想实验(thought experiment)[Poppendieck 07]。

图7 精益+敏捷看板

假设在一个产品开发流中有客户团队、产品所有人、开发团队和QA团队,他们使用队列传递移交物来协调工作,以使得团队之间能异步工作,并维持工作速 度。每一个“DONE”空间是一个队列,其工作方式就像制造工厂中的“仓库”那样,并且看起来非常像TPS看板系统。同时,它看起来就像每条工序内同步地 使用敏捷看板,而在贯穿各个工序的整个价值流上异步地使用持续看板。我认为看板系统可以扩展至覆盖整个价值流,在这种情况下,它是价值流的一个活生生的视 觉表现。

在这里例子中,通过设定每一个区域的大小可以限制在制品的数量。而为了使其变成拉动式系统,还需要一种机制来使下游工序以某种信号通知上游工序开始 工作。其中一种方法是制定一个规则只允许下游移动DONE区域中的卡片来通知上游。另一种方法是定期召开“迭代会议”,来同步团队和团队之间传递(通讯) 的信息。这两种通讯方式可能对应于我们在第一章节中讨论的零部件领取的两种信号,即领取看板的数量(a)和时间间隔(b)的可视信号。一次迭代中的一组用 户故事对应于迭代中托盘里的零部件,而零部件的数量对应于迭代中的项目“生产率”(昨日天气[Beck00])。我叫它为“精益+敏捷看板”,如下一个例 子展示的那样它可以与“敏捷看板”相结合。

图8中是一个小型的“便携式”看板系统,这是我在CENTRAL COMPUTER SERVICES有限公司的某个项目里发现的。在这个项目中,团队被分为了几个小型子团队(通常是一对人)。整个团队有一个与图7概念相似的工作流,还有 图8所示的小型敏捷看板面板(ToDo、Doing、DONE)。 当一个子团队选取了一个用户故事,他们将其分解到任务并张贴在便携式看板面板上。在这种情况下,看板系统由两个层面组成,在项目层面一张卡片代表一个用户 故事,而在团队(或者结对)层面一张卡片代表一个任务。

他们很喜欢这个便携式小型看板系统,并命名为“看板nano”。

图8 便携式敏捷看板(“看板nano”)

如你所见,将看板的概念应用于软件开发有许多方式。“敏捷看板”用来在团队中分享信息并使工作自导向,但它不支持流程。“持续看板”是另一种类型的 看板,能够让小批量的维护工作在几个状态之间流转。这种结合便是“精益+敏捷看板”,使用“持续看板”贯穿价值流,同时在子流(sub-stream)中 使用“敏捷看板”。

注意,图5中的“敏捷看板”(在当今敏捷项目中随处可见)仅仅可以看到价值流中的一个子流。当你考虑从客户到客户的完整价值流,经常由处于同一流中 的某个团队递交给你需求,而另一个团队则交付你的工作结果给客户。这篇文章的目的之一,就是要设法让看板的应用超越“敏捷看板”,扩大看板在价值流中的应 用范围。

生产与开发

软件开发是不同于生产活动或者制造活动的。软件工程师每次创造的产物都是不同的,而制造业总是周而复始的生产相同的东西。所以直接将两者等同起来是 危险的。可是,让我们研究一下如何在软件开发看板中找到TPS看板中的特性。表1显示了我们在章节1中总结的看板特性在我们已经提及的两种软件看板中是否 仍然有效。


图5所示的敏捷看板例子本身并没有实现“限制在制品的数量”、“连续流通”和“拉动式”特性。敏捷看板更关注于实现任务、“可视化”和“自导向”,以便帮助团队自治并改进其工序。为了使工序连续流通并限制在制品的数量,需要召开“迭代会议”交流信息。

图6中的“持续看板”不仅可以限制在制品的数量,还可以以“单件(one-piece)”和“拉动式”的方式控制流程,而不需要召开迭代会议。在这种方法中,它的关注点是“限制在制品的数量”、“连续流通”和“拉动式”,同时允许团队(或者管理人员)借其改进工序。

回顾一下图3,我将看板的特性和作用分成图9所示的两个关键区域,以便上面提到的两类软件看板概念的用途各得其所。图10显示了生产和开发的频谱 图。生产是成功几率很高(高于99%)的工序,而开发的成功几率要低。当成功几率在50%左右的时候敏捷是理想的开发方法,而当成功几率超过90%的时候 瀑布式则是理想的开发方式(依据Shannon理论,一个具有50%成功几率的项目是最有价值的项目)。通常随着开发进入到支持维护状态,修改缺陷和添加 新功能的成功几率逐步提高。

看板系统的“工序控制焦点(Process Control Focus)”适合在“高于90%”的成功率下工作,而“工序改进焦点(Process Improvement Focus)”既适合在50%成功率也适合在90%成功率下工作。

值得注意的是,敏捷方法在产品维护状态(sustaining mode )下仍能工作良好,同样看板的“工序改进焦点”特性也在维护状态下工作良好。

图9 看板的特性和作用(2

图10 使用看板的方法频谱

KSSE——持续工程的看板系统

接下来,我介绍最近出现的一种软件开发精益应用。Agile2007会议时,我参加了David Anderson主持的关于软件看板的一个会中会(Conference-Within-A-Conference,CWAC)。他在 Corbis.com管理着一个“维护状态(maintenance mode)”类型的看板系统,并发表了一篇相关论文——持续工程的看板系统[Anderson 07]。他的方法首先关注于看板的“限制在制品数量”特性——就像在图4所示的抽象图表那样——也关注“自导向”特性以使得团队自组织,减少自上而下的 (top-down)管理。然后,通过看板观察流程,找出整个工序流中的驻点(stagnation point)并调整人力资源,也就是在工序间调动成员。这意味着他的方法,像图3表现的那样,涵盖了看板特性中从“限制在制品数量”、“自导向”到“改 善”。

会议之后,Anderson启动了一个看板开发邮件列表2,这里已经成为将看板应用于软件开发的一个新兴知识创新讨论社区,名为“KSSE”——持续工程的看板系统,读作Kiss-ee ;-)。Aaron Sanders还着手创建关于看板的知识体系,并已经开始构建KSSE词汇表

KSSE对于通过队列在工序间传递移交物、连续相接的多个工序运作良好。请注意KSSE不一定需要纳入“迭代”的概念。使用KSSE的方式,我看到了另一种缩放(scaling)敏捷的可能性方式并且好过“scrum of scrums”。[Ladas07]

创造价值流

当通过看板从敏捷放大到精益时,一张看板卡应该代表什么东西呢?

 在敏捷看板系统中,一个卡片是一个从“用户故事”中分解出来的“任务”。在开发团队中,它作为工作的一个基本单元执行,因为团队中每个人都能明白 它的意思。但是,在看板系统中它贯穿了价值流中的多个工序(多个团队),在其中流转之物应该带有客户认可的价值。既然这样,看板卡片就不是对应于“工作 (work)”而是对应于“功能(feature)”,并且它不是WBS(任务分解结构,work breakdown structure)的组成部分,而是FBS(功能分解结构,feature breakdown structure)的组成部分。因此团队中的每个人,甚至是客户,都能够理解看板的含义和流转之物的价值。Jim Highsmith 在《敏捷项目管理(Agile Project Management)》[Highsmith04]书中所概述的原理也将FBS定位高于WBS。

“用户故事”,“Backlog事项”或者“用例”都被抽象为“MMF”(最小可市场化功能,minimum marketable features),用来明确地声明流转之物具有客户价值。于是精益开发就可以说成“使得MMF快速流过整个价值流。”

图5中“敏捷看板”的例子是一个工作的分解,它在团队内部工作良好。图6中“持续看板”的例子是一个功能的分解并且一张卡片代表一个MMF。图7中“精益+敏捷看板”的例子与图8一起展示了上级功能分解和下级工作分解的结合。

一旦建立起工作流程,五个“精益思想”[Womack1996]的核心概念就可直接应用于整个工序。精益工序的管理可以简单地遵循以下原则。

  • 从客户的角度定义价值——确定和分类MMF
  • 明确价值流并消除浪费——找出驻点(停滞的任务)
  • 在客户的拉动下创造价值流——制定看板的拉动规则
  • 关注员工并给予一定的权力——授权给在现场的团队
  • 追求完美的持续改善——反省和改善

TPS全景视图

以下内容相当于附录,我在这一部分分享从TPS中学到,并发现可以适用于软件开发的知识。Mary和Tom Poppendieck已经发现有效的软件开发方式和精益或者TPS方法有着很多的相同点——不是在实践层面,而是在原理层面上 [Poppendieck03, 07]。让我们从更高的角度回过头来再看下TPS中的看板。

读者很容易假定看板是整个TPS的中心,但其实并不是。图11展示了TPS的概念结构,有时也叫做“TPS之屋(TPS House)”。它有好几种版本,图11是基于Toshiko Narusawa和John Shook的版本[Narusawa06]。在TPS中,看板仅仅是“拉动式系统”实现准时制(Just-In-Time9)的一种方法。准时制可以解释为“仅在需要的时候生产和交付所需要的东西,并且仅完成需要的数量”。它直接瞄准的是客户的需要:“尽快以最低的价格提供最高质量的产品。”注意,准时制是TPS的两大支柱之一,另一个是Jidoka10(译 注:写作“自动化/自働化”,但其含义与对应于Automation的中文的“自动化”不同,详见注释)。制造业中的“Jidoka”即自动停机 (Autonomation)与软件开发中的测试驱动开发类似。Mary和Tom Poppendieck把Jidoka解释为“停止流水线文化(Stop the Line Culture)”。丰田工厂的工人真正地可以停止流水线而不是把次品推到下一个工序——它不仅是一种规定,也是丰田公司的一种文化,它的萌芽可以追溯到 (丰田集团创始人)丰田佐吉时期。

图11 TPS概念结构

准时制由以下三部分元素构成,“节拍时间(Takt time)”、“连续流通”和“拉动式系统”。

  1. 节拍时间基于销售率制定产品生产率。
  2. 连续流通与节拍时间相匹配,无停滞地在工序中生产部件。
  3. 拉动式系统在工序之间移动零部件并通知生产,同时限制库存数量。

也应该注意到两个支柱依赖于改善和人。丰田一年生产近千万辆汽车,同时,他们通过在现场(也就是车间)中近1百万次的改善来完善他们的工序。形象化地表示出团队正在做些什么,总是改善的出发点。

结论

文中,我分析了看板在制造业的实施,接着列出了看板的特性。看板系统用以达到以下目标:

  1. 更优的工序控制——在限制在制品数量的同时保持连续流通
  2. 更优的工序改进——使流程可视化并且激励改善

“敏捷看板”专注于#2,而“持续看板”专注于#1。我提出将两者结合,来拓展可视化和“拉动式系统”到整个价值流,以使得整个生产精益化。

感谢

Tom Poppendieck与Mary对本文进行了通篇审校,并给出了许多见解和建议,在此我表示非常感谢。感谢Yamaha Motor Solution有限公司总裁Yasuharu Terai以及Ryuichi Sato允许本文中使用其看板系统的图片。另外David Anderson也参与了本文的审校工作并且提出了一个更好的看板抽象层次来推进KSSE的发展。KSSE概念最初来源于Kanbandev Yahoo团队的讨论。最后感谢Deborah Hartmann的最后校订工作,使得我的表达更清晰。

关于作者

Kenji HIRANABE是日本Change Vision公司的CEO。他是JUDE(一个集成了ERD、DFD和Mind Map的UML编辑器)和TRICHORD 11(一 个集成了Burndown图表和Parking Lots图的敏捷项目管理看板系统)的创始人。他还把《Lean Software Development》、《XP Installed》、《Agile Project Management》以及其他XP/Agile书籍翻译成日文。Kenji把软件开发看作是一种沟通游戏,并一直在寻求提高软件开发的生产效率、合作程 度以及乐趣的途径。

参考文献

TPS

  • [Ohno78] Taiichi Ohno, "Toyota Seisan Houshiki", 1978 (English: "Toyota Production System", 1988). The bible of TPS. This is a MUST book for lean practicioners.
  • [Narusawa06] Toshiko Narusawa, John Shook, "Eigo de Kaizen, Toyota seisan houshiki", 2007(Japanese and English). Recently published English/Japanese parallel translation of TPS.
  • [Ishii05] Masamitsu Ishii, "Toyota no moto-koujousekininnsha ga oshieru nyuumon Toyota seisan houshiki", 2005. Includes a version of TSP concept structure.
  • [Monden06] Yasuhiro Kadota, "Toyota Production System", 2006(English: "Toyota Production System 3rd", 1998). The bible of implementation of TPS. I studied Kanban discussion in section 1 from this book.

敏捷和精益的主流文献

  • [Reeves92] Jack W. Reeves, "What is Software Design?" C++ Journal, 1992. My favorite article about Software Design.
  • [Kent00] Kent Beck and Martin Fowler, "Planning Extreme Programming", 2000, Addison-Wesley. About planning of release and iteration. Yesterdays weather and project Velocity idea first explained in detail in the book.
  • [Poppendieck03] Mary and Tom Poppendieck, "Lean Software Development", 2003 Addison-Wesley. The first classic of drawing lines between TPS and software development.
  • [Highsmith04] Jim Highsmith, "Agile Project Management", 2004, Addison-Wesley. Feature breakdown structure is introduced as a practice of APM.
  • [Poppendieck07] Mary and Tom Poppendieck, "Implementing Lean Software Development", 2006 Addison-Wesley. Explains Kanban in lean and how it works as a pull process mechanism.
  • [Cockburn06] Alistair Cockburn, What engineering has in common with manufacturing and why it matters" 2006. Alistair has another discussion of viewing unvalidated decision as WIP in software engineering process.

最近出现的看板概念


1关于看板的更多信息http://en.wikipedia.org/wiki/Kanban
2关于kanbandev的更多信息http://finance.groups.yahoo.com/group/kanbandev
3关于Taiichi Ohno的更多信息http://en.wikipedia.org/wiki/Taiichi_Ohno
4关于改善的更多信息http://en.wikipedia.org/wiki/Kaizen
5关于现场的更多信息http://en.wikipedia.org/wiki/Gemba
6JUDE是一个集成了UML、ERD、DFD和Mind Map的可视化软件编辑器。点击http://jude.change-vision.com/了解更多信息。
7Yamaha Motors有硬件产品生产线,因此他们的软件团队有很好的条件从工厂中学习精益思想。在我参观他们公司的时候,看到了许多用于软件项目可视化的 “XFDs”(极限反馈装置,extreme feedback devices),这与他们工厂中的可视化管理系统——“行灯(Andon)”是类似的。
8虽然无关紧要,但知道那些红盒子(工序)表示是由中国外包生产的也很有趣。
9关于准时制的更多信息http://www.toyota.co.jp/en/vision/production_system/just.html
10关于Jidoka的更多信息http://www.toyota.co.jp/en/vision/production_system/jidoka.html
11关于TRICHORD的更多信息http://trichord.change-vision.com/

查看英文原文Kanban Applied to Software Development: from Agile to Lean