Silverlight以及Mvc最佳文件下载解决方案(附源码)

(一)前言

 

目前,在Silverlight中下载文件通常采用两种方式进行文件下载:

1、客户端通过SaveFileDialog类进行文件下载,服务端使用字节数组(byte[])进行数据传递。

2、客户端通过访问服务端的一般处理文件(.ashx)来进行文件下载。

 

对于第1种方式下载,缺陷主要为:点击下载之后,弹出的SaveFileDialog对话框居然没有文件名!!!(必须自己手写文件名,这里Silverlight还有待提高)。Silverlight中的SaveFileDialog相关属性和方法如下:

 1       public   sealed   class  SaveFileDialog
 2      {
 3            public  SaveFileDialog();
 4            public   string  DefaultExt {  get set ; }
 5            public   string  Filter {  get set ; }
 6            public   int  FilterIndex {  get set ; }
 7            public   string  SafeFileName {  get ; }
 8           public  Stream OpenFile();
 9           public   bool ?  ShowDialog();
10      }

 

对于第2种方式下载的话,容易暴露相关的信息(处理文件页面有时直接在地址栏显示相关的信息)。

Silverlight主要通过HtmlPage.Window.Navigate(new Uri(url));来访问一般处理文件,一般处理文件执行文件下载(Response来执行);

 

到目前为止,开发华为悍马项目已经半年多了,主要以MVC和Silverlight进行开发。因此,针对于当前的项目,本人试图以Silverlight调用Mvc action来进行下载,如下的内容都将围绕该主题进行讲解(目前这个还木有更新到项目中,仅仅是本人笔记本上设计的)。

 

(二)相关类图以及FileDownloadResult

 

 

在MVC中,Action主要以ActionResult来作为返回结果,然后调用ActionResult的ExecuteResult()方法来执行相关操作。然而,到目前为止关于文件操作的ActionResult主要为FileStreamResult、FileContentResult以及FilePathResult,这些都不太方便使用(对于文件下载来说)。因此本人打算以FileDownloadResult类来进行文件下载的相关操作。

 

(三)具体实现

 

1、FileDownloadResult类的具体实现

FileDownloadResult类主要是实现抽象类ActionResult的ExecuteResult(ControllerContext context)方法,具体代码如下:

 1       public   class  FileDownloadResult : ActionResult
 2      {
 3           public  FileDownloadResult( string  fileFullPath)
 4          {
 5               this .FileFullPath  =  fileFullPath;
 6          }
 7 
 8           public  FileDownloadResult( string  fileName,  string  fileFullPath)
 9          {
10               this .FileName  =  fileName;
11               this .FileFullPath  =  fileFullPath;
12          }
13 
14           public   string  FileName 
15          {
16               get
17               private   set ;
18          }
19 
20           public   string  FileFullPath 
21          {
22               get ;
23               private   set
24          }
25 
26           public   override   void  ExecuteResult(ControllerContext context)
27          {
28               if  (context  ==   null   ||  ( ! File.Exists( this .FileFullPath)))
29              {
30                   return ;
31              }
32 
33              FileInfo fileInfo  =   new  FileInfo( this .FileFullPath);
34              SetFileName(fileInfo);
35              SetResponse(context.HttpContext.Response);
36              OutputFile(context.HttpContext.Response, fileInfo);
37          }
38 
39           private   void  SetFileName(FileInfo fileInfo)
40          {
41               if  ( string .IsNullOrWhiteSpace( this .FileName))
42              {
43                   this .FileName  =  fileInfo.Name;
44              }
45          }
46 
47           private   static   void  OutputFile(HttpResponseBase response, FileInfo fileInfo)
48          {
49              response.WriteFile(fileInfo.FullName,  0 , fileInfo.Length);
50              response.Flush();
51              response.End();
52          }
53 
54           private   void  SetResponse(HttpResponseBase response)
55          {
56              SetResponseState(response);
57              SetResponseHead(response);
58              SetResponseContent(response);
59          }
60 
61           private   static   void  SetResponseState(HttpResponseBase response)
62          {
63              response.ClearHeaders();
64              response.Clear();
65              response.Expires  =   0 ;
66              response.Buffer  =   true ;
67          }
68 
69           private   static   void  SetResponseContent(HttpResponseBase response)
70          {
71              response.ContentEncoding  =  Encoding.UTF8;
72              response.ContentType  =   " Application/octet-stream " ;
73          }
74 
75           private   void  SetResponseHead(HttpResponseBase response)
76          {
77              response.HeaderEncoding  =  Encoding.UTF8;
78              response.AddHeader( " Content-Disposition " " attachment;filename= "   +
79                  HttpUtility.UrlEncode( this .FileName, Encoding.UTF8).Replace( " + " "   " ));
80          }
81      }

 

