由Stream.Position问题而引发的思考

    Web开发中的文件上传功能一直以来都是一个比较棘手的问题,特别是在开发Ajax网站时,面对功能复杂的页面元素,实现页面无刷新的文件上传功能就变得更加复杂。我在http://www.cnblogs.com/jaxu/archive/2009/05/19/1459796.html一文中介绍过如何通过页面中隐藏的iFrame提交表单达到文件的上传,而且不刷新当页中的Form,模拟页面的无刷新文件上传功能。而且一般情况下,我们在服务端会这样来处理要上传的文件。
private List<ImageEntity> GetUploadImages()
{
    List
<ImageEntity> images = new List<ImageEntity>();
    HttpFileCollection files 
= Request.Files;
    
int fileLen;
    ImageEntity image 
= null;

    
if (files != null && files.Count > 0)
    {
        
for (int i = 0; i < files.Count; i++)
        {
            
if (files[i].FileName.Length != 0)
            {
                
try
                {
                    fileLen 
= files[i].ContentLength;
                    Byte[] bytes 
= new Byte[fileLen];
                    stream.Read(bytes, 
0, fileLen);

                    image 
= new ImageEntity();
                    image.Type 
= files[i].ContentType;
                    image.ImageBlob 
= bytes;
                    image.Title 
= Path.GetFileName(files[i].FileName);
                    images.Add(image);
                }
                
catch { }
            }
        }
    }

    
return images;
}
    这是一个得到客户端上传的图片文件的示例代码,其中的ImageEntity是一个Image的实体类,我们可以通过该实体类描述的信息将图片存储在数据库中(或者存储在服务器磁盘上)。
public class ImageEntity
{
    
public ImageEntity()
    {
    }

    
public ImageEntity(string title, Byte[] imageBlob, string type)
    {
        Title 
= title;
        ImageBlob 
= imageBlob;
        Type 
= type;
    }

    
public string Title { getset; }
    
public string Type { getset; }
    
public Byte[] ImageBlob { getset; }
}

    这么使用是没有问题的!当我们上传图片时,页面通过Post方法提交到服务端,服务端构造一个和图片字节大小相同的byte数组,通过HttpPostedFile的InputStream属性得到一个System.IO.Stream对象,然后使用该对象的Read方法将图片数据流读到byte数组中。这个过程页面是需要回传的,并且下一次用户上传图片时Stream对象会被重新构造,然后重新填充byte数组。

    不过在一次MOSS开发中我偶然地发现使用该方式上传图片时,保存图片到数据库没有出现问题,当从数据库中读取图片时却显示了一个红色的叉,表示图片不可用或加载失败。仔细查看了一下数据库中的数据,除了保存图片二进制数据的字段显示为<Binary data>外,其余字段的数据都很正常,没有发现什么异常情况,调试了一下程序,图片上传和保存的过程中并没有抛出任何异常,一切都很顺利,但就是在读取图片的时候页面上无法正常加载图片。

    一开始我就感觉这个问题很奇怪,看来问题应该是出在上传图片时图片的二进制数据获取得不正确或不完整,我再次调试跟踪了一下代码,发现在上传的过程中byte数组中的值始终都是0,即便是在通过Stream.Read方法填充数据之后也是如此。这到底是为什么?难道我用错对象了?查了一下MSDN,发现上面给出的示例基本上也是通过这种方法得到要上传的文件并通过Stream.Read方法填充byte数组的,MSDN的示例代码肯定是不会有错误的,那错误到底在哪里呢?我开始迷惑了...

    仔细搜了搜Google,其中有一位朋友给出的帮助对我很有用,他建议在使用Stream.Read方法填充byte数组前先判断一下Stream.Position的值是否为0,如果不为0就先将它置为0,然后再进行byte数组的填充。不过我先是想到每次页面在上传文件时所构造的Stream对象都是新的(因为页面会PostBack回来),我并没有在程序的任何地方缓存Stream对象,既然Stream对象是新的,那么Position属性的值肯定就是0啦。我抱着半信半疑的心态试了试这位朋友介绍的方法,果然奏效了,看来Stream对象真的是在某个地方被缓存了,或者说在前一次文件上传之后Stream对象没有被完全释放。于是我修改了上面的那个得到上传图片的方法。

private List<ImageEntity> GetUploadImages()
{
    List
<ImageEntity> images = new List<ImageEntity>();
    HttpFileCollection files 
= Request.Files;
    
int fileLen;
    ImageEntity image 
= null;

    
if (files != null && files.Count > 0)
    {
        
for (int i = 0; i < files.Count; i++)
        {
            
if (files[i].FileName.Length != 0)
            {
                
try
                {
                    fileLen 
= files[i].ContentLength;
                    Byte[] bytes 
= new Byte[fileLen];
                    
using (Stream stream = files[i].InputStream)
                    {
                        stream.Position 
= 0;
                        stream.Read(bytes, 
0, fileLen);
                    }

                    image 
= new ImageEntity();
                    image.Type 
= files[i].ContentType;
                    image.ImageBlob 
= bytes;
                    image.Title 
= Path.GetFileName(files[i].FileName);
                    images.Add(image);
                }
                
catch { }
            }
        }
    }

    
return images;
}
    因为我是在MOSS平台上开发的,与普通的ASP.NET项目就有很多的不同,有可能会受到很多MOSS本身的东西影响,例如缓存机制等。但不管怎样,涉及到像Stream这样的对象,在使用之后最好都让它立即释放,因为即使Stream不被缓存,它也有可能长时间占用内存而消耗掉很多的服务器资源。建议在Using语句中使用Stream,并且在使用Read方法填充byte数组前重置Position为0,这样可以确保byte数组被正确填充,从而保证可以得到正确的文件数据。
posted @ 2009-05-19 15:22  Jaxu  阅读(3105)  评论(2编辑  收藏  举报