I come, I see, I conquer

                    —Gaius Julius Caesar

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 :: 管理 ::

 

来自《ASP.NET 4高级程序设计》

 

一、Web窗体

 

1、页面处理

 

ASP.NET Web应用程序特点:

 

  • Web应用程序在服务器上执行。例如,假设建立一个允许用户选择产品记录并更新其信息的窗体。用户在浏览器上执行这些任务,但为了能够让你执行必需的操作(譬如更新数据库),代码需要运行在Web服务器上。ASP.NET通过一种叫做回送的技术处理这种分隔,当发生特定的行为时,它会把页面(以及所有用户提供的信息)发送到服务器。ASP.NET接受到这个页面后,就会触发相应的服务器端事件来通知你的程序代码。
  • Web应用程序是无状态的。换句话说,在呈现的HTML页面被发送给用户之前,页面对象被销毁且所有的客户端特定信息被丢弃。这种模型可用于高度可扩展的、高流量的应用程序,但却难于创建无缝的用户体验。ASP.NET提供了几个工具帮助你弥补这一缺陷,其中最值得关注的一个持久化机制是视力状态,它自动把信息嵌入到呈现HTML的隐藏输入框中。

 

HTML表单:

    如果你熟悉HTML,你一定知道把客户端数据发送到服务器的最简单的办法是使用<form>标签。在<form>标签里,你可以放置其他<input>标签来表示用户界面的基本元素。 

 

<form method="post" action="page.aspx">
    <div>
        Enter your fist name:&nbsp;
        <input type="text" name="FirstName" />
        <br />
        Enter your Last name:&nbsp;
        <input type="text" name="LastName" />
        <br />
        <input type="checkbox" name="CS" /> C#
        <input type="submit" value="Submit" id="OK" />
    </div>
</form>

 

    当用户单击“提交”按钮时,浏览器收集每个控件的当前值,并把它们粘成一个长字符串。然后使用一个HTTP POST操作在<form>标签中把这个字符串发送到页面(这里是page.aspx)。对于这个例子,Web服务器可能收到这样一个字符串信息:FirstName=Matthew&LastName=MacDonald&CS=on

    服务器端在解析这个字符串时,以一种更为有用的方式显现该字符串。在ASP和ASP.NET中,可以通过名称从Request.Form集合中获取值。如:string FirstName = Request.Form["FirstName"]。但这种方式离真正的面向对象框架还很远。这也是ASP.NET还要进一步采取其他步骤的原因。将页面回送到ASP.NET时,它抽取值并填充Form集合(为了与ASP代码后向兼容),然后配置相应的控件对象。这就意味着你能够在ASP.NET Web窗体中用下面更直观的语法来获取信息:

    string FirstName = txtFirstName.Text 


动态用户界面:

    传统的ASP中:

 

string message = "<span style = \"color:Red\"> Welcome " +
            FirstName + " " + LastName + "</span>";
Response.Write(message);

    另一方面,ASP.NET只要定义一个Label控件,一切就简单多了:

<asp: Label id="lblWelcome" runat="server" />
lblWelcome.Text = "Welcome" + FirstName + " " + LastName;
lblWelcome.ForeColor = Color.Red;


ASP.NET事件模型

工作原理如下:

(1)在页面第一次运行时,ASP.NET创建页面和控件对象,接着执行初始化代码,然后页面被带现为HTML并返回到客户端。页面对象在服务器内在中被释放。

(2)用户在某点触发某个回送时,例如单击某个按钮,页面所有的表单数据都被提交。

(3)ASP.NET截获这些返回的页面并重建页面对象,准确还原到页面被发送到客户端时的最后状态。

(4)ASP.NET检查是什么操作触发了回送,并引发适当的事件(例如Button.Click事件),你可以在事件中编写响应的代码。一般来说,这个时间内你会执行某些服务器端操作(例如,更新数据库或读取文件),然后修改控件对象来显示新的信息。

(5)修改后的页面转换成HTML并返回到客户端。页面对象从内存中释放。假如有新的回送发生,ASP.NET重复第(2)到第(4)步的操作。

 

