锋利的C#

  程序员博客 :: 首页 :: 新随笔 :: 订阅 :: 管理 :: 登录 ::
访问csharp的空间

随笔分类

随笔归档

个人相册

阅读排行榜

评论排行榜

最新评论

csharp 阅读(2821) 评论(1)

作者:Kanasz Robert

介绍
几个月前,老板要我创建一个Outlook插件,能够将email附件保存到SQL Server数据库。主要的问题是用户没办法直接连接SQL Server,因为SQL Server在防火墙的后面。第一个版本的文件传输模块没有将文件分割成小块,整个文件传送。当用户传送大文件时可能会产生问题。我决定添加ProgressBar显示文件传送进度,这是第二个问题,如何解决呢?我想到了一个好的解决方法,将文件分成独立部分传送到服务器。于是我创建了.NET Web Service用于传送文件。本文中我将介绍它是如何工作的并提供一个简单的项目。

背景
选项之一,如何使用.NET Web Service将大文件解析为多个部分传送到服务器临时文件中,然后存储到数据库。下面是具体步骤:
1)加载需要传送的文件
2)分割成小块
3)传送小块到服务器
4)合并小块到临时文件
5)保存临时文件到数据库
6)删除临时文件

当你加载好文件并分割为小块,可以独立传送到服务器,你可以很容易对传送进行监控。例如,你想传送1MB,你可以分割为10个部分,传送好一个部分,你可以更新一下ProgressBar,我会解释如何工作的。

数据库
这里,你肯能会问,为什么我使用数据库而不是将文件放到文件夹中。将文件存储到数据库有很多理由,文件存储到数据库表中,你备份数据库的时候,文件也随之备份了。当文件不是太大的时候这是一个有效的方法,对我的解决方案而言这是个简单的方法。
现在让我们看看数据库是怎么样的,这个例子中,我创建了名为FileStorage的数据库,只有一张名为File的表。

