• 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 { getset; }
     5          public string Filter { getset; }
     6          public int FilterIndex { getset; }
     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

  • 相关阅读:
    OSG学习笔记0——解决OSG读obj模型问题[转]
    Shell脚本——make命令和Makefile文件【转】
    Makefile教程(绝对经典,所有问题看这一篇足够了)【转】
    cmake 手册详解【转】
    Grafana密码恢复
    nsenter的用法
    ssh “permissions are too open” error
    CoreDns配置以及外部dns使用
    Prometheus监控系统-Alertmanager Silences静默配置
    linux下解决bash: syntax error near unexpected token `(' 的错误
  • 原文地址:https://www.cnblogs.com/jasenkin/p/silverlight_mvc_action_file_download.html
Copyright © 2020-2023  润新知