自动回送

    当Web控件的AutoPostBack属性设为true时,ASP.NET使用客户端的Javascript来连接客户端和服务器端的代码。

    AutoPostBack的工作方式是,如果创建了一个包含一个或多个AutoPostBack属性为true的Web控件的页面,ASP.NET在呈现的HTML页面上添加一个名为_doPostBack()的Javascript方法,当调用该方法时,它将触发回送,把窗体所有的信息回送到Web服务器。

    ASP.NET还会添加两个隐藏的输入字段,_doPostBack()方法用它们把信息传回到服务器。这个信息包括引发该事件的控件的ID以及其他任何相关的可能的附加信息。这些字段起初为空,如下所示:

<div class="aspNetHidden">
    <input type="hidden" name="_EVENTTARGET" id="_EVENTTARGET" value="" />
    <input type="hidden" name="_EVENTARGUMENT" id="EVENTARGUMENT" value="" />
    ...
</div>

    _doPostBack()方法负责把这些值设为事件的适当信息,然后提交窗体。下面是一个_doPostBack()方法的示例:

<script type="text/javascript">
function _doPostBack(eventTarget, eventArgument){
    if(!theForm.onsubmit || (theForm.onsubmit() != flase)) {
        theForm._EVENTTARGET.value = eventTarget;
        theForm._EVENTARGUMENT.value = eventArgument;
        theForm.submit();
    }
    ...
}
</script>

    最后,任何AutoPostBack属性设置为true的控件都可以通过onclick或onchange特性来连接_doPostBack()方法。这些特性指示浏览器应如何响应客户端的JavaScript脚本事件onclick和onchange。

    下面的例子显示了列表控件lstCountry呈现的HTML,该列表旁控件自动回送。无论什么时候用户改变列表中的选项,客户端的onchange事件都会触发。然后浏览器会调用_doPostBack()方法把页面回送到服务器,如下所示:

<select name="lstCountry" onchange=
    "javascript:setTimeout('_doPostBack(\'lstCountry\',\'\')\',0)"
>

    换句话说,ASP.NET自动将客户端的JavaScript事件变为一个服务器端的ASP.NET事件,其中利用_doPostBack()方法作为媒介。

 

 

二、.NET组件 

 

    用ASP.NET编写的设计良好的Web应用程序通常会包含单独的组件,它们用于区分数据层和业务层。可以通过两种方式创建组件。

  • 在App_Code子目录中创建一个新的.cs文件。ASP.NET会自动编译这个目录的所有代码,并使它们对Web应用程序的其余部分可用。在Visual Studio里添加新类时,它会提醒你创建App_Code目录(如果还没有创建)并把类文件放到该目录下。
  • 在Visual Studio里创建一个新的类库项目。这个项目里的所有类将会被编译到一个DLL程序集中。把它们编译为程序集后,可通过“网站”->“添加引用”命令(或者“项目”->“添加引用”命令)把它们加入到你的Web应用程序中。这个步骤在你的web.config文件中添加了对程序集的引用,并把程序集复制到应用程序的Bin子目录下。

    上述两种方式具有相同的结果。虽然两种方式在本质上是相同的,但它们对代码的管理却很不相同。第二种方式更适合一个开发团队开发大型应用程序。

 

方式一:

 

 

方式二:

编译类库后,就可以在Web网站项目中添加对该类库的引用了,图示如下:

 

 

三、ADO.NET基础 

 

1、Command类和DataReader类(P217)

   

    Command类可以执行所有类型的SQL语句,虽然它也可以用于执行数据定义任务(如创建或修改数据库、表和索引),但一般被用来执行数据操作任务(如读取或更新表中的记录)。

 

    使用命令前,需要选择命令类型,设置命令文本并把命令绑定到连接上。

 

CommandType

    CommandType.Text: 该命令执行一条SQL语句。SQL语句通过CommandText属性设置。该选项为默认值。

    CommandType.StoredProcedure:该命令执行数据源中的一个存储过程。通过CommandText属性设置存储过程的名字。

    CommandType.TableDirect:该命令将查询表中的所有记录。CommandText为要从中读取记录的表的名字。不常用。 

 

Command方法

    ExecuteNonQuery():执行非SELECT语句,如插入、删除、更新等SQL语句。返回值显示命令影响的行数。也可以执行数据定义命令,如创建、修改和删除数据库对象(如表、索引、约束等)。

    ExecuteScalar():执行SELECT查询,并返回命令生成的记录集的第一行第一列的字段。常用来执行使用COUNT()、SUM()等函数的聚合SELECT语句,用于计算单个值。

    ExecuteReader():执行SELECT查询,并返回一个封闭了只读、只进游标的DataReader对象。 

 

