IFileSystem Dependency Inversion Part 2
In my last post in this IFileSystem series, I described the problem I'm working on of removing a dependency on the System.IO Windows file system in my ASP.NET application. A bit of research on this subject revealed some help on making file uploads testable by ScottHa, but his technique still makes use of the SaveAs() method and ultimately ties the solution to the server file system. A similar post on creating Unit Test Friendly File Uploads was more helpful, in that it provided some helper methods for saving files from HttpPostFile without using its built-in SaveAs() method.
Since my ultimate goal is to be able to swap between Amazon S3 and Windows file systems (and likely Azure as well), I decided to create a spike solution that allows me to accomplish both tasks using the same API. To this end, I created a simple ASP.NET web form like so:
Default.aspx
1: <%@ Page Language="C#" AutoEventWireup="true"
2: CodeBehind="Default.aspx.cs" Inherits="UploadTestCS._Default" %> 3: 4: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
5: "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
6: 7: <html xmlns="http://www.w3.org/1999/xhtml" >
8: <head runat="server">
9: <title>Untitled Page</title>
10: </head>
11: <body>
12: <form id="form1" runat="server">
13: <div>
14: <asp:FileUpload ID="FileUpload1" runat="server" />
15: <asp:Button ID="Button1" runat="server"
16: Text="Button" onclick="Button1_Click" />
17: </div>
18: <asp:Label ID="MessageLabel" runat="server" EnableViewState="False"></asp:Label>
19: </form>
20: </body>
21: </html>
Default.aspx.cs
1: using System;
2: using System.IO;
3: using Affirma.ThreeSharp;
4: using Affirma.ThreeSharp.Model;
5: using Affirma.ThreeSharp.Query;
6: 7: namespace UploadTestCS
8: {9: public partial class _Default : System.Web.UI.Page
10: {11: private readonly string uploadFolder;
12: private static readonly string awsAccessKeyId = "KEY";
13: private static readonly string awsSecretAccessKey = "SECRETKEY";
14: private static readonly string uploadBucket = "TESTBUCKET";
15: 16: public _Default()
17: {18: uploadFolder = Server.MapPath("upload/");
19: }20: protected void Button1_Click(object sender, EventArgs e)
21: {22: if(FileUpload1.FileBytes.Length > 0)
23: {24: MessageLabel.Text = String.Format("File uploaded: {0}, {1}",
25: FileUpload1.FileName, FileUpload1.FileBytes.Length); 26: 27: string fileName = FileUpload1.FileName;
28: // System.IO Byte Array
29: WriteBinaryFile(FileUpload1.FileBytes, fileName);30: // S3 Byte Array
31: WriteBinaryFileS3(FileUpload1.FileBytes, fileName); 32: } 33: } 34: 35: public void WriteBinaryFileS3(byte[] bytes, string savingPath)
36: {37: ThreeSharpConfig config = new ThreeSharpConfig();
38: config.AwsAccessKeyID = awsAccessKeyId; 39: config.AwsSecretAccessKey = awsSecretAccessKey;40: string testBucketName = "sometestfolder";
41: 42: IThreeSharp service = new ThreeSharpQuery(config);
43: using (ObjectAddRequest request =
44: new ObjectAddRequest(testBucketName, savingPath))
45: { 46: request.LoadStreamWithBytes(bytes);47: using (ObjectAddResponse response = service.ObjectAdd(request))
48: { } 49: } 50: } 51: 52: public void WriteBinaryFile(byte[] bytes, string savingPath)
53: {54: using (var writingStream =
55: new FileStream(savingPath, FileMode.OpenOrCreate))
56: { 57: writingStream.Write(bytes, 0, bytes.Length); 58: writingStream.Flush(); 59: writingStream.Close(); 60: } 61: } 62: } 63: }Now I know what my signature needs to be for IFileSystem's WriteBinaryFile() method, and I have implementations that work for both S3 and Windows. I also know the implementation-specific properties/configuration each implementation will need, specifically the upload folder for System.IO and the S3 keys and bucket name for S3.
Note that I'm making use of the Affirma ThreeSharp Amazon S3 library in the above code, available for free on CodePlex.
Next Steps
The next step is to throw away my spike solution and go back and TDD my IFileSystem to provide direct support for WriteBinaryFile. Then I need to get my ugly GetImageOrFlashData() method under test, apply some Dependency Injection (via the Strategy pattern) to it, and hopefully I'll then be able to flip a switch in my application and have the uploaded file go directly to Amazon S3 (without ever hitting my server's disk) or to my server's disk.
(Note that later we may decide it's wiser to always upload to server disk, and then persist to S3/Azure/Cloud storage using a separate worker process. For now that's a premature optimization and we're just going to work on getting the simple, synchronous case to work first).
6 Comments
Pages tagged "false" said
Pingback from Pages tagged "false"
IFileSystem Dependency Inversion Part 2 said
You've been kicked (a good thing) - Trackback from DotNetKicks.com
IFileSystem Dependency Inversion Part 2 | Blog posts trends said
Pingback from IFileSystem Dependency Inversion Part 2 | Blog posts trends
IFileSystem Dependency Inversion Part 3 : Steve Smith's Blog said
Pingback from IFileSystem Dependency Inversion Part 3 : Steve Smith's Blog
IFileSystem Dependency Inversion Part 4 said
Still working on cleaning up some legacy ASP.NET code. Here's where we are: Part 1 : Define problem and demonstrate IFileSystem basic version Part 2: Spike solution to support saving files in IFileSystem that works in both Amazon S3 and the Windows file
ASP.NET MVC Archived Buzz, Page 1 said
Pingback from ASP.NET MVC Archived Buzz, Page 1