CREATE TABLE [dbo].[File](
[FileID] [int] IDENTITY(1,1) NOT NULL,
[Filename] [varchar](100) NULL,
[Content] [varbinary](max) NULL,
CONSTRAINT [PK_File] PRIMARY KEY CLUSTERED 
(
[FileID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, _
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

如你所见,这是只有三个字段的简单数据表,主键(FileID)是int类型,Filename是varchar类型用于存储文件名,varbinary类型的字段Content用于存储文件数据。

一个主要的问题是如何从表中获取数据?为了此目的,我写了一个spFile_Add存储过程,这只是一种方式,你也可以用标准的SQL命令,具体用哪一种方式取决于你。

spFile_Add

CREATE PROCEDURE [dbo].[spFile_Add] 
(
@Filename varchar(100),
@Content varbinary(max)
)
AS
BEGIN
INSERT INTO [File]([Filename],[Content]) VALUES (@Filename, @Content)
END


存储过程将一个新的文件插入到File表,@Filename参数varchar类型,描述了存储的文件名,第二个参数是@Content,varbinary类型,保存文件二进制内容。

创建Web Service

数据库建好后,我们可以写一个Web Service,当SQL Server(或其它资源)无法直接访问时,Web Services是非常有用的,就像我现在的情况。

使用Visual Studio 2008,我们用ASP.NET Web Service应用模板创建Web Service。这个例子中,我已经创建了了名为FileTransferWebService的项目,Web Service:FileTranser.asmx。默认有两个文件被创建:

AssemblyInfo.cs 包含了程序集的版本和配置信息
Web.config 定义应用如何运行(debug选项,cookies的使用,等等)。也包含了connectionStrings 区域, 你可以将数据库连接字符串写在这里。

本例中我创建的连接字符串名字为ftConnString:

<connectionStrings>
<add name="ftConnString" connectionString="user id=@USER;pwd=@PASSWORD;
data source=@DBSERVER;persist security info=False;
initial catalog=FileStorage;" providerName="System.Data.SqlClient" />
</connectionStrings> 



不要忘记设置一个有效的user id,pwd和data source。

每一个Web Service公开方法都有WebMethod属性。该属性写在方法的前面,便可以指定该方法为Web Service方法。在本例中,我创建了3个Web Service方法。

[WebMethod]
public string GetTempFilename()
{
try
{
Guid g = Guid.NewGuid();
//This method creates a temporary file on disk and 
//returns name of temporary file. 
//In this case it will be new Guid.
FileStream fs = new FileStream(Server.MapPath(g.ToString()), FileMode.Create);
fs.Close();
return g.ToString();
}
catch (Exception err)
{
throw new Exception("GetTempFilename: " + err.Message);
}
}

GetTempFilename()方法是你第一个调用的方法,该方法在服务器上创建了一个临时文件用于存储文件分块。

[WebMethod]
public void AppendToTempFile(string tempFilename, byte[] data)
{
try
{
//This method appends block of data to the end of the temporary file.
FileStream fs = new FileStream(Server.MapPath(tempFilename), FileMode.Append);
BinaryWriter bw = new BinaryWriter(fs);
bw.Write(data);
bw.Close();
fs.Close();
}
catch (Exception err)
{
throw new Exception("AppendToTempFile: " + err.Message);
}
} 

该方法将文件内容添加到刚才用GetTempFilename()方法生成的文件中。tempFilename 参数指定文件名。

[WebMethod]
public void SaveFileIntoDatabase(string filename, string tempFilename)
{
SqlDatabase sqlDatabase = new SqlDatabase
(ConfigurationManager.ConnectionStrings["ftConnString"].ConnectionString);
//Now we must get all bytes from file and put them into fileData byte array.
//This byte array we later save on database into File table.

byte[] fileData = GetBytes(tempFilename);

string sql = "spFile_Add";

SqlCommand sqlCommand = sqlDatabase.GetStoredProcCommand(sql) as SqlCommand;
try
{
sqlDatabase.AddInParameter(sqlCommand, "@Filename", SqlDbType.VarChar, filename);
sqlDatabase.AddInParameter(sqlCommand, "@Content", SqlDbType.VarBinary, fileData);
sqlDatabase.ExecuteNonQuery(sqlCommand);
}
catch (Exception err)
{
throw new Exception("SaveFileIntoDatabase: " + err.Message);
}
}
public byte[] GetBytes(string path)
{
FileStream fs=null;
BinaryReader br=null;
try
{
byte[] buffer = null;
fs = new FileStream(Server.MapPath(path), FileMode.Open, FileAccess.Read);
br = new BinaryReader(fs);
long numBytes = new FileInfo(Server.MapPath(path)).Length;
buffer = br.ReadBytes((int)numBytes);
br.Close();
fs.Close();
return buffer;
}
catch (Exception err)
{
throw new Exception("SaveFileIntoDatabase: " + err.Message);
}
}


SaveFileIntoDatabase(string filename, string tempFilename)方法将临时文件保存到数据库。filename参数指定原始文件名,tempFilename指定临时文件,将临时文件通过GetBytes(string path)加载到byte数组fileData,path指定了临时文件的物理路径。

通过SqlDatabase类连接数据库,在定义类实例之前,你必须包含命名空间Microsoft.Practices.EnterpriseLibrary.Data.Sql,如果你没有安装微软企业库,你可以到这里下载:http://msdn.microsoft.com/en-us/library/ff648951.aspx
你应使用SqlCommand执行存储过程spFile_Add,当文件上传成功,你可以用File.Delete()删除临时文件。


添加Web Service

当你想使用Web Service,必须首先告诉应用哪里可以找到它。Visual Studio 2008有一个很好的向导可以帮你添加Web Service的引用。

在解决方案浏览器中,右键点你的项目然后选择"添加服务引用"

然后点"高级"按钮


在"服务引用设置",点"添加Web引用"按钮。

在地址栏,输入Web Service的URL,在我的例子中,URL是:http://localhost/FileTransferWebService/FileTransfer.asmx,在添加Web 引用之前,应先写好Web引用的名称,我用FileTransferWebService这个名字,之后点击"添加引用"按钮。

使用Web Service
Web引用添加好后,可以开始写代码了。在我的例子里,我创建了一个名为frmMain的form,

拥有以下对象:
名字 描述
txtFile 存储上传文件的路径
cmdSelectFile 显示OpenFileDialog
pbTansferStatus 显示传输状态
cmdSave 开始传输操作

在将文件存入数据库之前,你需要选择文件。点击cmdSelect按钮,打开了OpenFileDialog 窗口,浏览文件并选择文件。选择好后,在txtFile文本框中可以看到文件的全路径。之后我们将通过FileStream使用该文件。FileStream类的实例名为fs,作为BinaryReader的参数,读取并存储字节到名为b的字节数组。ReadBytes函数从当前流读取指定字节数的数据到字节数组中。当我们得到了小块文件的字节数组,我们通过AppendToTempFile()方法传送到服务器。该方法由FileTransfer类提供。

private void cmdSelectFile_Click(object sender, EventArgs e)
{
//Using OpenFileDialog we can browse through file system
//and select a file, we want to save into to database.
OpenFileDialog dialog = new OpenFileDialog();
dialog.Filter = "All files (*.*)|*.*";
dialog.Title = "Select a file";
txtFile.Text= (dialog.ShowDialog() == DialogResult.OK)
? dialog.FileName : null;
}
private void cmdSave_Click(object sender, EventArgs e)
{
try
{
//now we have to set value of pbTransferSatatus to 0.
pbTransferStatus.Value = 0;
//Before saving we must create an instance of FileTransfer class, 
//which allows us use 3 methods mentioned earlier.
FileTransferWebService.FileTransfer ft =
new FileTransfer.FileTransferWebService.FileTransfer();

//GetTemFilename() method creates temporary file on server and returns it's name.
string tempFileName = ft.GetTempFilename();
FileInfo fi = new FileInfo(txtFile.Text);
using (FileStream fs = new FileStream(txtFile.Text,FileMode.Open,FileAccess.Read))
{
//using BinaryReader we will read blocks of data from file
//and append then to the temporary file.
using (BinaryReader br = new BinaryReader(fs))
{
fs.Position = 0;
int numBytesToRead = (int)fs.Length;
int numBytesRead = 0;
while (numBytesToRead > 0)
{
//I choose 100 000 bytes long blocks.
byte[] b = br.ReadBytes(100000);
ft.AppendToTempFile(tempFileName, b);
if (b.Length == 0)
{
break;
}
numBytesRead += b.Length;
numBytesToRead -= b.Length;
//When block is saved, than we can update pbTransferStatus value
int progressStatus = (int)((double)100 / 
(double)fs.Length * (double)numBytesRead);
pbTransferStatus.Value = progressStatus;
}
ft.SaveFileIntoDatabase(Path.GetFileName(txtFile.Text), tempFileName);
MessageBox.Show("File has been saved into database", 
"Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
catch (Exception err)
{
MessageBox.Show
(err.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}

结论

在这篇文章中,我描述了如何存储大文件到数据库表中,如何监控状态转移。 为了这个目的,我们编写了.NET Web服务。 编写Web服务是很容易的。 Visual Studio的向导使得编写services非常快。 在这个例子中,使用Web服务不是唯一的解决方案,它是一个可以选择的解决方案之一。

源代码下载:http://www.okbase.net/file/item/23081


评论列表
Dayane
So much info in so few words. Totlsoy could learn a lot.

发表评论
切换编辑模式