DataReader类

    DataReader允许你以只进、只读流的方式每次读取一条SELECT命令返回的记录。使用DataReader是获取数据最简单的方式,不过它缺乏非连接的DataSet所具的排序等功能。 

 

DataReader方法

    Read():将行游标前进到流的下一行。在读取第一行记录前,也必须调用这个方法(DataReader刚创建时,行游标在第一行之前) 。当还有其他行时,返回true,已是最后一行时返回false。

    GetValue():返回当前行中指定序号的字段值。

    GetValues(),GetInt32(),GetChar(),GetDateTime(),GetXxx()

    NextResult():如果命令返回的DataReader包含多个行集,该方法将游标移到下一个行集(刚好在第一行之前)

    Close()

 

ExecuteReader()方法和DataReader

 

...
SqlCommand cmd = new SqlCommand(sql, con);
...
con.Open();
SqlDataReader reader = cmd.ExecuteReader();
...
StringBuilder htmlStr = new StringBuilder("");
while(reader.Read())
{
    htmlStr.Append("<li>");
    htmlStr.Append(reader["TitleOfCourtesy"]);
    htmlStr.Append("<b>");
    htmlStr.Append(reader.GetString(1));
    htmlStr.Append(reader.GetString(2));
    htmlStr.Append(reader.GetDateTime(6).ToString("d"));
}

 

 

ExecuteScalar()方法 

下面的方法演示如何使用该方法得到Employees表的记录数

 

SqlCommand cmd = new SqlCommand(sql, con);
int numEmployees = (int)cmd.ExecuteScalar();

 


ExecuteNonQuery()方法 

 

string sql = "delete from employees where employeeId = " + empId.ToString();
SqlCommand cmd = new SqlCommand(sql, con);
try
{
    con.Open();
    int numAff = cmd.ExecuteNonQuery();
}
catch (SqlException exc)
{
    //print(exc.Message)
}
finally
{
    con.Close();
}

 


使用参数化命令防止SQL注入攻击

例如,下面这条SQL语句:

SELECT * FROM Customers WHERE CustomerId = 'ALFKI'

可以写成这样:

SELECT * FROM Customers WHERE CustomerId = @CustId 

为每个参数创建一个Parameter对象,这些对象被加入到Command.Parameters集合中。 

 

SqlCommand cmd = new SqlCommand(sql, con);
cmd.Parameters.AddWithValue("@CustId", txtId.Text);
...
SqlDataReader reader = cmd.ExecuteReader();
GridView1.DataSource = reader;
GridView1.DataBind();
reader.Close();
con.Close();

 

 

调用存储过程

参数化命令是调用完整功能存储过程的诸多命令中的一小部分。存储过程当然是保存在数据库上的批次执行的一条或多条SQL语句。与函数相似,可以接收和返回数据。

创建往Employees表中插入记录的存储过程的SQL代码如下:

 

CREATE PROCEDURE InsertEmployee
@TitleOfCourtesy  varchar(25),
@LastName         varchar(20),
@FirstName        varchar(20),
@EmployeeId       int OUTPUT
AS
INSERT INTO Employees
(TitleOfCourtesy, LastName, FirstName, HireDate)
VALUES(@TitleOfCourtesy@LastName@FirstNameGETDATE());
SET @EmployeeId = @@IDENTITY

 


 

SqlCommand cmd = new SqlCommand("InsertEmployee", con);
cmd.CommandType = CommandType.StoredProcedure;

 

将存储过程的参数加入到Command.Parameters集合。添加参数时,需要精确指定数据类型和参数的大小以便和数据库中的细节相匹配。

 

cmd.Parameters.Add(new SqlParameter(
    "@TitleOfCourtesy", SqlDbType, NVarChar, 25));
cmd.Parameters["@TitleOfCourtesy"].Vlaue = title;

 

 

最后,你就可以打开连接并通过ExecuteNonQuery()方法执行命令。命令执行完毕后,就可以读取输出值了,如下所示:

 

 

con.Open();
try
{
    int numAff = cmd.ExecuteNonQuery();
    int empId = (int)cmd.Parameters["@EmployeeID"].Value;
}
finally
{
    con.Close();
}

 

 

 

2、事务和ASP.NET应用程序


 