主要的要点如下:

 (1) 第28行       if (context == null || (!File.Exists(this.FileFullPath)))    ---->主要为了避免异常发生而进行的防御性编码。

 (2) 第34行       SetFileName(fileInfo);    ---->如果文件名FileName不存在,则获取文件完整路径的具体文件名称。此处主要是设置下载对话框的文件名称,可以解决Silverlight中SaveFileDialog不能设置文件名称的缺陷。具体的设置文件名称到下载对话框为如下的77-79的代码:

        77           response.HeaderEncoding = Encoding.UTF8;
        78           response.AddHeader("Content-Disposition""attachment;filename=" +
        79                 HttpUtility.UrlEncode(this.FileName, Encoding.UTF8).Replace("+"" "));
(3)第72行         response.ContentType = "Application/octet-stream";    ----->主要解决文件下载类型的问题。

 

以上的这些方法重构后代码度量的可维护性指数为81,,基本上达到代码质量的要求了。

 

2、Mvc Download Action的实现

 1       public   class  FileController : Controller
 2      {
 3           public  ActionResult Download( string  filePath)
 4          {
 5               if  ( ! System.IO.File.Exists(filePath))
 6              { 
 7                  return  RedirectToAction( " FileNotFound " " Error " );
 8              }
 9 
10               return   new  FileDownloadResult(filePath);
11          }
12      }


 对于文件下载,调用的方式很简单,实例化FileDownloadResult即可。

 5             if (!System.IO.File.Exists(filePath))
 6             { 
 7                return RedirectToAction("FileNotFound""Error");
 8             }
主要是对传入的文件地址的防御性的编码,对传入的空值、NULL值以及不存在的文件进行验证(后续的单元测试可以查看相关测试)。

 

3、Silverlight中访问Mvc的Download Action

1         void  btnDownload_Click( object  sender, RoutedEventArgs e)
2          {
3               string  url  =   @" http://localhost:2429/File/Download?FilePath= "   +   " E:\\图片操作源码.txt " ;
4              HtmlPage.Window.Navigate( new  Uri(url));
5          }

 

 4、关于Silverlight中SaveFileDialog下载,服务端获取文件字节数组的代码如下(以下的代码为本人笔记本上的代码,比项目中自己以前写的那个更简洁):

 1     public   class  FileHelper
 2      {
 3           public   static   byte [] LoadFileBytes( string  fileFullName)
 4          {
 5               if  ( ! File.Exists(fileFullName))
 6              {
 7                   return   new   byte [ 0 ];
 8              }
 9 
10               try
11              {
12                   return  ConvertToBytes(fileFullName);
13              }
14               catch
15              {
16                   return   new   byte [ 0 ];
17              }
18          }
19 
20           private   static   byte [] ConvertToBytes( string  fileFullName)
21          {
22               using  (FileStream fileStream  =  File.OpenRead(fileFullName))
23              {
24                   return  CopyToArray(fileStream);
25              }
26          }
27 
28           private   static   byte [] CopyToArray(FileStream fileStream)
29          {
30               using  (MemoryStream memoryStream  =   new  MemoryStream())
31              {
32                  fileStream.CopyTo(memoryStream, ( int )fileStream.Length);
33                   return  memoryStream.ToArray();
34              }
35          }
36      }

 

 (三)单元测试

 

