BoundFieldDataField="Description"HeaderText="Description"
SortExpression="Description"/>
BoundFieldDataField="BrochurePath"HeaderText="Brochure"
SortExpression="BrochurePath"/>
GridView>
ObjectDataSourceID="CategoriesDataSource"runat="server"
OldValuesParameterFormatString="original_{0}"
SelectMethod="GetCategories"TypeName="CategoriesBLL">
ObjectDataSource>
在浏览器查看该页(如图5)。
列出了所有的8个类,除了Seafood,其它7个类的BoundField列里显示各自的BrochurePath值。
由于Seafood的BrochurePath为NULL值,看起来为空格。
图5:
显示了每个类别的Name,Description和BrochurePath值
与其显示BrochurePath的text值,不如创建一个指向小册子的链接。
移除BrochurePath,代之以HyperLinkField。
设它的HeaderText属性为“Brochure”,
Text属性为“ViewBrochure”,DataNavigateUrlFields属性为“BrochurePath”。
图6:
添加一个指向BrochurePath的HyperLinkField
这样将在GridView里添加一列链接,如图7所示。
点“ViewBrochure”时要么直接在浏览器显示PDF,要么提示用户下载该文件。
这取决于浏览器的设置以及是否安装了PDF阅读器。
图7:
点击“ViewBrochure”访问某类的Brochure
图8:
显示某类的PDF文件
隐藏无小册子图片的类的“ViewBrochure”文本
如图7所示,不管某个类的BrochurePath是否为NULL值,名为BrochurePath的HyperLinkField都呈现为其Text属性(“ViewBrochure”)。
当然,如果BrochurePath为NULL值,链接只显示为文本(而不带下划线),就像Seafood类一样(见图7)。
与显示文本“ViewBrochure”相比,更为可取的是将那些BrochurePath值为空的类显示为“NoBrochureAvailable”。
为达此目的,我们需要用到TemplateField,使其产生一个基于BrochurePath值的合适的结果。
我们先来看看如何实现,就像在教程之12《在GridView控件中使用TemplateField》一样。
在“编辑列”对话框里选中名为BrochurePath的HyperLinkField,再点“ConvertthisfieldintoaTemplateField”链接,将其转换为TemplateField。
图9:
将HyperLinkField转换为TemplateField
这样将创建一个TemplateField,其ItemTemplate模板包含一个HyperLinkWeb控件,该控件的NavigateUrl属性为BrochurePath值。
用下面的代码将其替换掉:
TemplateFieldHeaderText="Brochure">
<%#GenerateBrochureLink(Eval("BrochurePath"))%>
TemplateField>
然后,在ASP.NET页面的“后台代码”里添加一个protected类型的GenerateBrochureLink方法,它接受一个输入参数并返回一个字符串。
protectedstringGenerateBrochureLink(objectBrochurePath)
{
if(Convert.IsDBNull(BrochurePath))
return"NoBrochureAvailable";
else
returnstring.Format(@"ViewBrochure",
ResolveUrl(BrochurePath.ToString()));
}
该方法判断传入的值是否为NULL。
如果是,则返回一个消息指出该类没有小册子文件;相反,如果传入值不为空,将显示为一个链接。
我们注意到,当BrochurePath值不为空时,将调用ResolveUrl(url)方法。
该方法的作用在于将传入的相对路径转换为物理路径。
比如应用程序的根目录在/Tutorial55,ResolveUrl("~/Brochures/Meats.pdf")返回的路径是/Tutorial55/Brochures/Meat.pdf.
图10为经过上述修改后的界面。
我们注意到Seafood类的BrochurePath列现在显示为文本“NoBrochureAvailable”.
图10:
没有小册子的类将显示为文本“NoBrochureAvailable”
第3步:
新增页面以显示类的图片
当用户访问一个ASP.NET页面时,他将接收该页面的HTML代码。
HTML代码仅仅包含了text文本,而并不包含任何的二进制数据。
任何的二进制数据,比如图片,音乐文件、Flash程序、WindowsMediaPlayer视频等,以独立资源的形式存放于服务器。
HTML只包含了这些文件的引用,并不包含这些文件本身。
比如,在HTML里元素用来引用一张图片,其src属性指向该图片文件,如:
当浏览器收到HTML代码时,它向服务器发送获取图片的请求并将其显示在浏览器中,该模式对所有的二进制数据都适用。
在第2步中,我们没有在页面的HTML标记里将小册子显示在浏览器,而是在HTML标记里提供一个超链接,当点击它是,导致浏览器直接请求PDF文件。
为了显示或允许用户下载储存在数据库中的二进制数据,我们需要另外创建一个页面,用于从数据库返回所需的数据。
对我们的应用程序而言,由于直接存储在数据库中的二进制数据只有一项——类的图片,所以我们需要一个页面,当需要时从数据库返回某个特定类的图片。
在BinaryData文件夹添加一个DisplayCategoryPicture.aspx页面,注意不要使用母版页。
该页面接受一个包含CategoryID值的查询字符串,返回Picture列的二进制数据。
由于该页只返回二进制数据,所以我们不需要页面的HTML部分有任何代码。
进入页面的“源码”模式,删除页面的所有代码,只保留<%@Page%>部分。
也即:
DisplayCategoryPicture.aspx页面的声明代码应该只由如下的单独行构成:
<%@PageLanguage="C#"AutoEventWireup="true"
CodeFile="DisplayCategoryPicture.aspx.cs"
Inherits="BinaryData_DisplayCategoryPicture"%>
如果<%@Page%>里包含有MasterPageFile属性,将其删除,同时在后台代码类的Page_Load事件处理器里添加如下代码:
protectedvoidPage_Load(objectsender,EventArgse)
{
intcategoryID=Convert.ToInt32(Request.QueryString["CategoryID"]);
//Getinformationaboutthespecifiedcategory
CategoriesBLLcategoryAPI=newCategoriesBLL();
Northwind.CategoriesDataTablecategories=
categoryAPI.GetCategoryWithBinaryDataByCategoryID(categoryID);
Northwind.CategoriesRowcategory=categories[0];
//OutputHTTPheadersprovidinginformationaboutthebinarydata
Response.ContentType="image/bmp";
//Outputthebinarydata
//ButfirstweneedtostripouttheOLEheader
constintOleHeaderLength=78;
intstrippedImageLength=category.Picture.Length-OleHeaderLength;
byte[]strippedImageData=newbyte[strippedImageLength];
Array.Copy(category.Picture,OleHeaderLength,
strippedImageData,0,strippedImageLength);
Response.BinaryWrite(strippedImageData);
}
代码先读取查询字符串的CategoryID值,并对名为categoryID的变量赋值。
然后,通过调用CategoriesBLL类的
GetCategoryWithBinaryDataByCategoryID(categoryID)方法获取图片数据,再通过Response.BinaryWrite(data)方法向客户端返回数据。
不过在此之前先要剥离数据的OLE报头。
怎么实现呢?
创建一个名为strippedImageData的byte数组,它包含的字节刚好比Picture列的数据少78。
而Array.Copy方法将从category.Picture的第78个字节开始复制数据(即刚好剥离OLE报头)。
代码中的Response.ContentType属性指定了要返回内容的MIMEtype,以便浏览器知道如何显示数据。
由于Categories表的Picture列存储的是位图图片,故在这里,位图图片的MIMEtype是(image/bmp).如果你忽视了MIMEtype,绝大多数浏览器也可以正确的显示图像,因为,它们能根据图像文件的二进制数据的内容而推断其类型。
即便如此,还是尽可能的使用MIMEtype。
关于MIMEtype的全都清单更多信息参加文章《InternetAssignedNumbersAuthority’swebsite》
创建页面后,可以访问页面
DisplayCategoryPicture.aspx?
CategoryID=categoryID来查看某个特定类的图片。
图11显示的是Beverages类的图片,页面为
DisplayCategoryPicture.aspx?
CategoryID=1.
图11:
显示类Beverages的图片
有时候,当你访问DisplayCategoryPicture.aspx?
CategoryID=categoryID页面时,有可能显示这样的提示:
“Unabletocastobjectoftype'System.DBNull'totype'System.Byte[]'”。
原因有可能是如下2方面。
第一,表Categories的Picture列允许为NULL值,而DisplayCategoryPicture.aspxpage页面总是假定传入的为非NULL值。
当Picture为NULL值时,不能直接访问CategoriesDataTable的Picture属性。
如果你允许Picture为NULL值,添加如下代码:
if(category.IsPictureNull())
{
//Displaysome"NoImageAvailable"picture
Response.Redirect("~/Images/NoPictureAvailable.gif");
}
else
{
//SendbackthebinarycontentsofthePicturecolumn
//...SetContentTypepropertyandwriteout...
//...dataviaResponse.BinaryWrite...
}
上述代码假定在Images文件夹里存在名为NoPictureAvailable.gif的图片,当某个类没有图片时,就显示该图片。
另一种情况:
当你在向导里选用“使用SQL语句”的模式再次运行主查询时,它将影响GetCategoryWithBinaryDataByCategoryID方法的SELECT命令返回的列(换句话说,主查询没有返回Picture列,再次运行主查询时将使GetCategoryWithBinaryDataByCategoryID方法也不会返回Picture列)。
所以,应确保GetCategoryWithBinaryDataByCategoryID方法的SELECT命令返回Picture列。
注意:
每次访问DisplayCategoryPicture.aspx页面时,都会访问数据库并返回所需的图片。
如果图片自最近一次访问以来没有改变过的话,这样每次访问数据库再返回数据的做法效率是不高的。
幸运的是,HTTP允许使用conditionalGETs,这样的话,客户端使HTTP请求发送一个If-Modified-SinceHTTPheader。
If-Modified-SinceHTTPheader包含了客户端最近一次从服务器获取的数据以及时间。
如果请求的内容没有发生改变,服务器响应为NotModifiedstatuscode(304),并不返回请求的内容。
简而言之,如果请求的资源自最近一次访问以来没发送改变的话,服务器将不会回传该资源,以达到减轻服务器负荷的目的。
关于If-Modified-Sinceheader和conditionalGET的详细信息请参阅文章《HTTPConditionalGETforRSSHackers》和《ADeeperLookatPerformingHTTPRequestsinanASP.NETPage》
第四步:
在GridView控件里显示CategoryPictures
现在我们有一个web页面来显示某个特定种类的图片的。
通过ImageWeb控件或HTML元素来指向DisplayCategoryPicture.aspx?
CategoryID=categoryID页面,从而达到显示该图片的目的。
我们可以在GridView控件或DetailsView控件的ImageField里显示图片。
ImageField的DataImageUrlField属性、DataImageUrlFormatString属性与HyperLinkField的DataNavigateUrlFields属性、DataNavigateUrlFormatString属性用法相似。
让我们对DisplayOrDownloadData.aspx页面里名为Categories的GridView控件进行扩充。
添加一个ImageField,设其DataImageUrlField属性为CategoryID;
DataImageUrlFormatString属性为DisplayCategoryPicture.aspx?
CategoryID={0}。
这样将为GridView增加一列,呈现为一个元素,其src属性为DisplayCategoryPicture.aspx?
CategoryID={0},其中{0}将由GridViewrow的CategoryID值填充。
图12:
为GridView控件添加一个ImageField
添加完成后,你的GridView控件的声明代码看起来应像下面这样:
GridViewID="Categories"runat="server"AutoGenerateColumns="False"
DataKeyNames="CategoryID"DataSourceID="CategoriesDataSource"
EnableViewState="False">
BoundFieldDataField="CategoryName"HeaderText="Category"
SortExpression="CategoryName"/>
BoundFieldDataField="Description"HeaderText="Description"
SortExpression="Description"/>
TemplateFieldHeaderText="Brochure">
<%#GenerateBrochureLink(Eval("BrochurePath"))%>
TemplateField>