事务是一组必须全部成功或全部失败的操作。 ASP.NET Web应用程序可以使用3种基本类型的事务。

 

  • 存储过程事务
  • 客户端引发(ADO.NET)的事务
  • COM+事务 

 

提示:作为一项基本原则,记住仅在操作需要时才使用事务。单一语句(如独立的UPDATE、DELETE或者INSERT语句)不需要使用事务,因为它们天生就是事务性的。 

 

存储过程事务

 

CREATE Procedure TransferAmount
(
    @Amount Money,
    @ID_A int,
    @ID_B int
)
AS
    BEGIN TRANSACTION
    UPDATE Accounts SET Balance = Balance + @Amount WHERE AccountID = @ID_A
    IF (@@ERROR > 0)
        GOTO PROBLEM
    UPDATE Accounts SET Balance = Balance + @Amount WHERE AccountID = @ID_B
    IF (@@ERROR > 0)
        GOTO PROBLEM
    --No problem was encountered.
    COMMIT
    RETURN
    --Error handling code.
    PROBLEM:
        ROLLBACK
        RAISERROR('Could not update.'161)

 

或者使用TRY...CATCH来捕获错误。

 

客户端引发的ADO.NET事务

Transaction类的两个关键方法:Commit()和Rollback() 

 

SqlTransaction tran = null;
try
{
    //打开连接,创建事务
    con.Open();
    tran = con.BeginTransaction();
    //将两个Command对象列入事务中
    cmd1.Transaction = tran;
    cmd2.Transaction = tran;
    //执行命令
    cmd1.ExecuteNonQuery();
    cmd2.ExecuteNonQuery();
    //提交事务
    tran.Commit();
}
catch
{
    //错误发生时回滚事务
    tran.Rollback();
}
finally
{
    con.Close();
}

 

如果在事务进行中执行一个不在当前事务中的命令,会产生错误。


 

 

四、数据组件和DataSet 


ADO.NET是基于连接的数据访问。而DataSet用于处理非连接的数据访问。

 

1、DataSet类(P251)

DataSet构架设置:


 

 

2、DataAdapter类

 

    要在DataSet中提取记录并将它们填入表中,需要使用另一个ADO.NET对象DataAdapter。它是提供程序相关的对象,因此每一个提供程序都有一个DataAdapter类(如SqlDataAdapter、OracleDataAdapter)。

 

    DataAdapter是DataSet中的DataTable和数据源间的桥梁。它含有查询和更新数据源所需的全部命令。

    为了让DataAdapter能够编辑、删除或添加行,需要设定DataAdapter对象的InsertCommand、UpdateCommand、DeleteCommand属性。利用DataAdapter填充DataSet时,必须设定SelectCommand。

 

DataAdapter方法

    Fill():执行SelectCommand中的查询后,向DataSet添加一个DataTable。如果查询返回多个结果集,该方法将一次添加多个DataTable对象。还可以用该方法向现有的DataTable添加数据。

    FillSchema():执行SelectCommand中的查询,但只获取架构信息,可以向DataSet中添加一个DataTable。该方法并不往DataTable中添加任何数据。相反,它只利用列名、数据类型、主键和唯一约束等信息预配置DataTable。

    Update():检查DataTable中的所有变化并执行适当的InsertCommand、UpdateCommand、DeleteCommand操作为数据源执行批量更新。

 

 

下图显示了DataAdapter及其Command对象是如何一起和数据源及DataSet工作的。


 

填充DataSet

 

SqlDataAdapter da = new SqlDataAdapter(sql, con);

DataSet ds = new DataSet();
da.Fill(ds, "Employees");

StringBuilder htmlStr = new StringBuilder("");
foreach (DataRow dr in ds.Tables["Employees"].Rows)
{
    htmlStr.Append(dr["TitleOfCourtesy"]).ToString());
    htmlStr.Append(dr["LastName"]).ToString());
    htmlStr.Append(dr["FirstName"]).ToString());
}
HtmlContent.Text = htmlStr.ToString();

 

虽然每个DataAdapter对象都支持4个Command对象,但是只有一个(SelectCommand)是填充DataSet所必需的。填充DataSet时,可以指定表的名字,如果不指定,会自动定义一个默认名(如Table)。尽管不是必需的, 但还是使用了和数据库中源表一致的名字来命名表。另外,调用Fill() 方法时,DataAdapter自动打开和关闭相关联的连接。

 