1、FileDownloadResult的单元测试代码:

 1      [TestClass()]
 2       public   class  FileDownloadResultTest
 3      {
 4           ///   <summary>
 5           ///  ExecuteResult 的测试。
 6           /// </summary>
 7          [TestMethod()]
 8           public   void  ExecuteResultTest()
 9          {
10               string  fileFullPath  =   @" E:\TempTestFile.txt " ;
11              CreateFile(fileFullPath);
12 
13              FileController controller  =   new  FileController();
14              ExecuteResult(fileFullPath, controller);
15 
16              HttpResponseBase response = controller.ControllerContext.HttpContext.Response;
17 
18              Assert.IsNotNull(controller.ControllerContext);
19              Assert.IsNotNull(response);
20 
21              Assert.IsTrue(response.Buffer);
22              Assert.AreEqual( 0 , response.Expires);
23              Assert.IsTrue( string .Equals(response.ContentType,  " Application/octet-stream " ));
24              Assert.AreEqual(response.ContentEncoding, Encoding.UTF8);
25              Assert.AreEqual(response.HeaderEncoding, Encoding.UTF8);
26 
27              DeleteFile(fileFullPath);
28          }
29 
30           ///   <summary>
31           ///  当参数异常时,ExecuteResult 的测试。
32           /// </summary>
33          [TestMethod()]
34           public   void  ExecuteResultWithAbnormalArgTest() 
35          {
36               string  fileFullPath  =   @" E:\TempTestNonExsitingFile.txt " ;
37              FileController controller  =   new  FileController();
38              ExecuteResult(fileFullPath, controller);
39 
40              HttpResponseBase response  =  controller.ControllerContext.HttpContext.Response;
41 
42              Assert.IsNotNull(controller.ControllerContext);
43              Assert.IsNotNull(response);
44          }
45 
46           ///   <summary>
47           ///  当参数为null或者empty时,ExecuteResult 的测试。
48           /// </summary>
49          [TestMethod()]
50           public   void  ExecuteResultWithNullOrEmptyArgTest() 
51          {
52              FileController controller  =   new  FileController();
53              ExecuteResult( null , controller);
54 
55              HttpResponseBase response  =  controller.ControllerContext.HttpContext.Response;
56 
57              Assert.IsNotNull(controller.ControllerContext);
58              Assert.IsNotNull(response);
59          }
60 
61           private   void  DeleteFile( string  fileFullPath)
62          {
63               if  (File.Exists(fileFullPath))
64              {
65                  File.Delete(fileFullPath);
66              }
67          }
68 
69           private   void  CreateFile( string  fileFullPath)
70          {
71               if  ( ! File.Exists(fileFullPath))
72              {
73                   using  (FileStream fileStream  =  File.Create(fileFullPath))
74                  {
75                  }
76              }
77          }
78 
79           private   static   void  ExecuteResult( string  fileFullPath, FileController controller)
80          {
81              FileDownloadResult target  =   new  FileDownloadResult(fileFullPath);
82              MvcContextHelper.SetControllerContext(controller);
83              target.ExecuteResult(controller.ControllerContext);
84          }      
85      }

 

 对于69-77行的代码,其中73-75没有做任何操作,仅仅是释放掉资源而已,避免异常的发生:

69         private void CreateFile(string fileFullPath)
70         {
71             if (!File.Exists(fileFullPath))
72             {
73                 using (FileStream fileStream = File.Create(fileFullPath))
74                 {
75                 }
76             }
77         }

 

