统一Windows Azure和一般web应用之间的文件操作代码(转+译)
2010-12-01 10:59
288 查看
[b]公告
:本博客为微软云计算中文博客
的镜像博客。
部分文章因为博客兼容性问题
,会影响阅读体验
。如遇此情况,请访问
原博客
。
[/b]
最近我全身心的投入到我们第一个基于云的平台-XLR8- (研发代码: Xalent)的工作中。一周前,我们的首席架构师,
Ray,让我试着将该平台部署至Windows Azure上。我们需要对平台做一些修改,其中之一便是Windows
Azure不能使用本地文件系统来存储任何最终用户上传的文件。原因有2个:
所有web role 项目下的文件会被当做一个程序包。这意味着当我们部署web role时,Windows
Azure会删除原有的文件夹和文件,然后展开新的程序包,并进行初始化工作。因此所有用户上传的文件此时都会被删除。
在某些情况下,Windows Azure 平台会将您的应用从一个虚拟机搬移至另外一个。
我们无法确保应用根路径的一致性。所以对于useServer.Mappath()
,它会返回不同的结果。
因此,当应用部署至Windows Azure时,对于上传的文件最好将其存储在Windows Azure Blob storage 中。
难题和目标
当我们将一般web应用搬移至Windows
Azure时,我们需要修改所有上传文件相关的代码,甚至是显式图片的代码。我面临的问题是web应用应能同时满足Windows Azure
和一般的部署环境的情况。这意味着当其部署至Windows Azure
或一般服务器时,我们不应该在业务逻辑层和UI层去修改文件操作代码。我们要确保代码在2种部署情况下都能正常运行,我们能做的修改仅仅是一些部署配置。
一个解决办法是使用Cloud Drive 特性。那样的话我们可以在Blob挂载一个VHD
文件当做本地硬盘来使用。这样基本无需更改IO操作和代码。但是将文件存储于Blob内会有其他一些优势,例如可以通过URL直接访问文件。
所以难题便是,我需要一个设计模式来负责文件的操作,且无论是一般文件系统还是Blob
storage。本文我会介绍一下我是如何处理这些问题的,希望对读者在未来开发Windows Azure 和一般web应用时有所帮助。
简单的架构和实现
整个架构非常简单。为了使得web应用依赖于抽象的文件操作,我创建了一个接口来隔离一般文件系统和Blob storage实现上的差别。
在 IFileSystemAgent
接口中,我定义了基本的文件操作方法,例如Save
,Load
,Delete
和Exist
s
。GetResourceUrl
方法用于访问文件URL,这对于在网页上显示图片来说非常有用。它会基于当前部署的系统返回适当的URL。
public
interface
IFileSystemAgent
{
void
Save(Stream fileStream, string
filename, bool
overwrite);
void
Save(byte
[] bytes, string
filename, bool
overwrite);
byte
[]
Load(string
filename);
bool
Exists(string
filename);
void
Delete(string
filename);
string
GetResourceUrl(string
filename);
}
在IFileSystemAgent
接口之上我实现了2个类,一个用于一般的Windows系统的文件操作,一个用于Blob
storage。
这2个实现类的区别不仅在于文件操作,还有根路径问题。在web应用中,对于一般的文件系统,我们使用Server.MapPath()
来将虚拟路径转换为物理路径,以便保存和读取文件。但是在Blob
storage 中,我们需要获取Blob storage 账户信息,向该账户的端点传输字节或者数据流,这和一般文件系统是非常不同的。
当我们需要在一个网页上显示或链接文件时,在windows文件系统中,我们只需使用相对路径,举例来说:
"/upload/images/beijing-hotel-img1_50x50.jpg"。但是在Blob storage中,一般路径如下形式:
"http://xlr8.blob.core.windows.net/default/beijing-hotel-img1_50x50.jpg".
因此,当保存或链接文件时,
IFileSystemAgent
只接受文件名和相对路径,具体实现类会决定如何以及在哪里存储文件。
我将 HttpServerUtilityBase
以及一个名为Root的参数传入WindowsFileSystemAgent
的构造函数中。文件必须存储在Server.MapPath("/"
+
Root)
目录下。在 AzureBlobFileSystemAgent
构造函数中,我同样传入 CloudStorageAccount
以及 ContainerName
,这样文件便会存储在相应账户的指定容器内。
如下是2个实现类的具体实现。
public
class
WindowsFileSystemAgent
: IFileSystemAgent
{
private
HttpServerUtilityBase _server;
private
string
_root;
public
HttpServerUtilityBase Server
{
get
{
return
_server;
}
set
{
_server = value
;
}
}
public
string
Root
{
get
{
return
_root;
}
set
{
_root = value
;
}
}
public
WindowsFileSystemAgent()
: this
(null
, string
.Empty)
{
}
public
WindowsFileSystemAgent(HttpServerUtilityBase server, string
root)
{
_server = server;
_root = root;
}
private
string
GetServerSideFullname(string
filename)
{
return
Path.Combine(_server.MapPath("/"
+ _root),
filename);
}
#region
IFileSystemAgent
Members
public
void
Save(Stream fileStream, string
filename, bool
overwrite)
{
byte
[]
bytes = new
byte
[fileStream.Length];
fileStream.Read(bytes, 0, (int
)fileStream.Length);
Save(bytes, filename,
overwrite);
}
public
void
Save(byte
[] bytes, string
filename, bool
overwrite)
{
filename =
GetServerSideFullname(filename);
var
directory = Path.GetDirectoryName(filename);
if
(!Exists(directory))
{
Directory.CreateDirectory(directory);
}
if
(Exists(filename))
{
if
(overwrite)
{
Delete(filename);
}
else
{
throw
new
ApplicationException
(string
.Format("Existed
file {0} please select another name or set the overwrite =
true."
));
}
}
using
(var
stream =
File.Create(filename))
{
stream.Write(bytes, 0,
bytes.Length);
}
}
public
byte
[] Load(string
filename)
{
filename =
GetServerSideFullname(filename);
byte
[]
bytes;
using
(var
stream =
File.OpenRead(filename))
{
bytes = new
byte
[stream.Length];
stream.Read(bytes, 0,
bytes.Length);
}
return
bytes;
}
public
bool
Exists(string
filename)
{
filename =
GetServerSideFullname(filename);
if
(File.Exists(filename))
{
return
true
;
}
else
{
return
Directory.Exists(filename);
}
}
public
void
Delete(string
filename)
{
filename =
GetServerSideFullname(filename);
if
(File.Exists(filename))
{
File.Delete(filename);
}
}
public
string
GetResourceUrl(string
filename)
{
return
"/"
+ _root + "/"
+ filename;
}
#endregion
}
public
class
AzureBlobFileSystemAgent
: IFileSystemAgent
{
private
static
string
CST_DEFAULTCONTAINERNAME = "default"
;
private
static
string
CST_DEFAULTACCOUNTSETTING = "DataConnectionString"
;
private
string
_containerName { get
; set
;
}
private
CloudStorageAccount _storageAccount { get
;
set
; }
private
CloudBlobContainer _container;
public
AzureBlobFileSystemAgent()
: this
(CST_DEFAULTCONTAINERNAME,
CST_DEFAULTACCOUNTSETTING)
{
}
public
AzureBlobFileSystemAgent(string
containerName,
string
storageAccountConnectionString)
: this
(containerName,
CloudStorageAccount.FromConfigurationSetting(storageAccountConnectionString))
{
}
public
AzureBlobFileSystemAgent(string
containerName,
CloudStorageAccount storageAccount)
{
_containerName =
containerName;
_storageAccount =
storageAccount;
// create
the blob container for account logos if not exist
CloudBlobClient blobStorage =
_storageAccount.CreateCloudBlobClient();
_container =
blobStorage.GetContainerReference(_containerName);
_container.CreateIfNotExist();
// configure
blob container for public access
BlobContainerPermissions permissions =
_container.GetPermissions();
permissions.PublicAccess =
BlobContainerPublicAccessType.Container;
_container.SetPermissions(permissions);
}
#region
IFileSystemAgent
Members
public
void
Save(Stream fileStream, string
filename, bool
overwrite)
{
var
bytes = new
byte
[fileStream.Length];
fileStream.Read(bytes, 0,
bytes.Length);
Save(bytes, filename,
overwrite);
}
public
void
Save(byte
[] bytes, string
filename, bool
overwrite)
{
filename =
TranslateFileName(filename);
CloudBlockBlob blob =
_container.GetBlockBlobReference(filename);
if
(Exists(filename))
{
if
(overwrite)
{
Delete(filename);
}
else
{
throw
new
ApplicationException
(string
.Format("Existed
file {0} please select another name or set the overwrite =
true."
));
}
}
blob.UploadByteArray(bytes, new
BlobRequestOptions() { Timeout = TimeSpan
.FromMinutes(3) });
}
public
byte
[] Load(string
filename)
{
filename =
TranslateFileName(filename);
CloudBlockBlob blob =
_container.GetBlockBlobReference(filename);
return
blob.DownloadByteArray();
}
public
bool
Exists(string
filename)
{
filename =
TranslateFileName(filename);
CloudBlockBlob blob =
_container.GetBlockBlobReference(filename);
try
{
blob.FetchAttributes();
return
true
;
}
catch
(StorageClientException ex)
{
if
(ex.ErrorCode == StorageErrorCode.ResourceNotFound)
{
return
false
;
}
else
{
throw
;
}
}
}
public
void
Delete(string
filename)
{
filename =
TranslateFileName(filename);
CloudBlockBlob blob =
_container.GetBlockBlobReference(filename);
blob.DeleteIfExists();
}
private
string
TranslateFileName(string
filename)
{
return
filename.Replace('/'
, '~'
).Replace('//'
, '`'
);
}
public
string
GetResourceUrl(string
filename)
{
// when
using the local storage simulator the blob enpoint without the end
'/'
// but when
using the azure it has '/' at the end of it
// so here i
have to use Path.Combine to construct the path and then replace the '/' back to
'/'
var
url = Path.Combine(_storageAccount.BlobEndpoint.ToString(), _containerName,
TranslateFileName(filename));
return
url.Replace('//'
, '/'
);
}
#endregion
}
在ASP.NET MVC中保存和显示图片
让我以一个 ASP.NET MVC
应用来展示如何使用上述实现。首先我们需要一个辅助类来根据配置初始化相应的IFileSystemAgent
实例。我创建了一个非常简单的工厂类来返回相应的实例(根据在web.config文件中相应的值)。在实际项目中,我们最好使用一些 IoC
容器,例如Unity
。
public
static
class
FileSystemAgentFactory
{
public
static
IFileSystemAgent
Resolve()
{
var
config = System.Configuration.ConfigurationManager.AppSettings["filesystem-agent"
];
switch
(config.ToLower())
{
case
"windows"
:
if
(HttpContext.Current != null
&& HttpContext.Current.Server != null
)
{
return
new
WindowsFileSystemAgent(new
HttpServerUtilityWrapper(HttpContext.Current.Server), "Upload"
);
}
else
{
throw
new
NotSupportedException
("HttpContext ot its Server property is null. The
WindowsFileSystemAgent must be used under the web
application."
);
}
case
"blob"
:
return
new
AzureBlobFileSystemAgent();
default
:
return
null
;
}
}
}
然后,在处理文件上传的controller中,我们可以使用该工厂类来初始化适当的IFileSystemAgent
实例。如果要保存文件,只需要调用其 Save
方法,而不管实际使用的是哪个实现类。如果我们需要在一般的服务器和Windows
Azure之间进行搬移时,我们只需要更改web.config文件。
[HttpPost]
public
ActionResult UploadFile(string
filekey)
{
if
(Request.Files != null
&&
Request.Files.Count > 0)
{
var file =
Request.Files[0];
var filename = "Avatar/"
+ Guid.NewGuid().ToString() +
Path.GetExtension(file.FileName);
var filesys =
FileSystemAgentFactory.Resolve();
filesys.Save(file.InputStream,
filename, true
);
Repository.Images.Add(filename);
}
return
RedirectToAction("Index"
);
}
类似的,当我们在网页上需要显示或者链接文件时,我们也无需关注其具体存储在哪里。为此,我们需要为HtmlHelper
创建一个拓展方法。有了该辅助方法,当我们需要显示或链接文件时,我们只需要使用IFileSystemAgent
的GetResourceUrl
方法,它便会返回适当的URL。
public
static
class
HelpHelpers
{
public
static
MvcHtmlString Image(this
HtmlHelper helper, string
filename)
{
return
Image(helper,
FileSystemAgentFactory.Resolve(), filename);
}
public
static
MvcHtmlString Image(this
HtmlHelper helper, IFileSystemAgent agent, string
filename)
{
return
Image(helper, agent, filename,
VirtualPathUtility.GetFileName("/"
+
filename));
}
public
static
MvcHtmlString Image(this
HtmlHelper helper, IFileSystemAgent agent, string
filename, string
{
var html = string
.Format("<img
src=/"{0}/" alt=/"{1}/" />"
, agent.GetResourceUrl(filename),
alt);
return
MvcHtmlString.Create(html);
}
}
总结
本文我介绍了如何统一在Windows
Azure和一般web应用之间的文件操作代码。相信还可以做进一步的改进和优化。其中之一便是我们可以将HttpServerUtilityBase
以及
CloudStorageAccount
抽离出一个接口来,例如, IRootProvider
,这样会方便进行依赖注入,也可以进行完全的单元测试。
对于Windows Azure应用,还会有其他的部分可以改进。例如,我们应该将经常会更改的配置数据放入ServiceConfiguration.cscfg
,而不是web.config。这要求我们构建一个 provider 来读取配置信息,我会在后面的文章中进行讲解。
从 这里
下载本文的展示代码。
本文翻译自:http://geekswithblogs.net/shaunxu/archive/2010/08/26/unify-your-file-operation-code-between-azure-and-normal-web.aspx
:本博客为微软云计算中文博客
的镜像博客。
部分文章因为博客兼容性问题
,会影响阅读体验
。如遇此情况,请访问
原博客
。
[/b]
Ray,让我试着将该平台部署至Windows Azure上。我们需要对平台做一些修改,其中之一便是Windows
Azure不能使用本地文件系统来存储任何最终用户上传的文件。原因有2个:
所有web role 项目下的文件会被当做一个程序包。这意味着当我们部署web role时,Windows
Azure会删除原有的文件夹和文件,然后展开新的程序包,并进行初始化工作。因此所有用户上传的文件此时都会被删除。
在某些情况下,Windows Azure 平台会将您的应用从一个虚拟机搬移至另外一个。
我们无法确保应用根路径的一致性。所以对于useServer.Mappath()
,它会返回不同的结果。
因此,当应用部署至Windows Azure时,对于上传的文件最好将其存储在Windows Azure Blob storage 中。
难题和目标
当我们将一般web应用搬移至Windows
Azure时,我们需要修改所有上传文件相关的代码,甚至是显式图片的代码。我面临的问题是web应用应能同时满足Windows Azure
和一般的部署环境的情况。这意味着当其部署至Windows Azure
或一般服务器时,我们不应该在业务逻辑层和UI层去修改文件操作代码。我们要确保代码在2种部署情况下都能正常运行,我们能做的修改仅仅是一些部署配置。
一个解决办法是使用Cloud Drive 特性。那样的话我们可以在Blob挂载一个VHD
文件当做本地硬盘来使用。这样基本无需更改IO操作和代码。但是将文件存储于Blob内会有其他一些优势,例如可以通过URL直接访问文件。
所以难题便是,我需要一个设计模式来负责文件的操作,且无论是一般文件系统还是Blob
storage。本文我会介绍一下我是如何处理这些问题的,希望对读者在未来开发Windows Azure 和一般web应用时有所帮助。
简单的架构和实现
整个架构非常简单。为了使得web应用依赖于抽象的文件操作,我创建了一个接口来隔离一般文件系统和Blob storage实现上的差别。
在 IFileSystemAgent
接口中,我定义了基本的文件操作方法,例如Save
,Load
,Delete
和Exist
s
。GetResourceUrl
方法用于访问文件URL,这对于在网页上显示图片来说非常有用。它会基于当前部署的系统返回适当的URL。
public
interface
IFileSystemAgent
{
void
Save(Stream fileStream, string
filename, bool
overwrite);
void
Save(byte
[] bytes, string
filename, bool
overwrite);
byte
[]
Load(string
filename);
bool
Exists(string
filename);
void
Delete(string
filename);
string
GetResourceUrl(string
filename);
}
在IFileSystemAgent
接口之上我实现了2个类,一个用于一般的Windows系统的文件操作,一个用于Blob
storage。
这2个实现类的区别不仅在于文件操作,还有根路径问题。在web应用中,对于一般的文件系统,我们使用Server.MapPath()
来将虚拟路径转换为物理路径,以便保存和读取文件。但是在Blob
storage 中,我们需要获取Blob storage 账户信息,向该账户的端点传输字节或者数据流,这和一般文件系统是非常不同的。
当我们需要在一个网页上显示或链接文件时,在windows文件系统中,我们只需使用相对路径,举例来说:
"/upload/images/beijing-hotel-img1_50x50.jpg"。但是在Blob storage中,一般路径如下形式:
"http://xlr8.blob.core.windows.net/default/beijing-hotel-img1_50x50.jpg".
因此,当保存或链接文件时,
IFileSystemAgent
只接受文件名和相对路径,具体实现类会决定如何以及在哪里存储文件。
我将 HttpServerUtilityBase
以及一个名为Root的参数传入WindowsFileSystemAgent
的构造函数中。文件必须存储在Server.MapPath("/"
+
Root)
目录下。在 AzureBlobFileSystemAgent
构造函数中,我同样传入 CloudStorageAccount
以及 ContainerName
,这样文件便会存储在相应账户的指定容器内。
如下是2个实现类的具体实现。
public
class
WindowsFileSystemAgent
: IFileSystemAgent
{
private
HttpServerUtilityBase _server;
private
string
_root;
public
HttpServerUtilityBase Server
{
get
{
return
_server;
}
set
{
_server = value
;
}
}
public
string
Root
{
get
{
return
_root;
}
set
{
_root = value
;
}
}
public
WindowsFileSystemAgent()
: this
(null
, string
.Empty)
{
}
public
WindowsFileSystemAgent(HttpServerUtilityBase server, string
root)
{
_server = server;
_root = root;
}
private
string
GetServerSideFullname(string
filename)
{
return
Path.Combine(_server.MapPath("/"
+ _root),
filename);
}
#region
IFileSystemAgent
Members
public
void
Save(Stream fileStream, string
filename, bool
overwrite)
{
byte
[]
bytes = new
byte
[fileStream.Length];
fileStream.Read(bytes, 0, (int
)fileStream.Length);
Save(bytes, filename,
overwrite);
}
public
void
Save(byte
[] bytes, string
filename, bool
overwrite)
{
filename =
GetServerSideFullname(filename);
var
directory = Path.GetDirectoryName(filename);
if
(!Exists(directory))
{
Directory.CreateDirectory(directory);
}
if
(Exists(filename))
{
if
(overwrite)
{
Delete(filename);
}
else
{
throw
new
ApplicationException
(string
.Format("Existed
file {0} please select another name or set the overwrite =
true."
));
}
}
using
(var
stream =
File.Create(filename))
{
stream.Write(bytes, 0,
bytes.Length);
}
}
public
byte
[] Load(string
filename)
{
filename =
GetServerSideFullname(filename);
byte
[]
bytes;
using
(var
stream =
File.OpenRead(filename))
{
bytes = new
byte
[stream.Length];
stream.Read(bytes, 0,
bytes.Length);
}
return
bytes;
}
public
bool
Exists(string
filename)
{
filename =
GetServerSideFullname(filename);
if
(File.Exists(filename))
{
return
true
;
}
else
{
return
Directory.Exists(filename);
}
}
public
void
Delete(string
filename)
{
filename =
GetServerSideFullname(filename);
if
(File.Exists(filename))
{
File.Delete(filename);
}
}
public
string
GetResourceUrl(string
filename)
{
return
"/"
+ _root + "/"
+ filename;
}
#endregion
}
public
class
AzureBlobFileSystemAgent
: IFileSystemAgent
{
private
static
string
CST_DEFAULTCONTAINERNAME = "default"
;
private
static
string
CST_DEFAULTACCOUNTSETTING = "DataConnectionString"
;
private
string
_containerName { get
; set
;
}
private
CloudStorageAccount _storageAccount { get
;
set
; }
private
CloudBlobContainer _container;
public
AzureBlobFileSystemAgent()
: this
(CST_DEFAULTCONTAINERNAME,
CST_DEFAULTACCOUNTSETTING)
{
}
public
AzureBlobFileSystemAgent(string
containerName,
string
storageAccountConnectionString)
: this
(containerName,
CloudStorageAccount.FromConfigurationSetting(storageAccountConnectionString))
{
}
public
AzureBlobFileSystemAgent(string
containerName,
CloudStorageAccount storageAccount)
{
_containerName =
containerName;
_storageAccount =
storageAccount;
// create
the blob container for account logos if not exist
CloudBlobClient blobStorage =
_storageAccount.CreateCloudBlobClient();
_container =
blobStorage.GetContainerReference(_containerName);
_container.CreateIfNotExist();
// configure
blob container for public access
BlobContainerPermissions permissions =
_container.GetPermissions();
permissions.PublicAccess =
BlobContainerPublicAccessType.Container;
_container.SetPermissions(permissions);
}
#region
IFileSystemAgent
Members
public
void
Save(Stream fileStream, string
filename, bool
overwrite)
{
var
bytes = new
byte
[fileStream.Length];
fileStream.Read(bytes, 0,
bytes.Length);
Save(bytes, filename,
overwrite);
}
public
void
Save(byte
[] bytes, string
filename, bool
overwrite)
{
filename =
TranslateFileName(filename);
CloudBlockBlob blob =
_container.GetBlockBlobReference(filename);
if
(Exists(filename))
{
if
(overwrite)
{
Delete(filename);
}
else
{
throw
new
ApplicationException
(string
.Format("Existed
file {0} please select another name or set the overwrite =
true."
));
}
}
blob.UploadByteArray(bytes, new
BlobRequestOptions() { Timeout = TimeSpan
.FromMinutes(3) });
}
public
byte
[] Load(string
filename)
{
filename =
TranslateFileName(filename);
CloudBlockBlob blob =
_container.GetBlockBlobReference(filename);
return
blob.DownloadByteArray();
}
public
bool
Exists(string
filename)
{
filename =
TranslateFileName(filename);
CloudBlockBlob blob =
_container.GetBlockBlobReference(filename);
try
{
blob.FetchAttributes();
return
true
;
}
catch
(StorageClientException ex)
{
if
(ex.ErrorCode == StorageErrorCode.ResourceNotFound)
{
return
false
;
}
else
{
throw
;
}
}
}
public
void
Delete(string
filename)
{
filename =
TranslateFileName(filename);
CloudBlockBlob blob =
_container.GetBlockBlobReference(filename);
blob.DeleteIfExists();
}
private
string
TranslateFileName(string
filename)
{
return
filename.Replace('/'
, '~'
).Replace('//'
, '`'
);
}
public
string
GetResourceUrl(string
filename)
{
// when
using the local storage simulator the blob enpoint without the end
'/'
// but when
using the azure it has '/' at the end of it
// so here i
have to use Path.Combine to construct the path and then replace the '/' back to
'/'
var
url = Path.Combine(_storageAccount.BlobEndpoint.ToString(), _containerName,
TranslateFileName(filename));
return
url.Replace('//'
, '/'
);
}
#endregion
}
在ASP.NET MVC中保存和显示图片
让我以一个 ASP.NET MVC
应用来展示如何使用上述实现。首先我们需要一个辅助类来根据配置初始化相应的IFileSystemAgent
实例。我创建了一个非常简单的工厂类来返回相应的实例(根据在web.config文件中相应的值)。在实际项目中,我们最好使用一些 IoC
容器,例如Unity
。
public
static
class
FileSystemAgentFactory
{
public
static
IFileSystemAgent
Resolve()
{
var
config = System.Configuration.ConfigurationManager.AppSettings["filesystem-agent"
];
switch
(config.ToLower())
{
case
"windows"
:
if
(HttpContext.Current != null
&& HttpContext.Current.Server != null
)
{
return
new
WindowsFileSystemAgent(new
HttpServerUtilityWrapper(HttpContext.Current.Server), "Upload"
);
}
else
{
throw
new
NotSupportedException
("HttpContext ot its Server property is null. The
WindowsFileSystemAgent must be used under the web
application."
);
}
case
"blob"
:
return
new
AzureBlobFileSystemAgent();
default
:
return
null
;
}
}
}
然后,在处理文件上传的controller中,我们可以使用该工厂类来初始化适当的IFileSystemAgent
实例。如果要保存文件,只需要调用其 Save
方法,而不管实际使用的是哪个实现类。如果我们需要在一般的服务器和Windows
Azure之间进行搬移时,我们只需要更改web.config文件。
[HttpPost]
public
ActionResult UploadFile(string
filekey)
{
if
(Request.Files != null
&&
Request.Files.Count > 0)
{
var file =
Request.Files[0];
var filename = "Avatar/"
+ Guid.NewGuid().ToString() +
Path.GetExtension(file.FileName);
var filesys =
FileSystemAgentFactory.Resolve();
filesys.Save(file.InputStream,
filename, true
);
Repository.Images.Add(filename);
}
return
RedirectToAction("Index"
);
}
类似的,当我们在网页上需要显示或者链接文件时,我们也无需关注其具体存储在哪里。为此,我们需要为HtmlHelper
创建一个拓展方法。有了该辅助方法,当我们需要显示或链接文件时,我们只需要使用IFileSystemAgent
的GetResourceUrl
方法,它便会返回适当的URL。
public
static
class
HelpHelpers
{
public
static
MvcHtmlString Image(this
HtmlHelper helper, string
filename)
{
return
Image(helper,
FileSystemAgentFactory.Resolve(), filename);
}
public
static
MvcHtmlString Image(this
HtmlHelper helper, IFileSystemAgent agent, string
filename)
{
return
Image(helper, agent, filename,
VirtualPathUtility.GetFileName("/"
+
filename));
}
public
static
MvcHtmlString Image(this
HtmlHelper helper, IFileSystemAgent agent, string
filename, string
{
var html = string
.Format("<img
src=/"{0}/" alt=/"{1}/" />"
, agent.GetResourceUrl(filename),
alt);
return
MvcHtmlString.Create(html);
}
}
总结
本文我介绍了如何统一在Windows
Azure和一般web应用之间的文件操作代码。相信还可以做进一步的改进和优化。其中之一便是我们可以将HttpServerUtilityBase
以及
CloudStorageAccount
抽离出一个接口来,例如, IRootProvider
,这样会方便进行依赖注入,也可以进行完全的单元测试。
对于Windows Azure应用,还会有其他的部分可以改进。例如,我们应该将经常会更改的配置数据放入ServiceConfiguration.cscfg
,而不是web.config。这要求我们构建一个 provider 来读取配置信息,我会在后面的文章中进行讲解。
从 这里
下载本文的展示代码。
本文翻译自:http://geekswithblogs.net/shaunxu/archive/2010/08/26/unify-your-file-operation-code-between-azure-and-normal-web.aspx
相关文章推荐
- 统一Windows Azure和一般web应用之间的文件操作代码(转+译)
- 统一Windows Azure和一般web应用之间的文件操作代码(转+译)
- 统一Windows Azure和一般web应用之间的文件操作代码(转+译)
- 统一Windows Azure和一般web应用之间的文件操作代码(转+译)
- 统一Windows Azure和一般web应用之间的文件操作代码(转+译)
- 统一Windows Azure和一般web应用之间的文件操作代码(转+译)
- android文件读写以及不同应用之间的文件读写操作
- Android学习笔记---13_文件的操作模式.各个应用之间的文件权限
- Linux下Docker对Web应用的自动化打包和发布,以及.tar文件的导出,常用操作命令大全(收藏)!!!
- 重新想象 Windows 8 Store Apps (70) - 其它: 文件压缩和解压缩, 与 Windows 商店相关的操作, app 与 web, 几个 Core 的应用, 页面的生命周期和程序的生命周期
- SharePoint Client Object应用 包含关于操作文件和文件夹的代码
- 重新想象 Windows 8 Store Apps (70) - 其它: 文件压缩和解压缩, 与 Windows 商店相关的操作, app 与 web, 几个 Core 的应用, 页面的生命周期和程序的生命周期
- asp.net连接sql2005,cs文件里直接书写的代码,纯手工操作,不在web.config里面进行配置。
- Spring 的优秀工具类盘点,第 1 部分: 文件资源操作和 Web 相关工具类
- Spring 的优秀工具类盘点,第 1 部分: 文件资源操作和 Web 相关工具类
- 第一节 无IDE下的web应用文件结构梳理
- windows网络编程(四)——不同主机之间传送文件应用
- 在ASP.NET项目中的web.config文件里配置数据库连接并在程序代码中获取连接字符串
- android JNI C代码对sdcard中文件的操作
- 管理类应用系统参数配置管理统一解决方法(含代码参考)