使用多个表和关系

以下示例演示了如何从Northwind数据库的Categories表和Products表中检索数据,如何在两表间创建关系从而方便地从类别记录导航到它所属的子产品并创建报表。 

 

string sqlCat = "SELECT CategoryID, CategoryName FROM Categories";
string sqlProd = "SELECT ProductName, CategoryID FROM Products";

SqlDataAdapter da = new SqlDataAdapter(sqlCat, con);
DataSet ds = new DataSet();

try
{
    con.Open();
    da.Fill(ds, "Categories");

    da.SelectCommand.CommandText = sqlProd;
    da.Fill(ds, "Products");
}
finally
{
    con.Close();
}

 

 

现在你得到了一个有两张表的DataSet。在Northwind数据库里,这两张表通过CategoryID字段关联。该字段是Categories表的主键,同时也是Products表的外键。遗憾的是,ADO.NET没有提供任何从数据源中读取关系并自动应用到DataSet的方法。我们需要手工创建一个DataRelation来表示关系。

 

 

DataRelation rela = new DataRelation("CatProds",
    ds.Tables["Categories"].Columns["CategoryID"],
    ds.Tables["Products"].Columns["CategoryID"]);

ds.Relations.Add(rela);
//遍历Categories表
StringBuilder htmlStr = new StringBuilder("");
foreach (DataRow dr in ds.Tables["Categories"].Rows)
{
    htmlStr.Append(dr["CategoryName"]).ToString());
    ...
    //读取子记录
    DataRow[] childRows = dr.GetChildRows(rela);
    foreach (DataRow childDr in childRows)
    {
        htmlStr.Append(childRows["ProductName"].ToString());
    }
}

 


 

查找特定行

 

DataRow[] matchRows = ds.Tables["Products"].Select("Discontinued = 0");

 

 


数据绑定

    虽然没有什么能阻止你循环非连接的数据并以手工的方式产生HTML,但是大部分情况下ASP.NET数据绑定可以大大简化这样的工作。后面会详细讨论数据绑定,但在继续研究下面的DataView示例前,你需要先知道一些基础知识。数据绑定背后的关键理念是由你创建数据对象和控件之间的关系,而由ASP.NET数据绑定架构负责生成相应的输出。

    GridView是最容易使用的数据绑定控件之一。GridView具有内建的自动生成HTML表的功能,每个记录生成HTML表中的一行,每个字段生成HTML表中的一列。

    在把数据绑定到GridView之类的数据绑定控件之前,首先要设置DataSource属性,该属性指向含有要显示的信息的对象。下面是一个DataSet:

    GridView1.DataSource = ds;

    因为数据绑定控件只能够绑定到单个表(而不是整个DataSet),所以还必须显式指定要使用的表。如:

    GridView1.DataMember = "Employees";  或者 GridView1.DataSource = ds.Tables["Employees"];

    最后,定义好了数据来源后,还需要调用控件的DataBind()方法把信息从DataSet复制到控件里。 

     

 

 

3、DataView类

 

    DataView为DataTable对象定义了一个视图——换句话说,是DataTable中支持自定义过滤和排序设置的数据的一个表现。DataView提供了Sort属性和RowFilter属性来允许你配置这些设置。借助这些属性,你可以选择视图中可见的数据。不过,它们并不会影响DataTable里的真实数据。

    在数据绑定的场景里,DataView特别有用。

 

使用DataView排序 

 

//Bind the original data to #1.
grid1.DataSource = ds.Tables["Employees"];
//Sort by last name and bind it to #2.
DataView view2 = new DataView(ds.Tables["Employees"]);
view2.Sort = "LastName";
grid2.DataSource = view2;
//multi-field sort
view2.Sort = "LastName, FirstName";

 

    绑定网格后,还要触发数据绑定的过程,这样数据才会从DataTable复制到控件上。可以单独为每个控件做这件事件,也可以调用Page.DataBind()为整个页面做这件事件。

 

使用DataView过滤

grid1.DataSource = ds.Tables["Products"];

//Filter for the Chocolade product
DataView view1 = new DataView(ds.Tables["Products"]);
view1.RowFilter = "ProductName = 'Chocolade'";
grid1.DataSource = view1;

Page.DataBind();

 


posted on 2012-06-20 12:40  jcsu  阅读(492)  评论(0)    收藏  举报