以上的测试涉及到ControllerContext 的模拟,因此这里采用Moq来进行测试,相关代码如下:

 1       public   class  MvcContextHelper
 2      {
 3           public   static  HttpContextBase SetHttpContext()
 4          {
 5              var context  =   new  Mock < HttpContextBase > ();
 6              var request  =   new  Mock < HttpRequestBase > ();
 7              var response  =   new  Mock < HttpResponseBase > ();
 8              var session  =   new  Mock < HttpSessionStateBase > ();
 9              var server  =   new  Mock < HttpServerUtilityBase > ();
10 
11              request.Setup(r  =>  r.Form).Returns( new  NameValueCollection());
12              request.Setup(r  =>  r.QueryString).Returns( new  NameValueCollection());
13              context.Setup(ctx  =>  ctx.Request).Returns(request.Object);
14              context.Setup(ctx  =>  ctx.Response).Returns(response.Object);
15              context.Setup(ctx  =>  ctx.Response.Headers).Returns( new  NameValueCollection());
16              context.Setup(ctx  =>  ctx.Session).Returns(session.Object);
17              context.Setup(ctx  =>  ctx.Server).Returns(server.Object);
18              context.Setup(ctx  =>  ctx.Response.Output).Returns( new  StringWriter());
19 
20               return  context.Object;
21          }
22 
23           public   static   void  SetControllerContext(Controller controller)
24          {
25              var httpContext  =  SetHttpContext();
26              ControllerContext context  =   new  ControllerContext(
27                   new  RequestContext(httpContext,  new  RouteData()),
28                  controller);
29              controller.ControllerContext  =  context;
30          }
31      }

 

2、FileController的单元测试

 1      [TestClass()]
 2       public   class  FileControllerTest
 3      {
 4           ///   <summary>
 5           ///  Download 的测试。
 6           /// </summary>
 7          [TestMethod()]
 8           public   void  DownloadTest() 
 9          {
10              FileController controller  =   new  FileController();
11               string  fileFullPath  =   @" E:\TempTestFile.txt " ;
12              CreateFile(fileFullPath);
13 
14              ActionResult actionResult   =  controller.Download(fileFullPath);
15 
16              FileDownloadResult result  =  actionResult  as  FileDownloadResult;
17              Assert.IsNotNull(result);
18              Assert.IsTrue( string .Equals(fileFullPath,result.FileFullPath));
19 
20              DeleteFile(fileFullPath);
21          }
22 
23           ///   <summary>
24           ///  当参数异常,Download 的测试。
25           /// </summary>
26          [TestMethod()]
27           public   void  DownloadWithAbnormalArgTest() 
28          {
29              FileController controller  =   new  FileController();
30               string  fileFullPath  =   @" E:\TempTestNonExsitingFile.txt " ;
31              ActionResult actionResult  =  controller.Download(fileFullPath);
32 
33              AssertAbnormalResult(actionResult);
34          }
35 
36           ///   <summary>
37           ///  当参数为null或者empty时,Download 的测试。
38           /// </summary>
39          [TestMethod()]
40           public   void  DownloadWithNullOrEmptyArgTest() 
41          {
42              FileController controller  =   new  FileController();
43              ActionResult actionResult  =  controller.Download( string .Empty);
44              AssertAbnormalResult(actionResult);
45 
46              actionResult  =  controller.Download( null );
47              AssertAbnormalResult(actionResult);
48          }
49 
50           private   static   void  AssertAbnormalResult(ActionResult actionResult)
51          {
52              RedirectToRouteResult result  =  actionResult  as  RedirectToRouteResult;
53              Assert.IsNotNull(result);
54              Assert.IsTrue( string .Equals( " FileNotFound " , result.RouteValues[ " action " ]));
55              Assert.IsTrue( string .Equals( " Error " , result.RouteValues[ " controller " ]));
56          }
57 
59           private   void  CreateFile( string  fileFullPath)
60          {
61               if  ( ! File.Exists(fileFullPath))
62              {
63                   using  (FileStream fileStream  =  File.Create(fileFullPath))
64                  {
65                  }
66              }
67          }
68 
69           private   void  DeleteFile( string  fileFullPath)
70          {
71               if  (File.Exists(fileFullPath))
72              {
73                  File.Delete(fileFullPath);
74              }
75          }
76      }

 

(四)效果图

 

在Silverlight中点击下载显示的效果图如下:

 

(五)总结

 

上述的代码以及随笔,本人从中午吃完饭一直整到现在,XX,45行代码花了哥这么久(还有一个FileHelperTest的内容没写在随笔了,再写的话,估计全部是代码了!在源代码中有相关测试代码)。时间过得真快,还木有吃饭,自己得马山煮饭吃了,,明天又得上班了.....

 

源代码下载:  /Files/jasenkin/MVC/Jasen.MvcDownload.Web.rar

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值