前端碰到对在一个系统遇到流程控制中需要存储在数据库存储一个签名图片的问题-一直控制不好, 今天特别关于这个问题详细看了一下.其实这个问题网上资源还是相当多的,但问题是过于凌乱 资料残缺不全 甚至我感觉其中有相当的一部分会对读者产生一些误导.对于Asp.net中存储图片我在08年一月份就做了一个详细解决方案,今天在这个基础主要对一些细节控制上以及页面显示上做了完善,详细步骤如下:
首先声明一下开发环境:VS2008+SQL2005数据库+.NET FrameWork 3.5版本
(1)存储图片ImageStore表数据库设计:
1 create table StoreImage
2 (
3 id int not null identity ( 1 , 1 ) primary key ,
4 markname varchar ( 100 ) not null , -- 图片备注名称
5 markContent image not null , -- 文件内容
6 markType varchar ( 100 ) not null , -- 保存文件类型 用于生成
7 markSize int not null , -- 图片长度 读取数据用
8 markLinkUrl varchar ( 1000 ) not null , -- 数据库路径
9 markDate datetime not null default ( getdate ()) -- 上传时间
10 )
11 go
其中在表中设计中添加了上传图片文件类型和文件大小(Byte[]字节大小),主要为了读取时对图片显示进行控制.请参考后面编码说明.存储图片内容采用Image类型,SQL2005数据容量为2G,对应C#中类型Byte[](字节数组),其中在设计中我还参考使用SQL中Binary类型,但是测试后发现Binary类型容量范围1-8000字节,对于图片容量太小, markLinkUrl为了测试以图片路径方式存储并读取显示在页面这种方式 请参考后面详细说明.
(2)图片存储到数据库并单一读取:
图片存储:通过文件上传获取图片并转换成Byte[]字节数组,保存到数据库Image字段,页面设计如下:
ExpandedBlockStart.gif 代码
1 <!-- 说面这是全部的页面代码: -->
2 < form id ="form1" runat ="server" style ="font-size:12px;" enctype ="multipart/form-data" >
3
4 备 注: < asp:TextBox ID ="markname" runat ="server" ></ asp:TextBox >
5 上 传: < asp:FileUpload ID ="FileUpload1" runat ="server" />
6 < asp:Button ID ="Button1" runat ="server" OnClientClick ="return checkClint()" Text ="上 传" onclick ="Button1_Click" />
7
8 < script language ="javascript" type ="text/javascript" >
9 function checkClint()
10 {
11 var getmarkname = document.getElementById( " markname " );
12 var getfile = document.getElementById( " FileUpload1 " );
13
14 if (getmarkname.value == "" )
15 {
16 alert( ' 请输入图片备注名称! ' );
17 getmarkname.focus();
18 return false ;
19 } else if (getfile.value == "" )
20 {
21 alert( ' 请选择上传文件路径! ' );
22 getfile.focus();
23 return false ;
24 } else
25 {
26 return true ;
27 }
28 }
29 </ script >
30
31 < asp:Button ID ="Button2" runat ="server" onclick ="Button2_Click" Text ="读取图片" />
32
33
34 < asp:Button ID ="Button3" runat ="server" onclick ="Button3_Click" Text ="读取到Image控件中" />
35
36 < asp:Button ID ="Button5" runat ="server" onclick ="Button5_Click" Text ="存储链接方式获取图片" />
37
38 < asp:Button ID ="Button4" runat ="server" onclick ="Button4_Click" Text ="下载图片" />
39 < br />
40 < br />
41 < asp:Image ID ="Image1" runat ="server" />
42 < br />
43 图片路径方式读取: < br />
44 服务器端: < asp:Image ID ="Image2" runat ="server" />
45 < br />
46 < asp:Label ID ="Label2" runat ="server" ></ asp:Label >
47 < br />
48 客户端Img: < img alt ="" runat ="server" id ="clintimg" />
49 < br />
50 < asp:Label ID ="Label1" runat ="server" ></ asp:Label >
51 </ form >
在页面From表单添加了一个属性-在页面Form中设置属性enctype -设置或获取表单的 MIME 编码
单一保存文件到数据库通用方法【注明:通用方法写在Button1_Click事件中-命名没有修改主要为了功能】 通用方法如下:
代码
1 /// <summary>
2 /// 上传文件同时并保存到数据中统一
3 /// Author:chenkai Date:2010年2月2日16:24:29
4 /// </summary>
5 protected void Button1_Click( object sender, EventArgs e)
6 {
7 // 获取数据
8 string getname = this .markname.Text;
9 string getfile = this .FileUpload1.PostedFile.FileName;
10
11 // 上传文件
12 string getlastpath = FileUploadCompant( this .FileUpload1);
13
14 // 获取上传文件流
15 byte [] getbyte = new byte [ this .FileUpload1.PostedFile.ContentLength];
16 Stream filestream = this .FileUpload1.PostedFile.InputStream;
17
18 // 读入数据
19 filestream.Read(getbyte, 0 , this .FileUpload1.PostedFile.ContentLength);
20
21 // 插入数据
22 #region
23 string sql = " insert into StoreImage(markname,markContent,markType,markSize,markLinkUrl) values(@name,@content,@type,@size,@link) " ;
24
25 SqlParameter[] getpars = new SqlParameter[ 5 ];
26 getpars[ 0 ] = new SqlParameter( " @name " , getname);
27 getpars[ 1 ] = new SqlParameter( " @content " , getbyte); // 文件内容插入This.Fileupload1.FileBytes同样可以直接转换成Byte数组不用转换
28 getpars[ 2 ] = new SqlParameter( " @type " , this .FileUpload1.PostedFile.ContentType); // 保存文件类型
29 getpars[ 3 ] = new SqlParameter( " @size " , this .FileUpload1.PostedFile.ContentLength); // 文件长度
30 getpars[ 4 ] = new SqlParameter( " @link " , getlastpath);
31
32 int getrescount = DBUtility.SqlHelper.ExecuteNonQuery(DBUtility.SqlHelper.connString,CommandType.Text,sql,getpars);
33
34 if (getrescount == 1 )
35 {
36 // 添加成功
37 ScriptManager.RegisterStartupScript( this .Page, this .GetType(), " aler " , " alert( '图片记录成功添加到数据库'); " , true );
38 }
39 else
40 {
41 // 添加失败
42 ScriptManager.RegisterStartupScript( this .Page, this .GetType(), " aler " , " alert( '图片记录添加失败'); " , true );
43 }
44
45 #endregion
46 }
This.Fileupload1.FileBytes可以直接把上传内容转换成字节数组而不必通过文件流来读取上传文件内容,这种方式更为快捷;如上图片成功保存到数据库 接下来就是如何读取和显示控制的问题:
(3)数据库存储图片的读取和显示控制:
从数据库中读取到字节流后把图片直接写入页面并对显示进行控制 读取方法如下【该方法下载Button2_Click中】:
代码
1 /// <summary>
2 /// 读取数据库中图片并显示出来
3 /// Author:chenkai Date:2010年2月2日16:48:18
4 /// </summary>
5 protected void Button2_Click( object sender, EventArgs e)
6 {
7 // 获得数据
8 string sql = " select * from StoreImage order by id desc " ;
9
10 #region
11 using (SqlDataReader getreader = DBUtility.SqlHelper.ExecuteReader(DBUtility.SqlHelper.connString, CommandType.Text, sql))
12 {
13 if (getreader != null && getreader.HasRows)
14 {
15 // 读取数据
16 while (getreader.Read())
17 {
18 Response.ContentType = getreader[ " markType " ] as string ;
19 Response.OutputStream.Write(getreader[ " markContent " ] as byte [], 0 , Convert.ToInt32(getreader[ " markSize " ].ToString()));
20 Response.End();
21 }
22 }
23 }
24 #endregion
25 }
数据库中MarkType字段用来设置请求回发页面内容类型,获得字节流后把回发内容转换成输出流然后输出到当前页面,当然也可以直接使用Response.BinaryWrite()写入页面,图片自动显示.但是有人会说我想把它显示在页面一个Image控件中或是放到一个DIV层中这样实际需求. 这就是在实际需求对读取图片进行显示控制问题. 制作过验证码图片应该知道,验证码生成图片单独放在一个页面让后通过Image控件的ImageUrl来链接该页面,即可实现在Image控件控制,同理这种方式也是适用的: -添加了一个页面用来存储生成图片,详细代码如下:OutPutImageDemo页面后台编码:
代码
1 // PageLoad事件中加载图片 并显示到OutPutImageDemo页面
2 // Author:陈凯 Date:2010年2月3日10:28:17
3 protected void Page_Load( object sender, EventArgs e)
4 {
5 if ( ! IsPostBack)
6 {
7 // 获得数据
8 string sql = " select * from StoreImage order by id desc " ;
9
10 #region
11 using (SqlDataReader getreader = DBUtility.SqlHelper.ExecuteReader(DBUtility.SqlHelper.connString, CommandType.Text, sql))
12 {
13 if (getreader != null && getreader.HasRows)
14 {
15 // 读取数据
16 while (getreader.Read())
17 {
18 Response.ContentType = getreader[ " markType " ] as string ;
19 Response.OutputStream.Write(getreader[ " markContent " ] as byte [], 0 , Convert.ToInt32(getreader[ " markSize " ].ToString()));
20 Response.End();
21 }
22 }
23 }
24 #endregion
25 }
26 }
那么在TestImageStoreToDB.aspx页面中一个控件中获取该图片 只需设置图片的链接路径即可 代码如下:
1 this .Image1.ImageUrl =
"
OutPutImageDemo.aspx
"
;
//
链接输出图片页面即可
(4)数据库存储图片路径方式:
直接在数据库存储图片对资源图片较多, 图片文件较大 类似银行内部系统中对VIP用户签名就是用签名图片方式存储的 这主要为了安全上考虑,实际需求中使用次数频繁 且常常更新 无疑中这种使数据库中数据显得有些臃肿 数据容量增大,同时也增加了访问数据库服务器的负载,而使用存储图片路径的方式:图片文件放在服务器硬盘上,数据库中只需一个文件路径指向硬盘上图片文件即可 存储方式相比存储字节流要简单容易控制 但是我在网上发现关于这种页面显示的诸多问题如下我说明一下再Asp.net对图片页面显示控制:
数据库中存储路径为绝对路径: 截图如下
现在页面放一个服务器端的Image控件和Html中的<img/>来验证. 如果直接用绝对路径对控件进行赋值 页面并不显示.其实这就涉及到.net中绝对路径,相对路径和虚拟路径三者之间转换的问题:
绝对路径是不行的,在硬盘上存储文件命名是唯一的同时不会存在不同的文件格式相同的命名情况,过滤绝对路径,从绝对路径就能看出图片的存储位置就在根目录下FileuploadDict文件夹下,我们只需把这个绝对路径转换成程序可用相对路径即可: 同时我们用服务器端的Image控件和Html测试一下结果:
// getfilepath是获得数据中图片存储路径(markLinkUrl字段)数据
2 if ( ! string .IsNullOrEmpty(getfilepath))
3 {
4 // 截取当前图片文件名
5 string getclintpath = getfilepath.Substring(getfilepath.LastIndexOf( ' \\ ' ));
6
7 // 路径~/ 仅对 ASP.NET 的服务器控件起作用 ~/ 的意思是相对站点的虚拟根路径
8 // “~”表示的路径是当前应用程序的跟目录。“~”和上面介绍的“/”最大的区别是由服务器进行动态解释
9 getclintpath = @" ~\FileuploadDict " + getclintpath; // 拼接成客户端服务器端路径
10
11 // 设置Html中<Img/> 注意"~"对基于后台程序来动态解析的 所以Img标签 添加Runat=“Server” 在服务器端可以访问到
12 this .clintimg.Src = getclintpath; // 测试结果成功显示
13 }
来看一下对于服务器端控件页面控制显示方式:
1 // getfilepath是从数据库获得图片存储绝对路径[markLinkUrl]的值
2 if ( ! string .IsNullOrEmpty(getfilepath))
3 {
4 // 截取文件的名称
5 string getclintpath = getfilepath.Substring(getfilepath.LastIndexOf( ' \\ ' ));
6
7 // 服务器端-重置获取当前路径下服务器端虚拟应用程序根路径-成功
8 // 使用"/"所有的路径都是从站点的跟目录开始的,例如/default.aspx指向的是localhost/default.aspx
9 this .Image2.ImageUrl = HttpContext.Current.Request.ApplicationPath + @" FileuploadDict " + getfilepath.Substring(getfilepath.LastIndexOf( ' \\ ' ));
10 // 测试结果:页面成功显示
来看一下对于服务器端控件页面控制显示方式:
在设置服务器端Image控件时用的是HttpContent.current.Request[获取当前请求的HttpRequest对象].ApplicationPath[获取服务器上 ASP.NET 应用程序的虚拟应用程序根路径] 来设置.我们来比对一下页面的路径:
Html中<Img/>标签: ~\FileuploadDict\2010-02-03-08-35-47rr4hnz45msimfqzh4tcdv545http_imgload6.jpg
Image服务端控件: /FileuploadDict\2010-02-03-08-35-47rr4hnz45msimfqzh4tcdv545http_imgload6.jpg
上面用的都是虚拟目录下相对路径来访问,如果直接通过拼接类似如上字符串 来对控件赋值 在Html页面时不识别的 ~/ 仅对 ASP.NET 的服务器空件起作用,.其实这就是关于Asp.net中相对路径的使用问题:处理方式如下;
(A): 如果链接中,源端点和目标端点在同一个目录下,则在链接中只需要指明目标端点的文档名称就可以了
(B):使用"/"所有的路径都是从站点的跟目录开始的,例如/default.aspx指向的是localhost/default.aspx
(C):如果在链接中,源端点和目标端点不位于同一个目录下,则只需要将目录的相对关系表达出来就可以了。如果链接指向的文档没有位于当前目录的子级目录中,则可以利用”..”符号来表示当前的父目录,多个..符号可以表示根高的父级目录,从而构建出目录的相对位置.
(D):在ASP.NET里增加了一个新的表达方法“~”,“~”表示的路径是当前应用程序的跟目录。“~”和上面介绍的“/”最大的区别是由服务器进行动态解释。由于”~”是相对于应用程序的根目录,所以利用它可以简化路径的设置,在某些情况下似乎还必须使用该控件
前面3种方法都是客户端解析的,也就是html种的源码就是这样,由浏览器来解析定向;而第四种方法是在服务器端解析,浏览器并不识别,所以常常在后台拼接时会出现字符串和服务器控件字符串相同 页面却无法显示图片问题,归咎还是Asp.net相对路径使用问题.
项目源码下载地址:/Files/chenkai/项目编码源文件/TestNetWorkConAPI.rar[修改后下载链接-测试下载通过]