关于上传文件到Azure Storage没有什么可讲的,不论我们使用哪种平台、语言,上传流程都如下图所示:

从上图我们可以了解到从客户端上传文件到Storage,是需要先将文件上传到应用服务上,然后再通过应用服务器上的后端代码将文件上传到Storage,在这里应用服务器就是起到一个代理的作用。

当然,这是最常规的做法,但是确有一个致命的弱点,那就是我们上传文件到Storage中,是要占用应用服务器的计算、网络、IO等资源的,这对于一些对性能要求很高的应用场景是很难接受的。

那么,我们是否有解决方案?答案是肯定的,我们可以跳过应用服务器从客户端将文件直传到Storage中,那么我们如何来构建这套解决方案呢?这就是我们下面要讲的。

我们先来看下直传文件到Storage的流程图:

这里特别有几点需要注意的地方:

1、  不能使用存储账号和存储密钥来验证身份进行文件上传,因为这会在客户端暴露存储账号和存储密钥等敏感信息。

2、  Storage默认是不能跨域访问的,所以我们需要将应用服务器所在域加入到Storage的跨域规则中。

3、  通过上图,我们可以看到客户端一共发送两次http请求,第一次从应用服务器获取Shared Access Signature(具有写权限),再带着获取到的SAS将文件从客户端上传到Storage。

大致流程和思路已经讲清楚了,那么下面我们就来看看用代码是如何实现的。

Index.html

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<meta charset="utf-8" />
<title>直传文件到Storage</title>
<script src="~/Scripts/jquery-1.10.2.js"></script>
<script src="~/Scripts/uploadfile.js"></script>
</head>
<body>
<input type="file" id="file" name="file" /> <div id="output">
<strong>File properties:</strong>
<br />
<p>
Name: <span id="fileName"></span>
</p>
<p>
File size: <span id="fileSize"></span>bytes.
</p>
<p>
File type: <span id="fileType"></span>
</p>
<p>
<input type="button" value="Upload File" id="uploadFile" />
</p>
</div>
</body>
</html>

updatefile.js

(function ($) {
var reader = null,
requestUri = null,
selectedFile = null,
blobName = null; function sendAjax(url, dataToSend, beforeSendFunction, successFunction) {
$.ajax({
url: url,
type: "PUT",
data: dataToSend,
processData: false,
beforeSend: beforeSendFunction,
tryCount: 0,
success: successFunction, });
} function readerOnLoadEnd(evt) {
if (evt.target.readyState === FileReader.DONE) {
var uri = requestUri,
requestData = new Uint8Array(evt.target.result);
sendAjax(uri, requestData,
function (xhr) {
xhr.setRequestHeader("x-ms-blob-type", "BlockBlob");
xhr.setRequestHeader("x-ms-blob-content-type", getContentType(blobName));
}, function (data, status) {
alert("upload success.");
});
}
}; function handleFileSelect(e) {
selectedFile = e.target.files[0];
blobName = selectedFile.name;
var fileSize = selectedFile.size;
$('#output').show();
$('#fileName').text(blobName);
$('#fileSize').text(fileSize);
$('#fileType').text(selectedFile.type); $.get('{获取Blob SAS的接口地址}', { 'blobName': blobName }, function (data) {
console.log(data);
requestUri = data;
});
} function startUpload() {
$("#uploadFile").prop('disabled', true);
$("#file").prop('disabled', true);
var slice = selectedFile.slice(0, selectedFile.size);
reader.readAsArrayBuffer(slice);
} function getContentType(blobName) {
var ext = blobName.substr(blobName.lastIndexOf('.'));
var contentType = 'application/octet-stream';
switch (ext) {
case ".txt":
contentType = "text/plain";
break;
case ".png":
contentType = "image/png";
break;
case ".zip":
contentType = "application/zip";
break;
case ".pptx":
contentType = "application/vnd.ms-powerpoint";
break;
case ".docx":
contentType = "application/msword";
break;
case ".pdf":
contentType = "application/pdf";
break;
case ".jpg":
case ".jpeg":
contentType = "image/jpeg";
break;
case ".html":
contentType = "text/html";
break;
case ".js":
contentType = "application/x-javascript";
break;
case ".css":
contentType = "text/css";
break;
case ".gif":
contentType = "image/gif";
break;
case ".ico":
contentType = "image/x-icon";
break;
case ".mp4":
contentType = "video/mpeg4";
break;
case ".mp3":
contentType = "audio/mp3";
break;
default:
break;
}
return contentType;
} $(function () {
$('#output').hide();
if (window.File && window.FileReader && window.FileList && window.Blob) {
reader = new FileReader();
reader.onloadend = readerOnLoadEnd;
} else {
alert('The File APIs are not fully supported in this browser.');
$('#file').prop('disabled', true);
return;
}
$('#file').bind('change', handleFileSelect);
//上传文件
$('#uploadFile').bind('click', startUpload);
});
}(jQuery))

后端提供的Shared Access Signature接口代码

private string _StorageConnectionString = ConfigurationManager.AppSettings["StorageConnectionString"];
private string _AccountName = ConfigurationManager.AppSettings["AccountName"];
private string _AccountKey = ConfigurationManager.AppSettings["AccountKey"];
private string _ContainerName = ConfigurationManager.AppSettings["ContainerName"]; [HttpGet]
public string GetRequestStorageUri(string blobName)
{
if (string.IsNullOrEmpty(blobName))
{
throw new ArgumentNullException();
}
if (string.IsNullOrEmpty(_ContainerName)) {
throw new NullReferenceException("container is null or empty");
} CloudBlockBlob blockBlob = GetCloudBlockBlob(_ContainerName, blobName);
string sas = GetBlobSharedAccessSignature(blockBlob);
string requestUri = string.Format("https://{0}.blob.core.chinacloudapi.cn/{1}/{2}{3}", _AccountName, _ContainerName, blobName, sas);
return requestUri;
} private CloudBlockBlob GetCloudBlockBlob(string containerName,string blobName)
{
if (string.IsNullOrEmpty(containerName) || string.IsNullOrEmpty(blobName))
{
throw new ArgumentNullException();
}
if (string.IsNullOrEmpty(_StorageConnectionString))
{
throw new NullReferenceException("storage connection string is null or empty");
}
CloudStorageAccount account = CloudStorageAccount.Parse(_StorageConnectionString);
CloudBlobClient blobClient = account.CreateCloudBlobClient();
ServiceProperties serviceProperties = blobClient.GetServiceProperties();
serviceProperties.Cors.CorsRules.Clear();
serviceProperties.Cors.CorsRules.Add(new CorsRule
{
AllowedOrigins = new List<string> { "{应用服务器所在域}" },
AllowedMethods = CorsHttpMethods.Get| CorsHttpMethods.Put| CorsHttpMethods.Post| CorsHttpMethods.Delete,
AllowedHeaders = new List<string> { "x-ms-*", "content-type", "accept" },
MaxAgeInSeconds = * *
});
blobClient.SetServiceProperties(serviceProperties);
CloudBlobContainer container = blobClient.GetContainerReference(containerName);
container.CreateIfNotExists();
CloudBlockBlob blockBlob = container.GetBlockBlobReference(blobName);
return blockBlob;
} private string GetBlobSharedAccessSignature(CloudBlob blob) {
if (blob == null)
{
throw new ArgumentNullException("blob is null");
}
return blob.GetSharedAccessSignature(new SharedAccessBlobPolicy {
Permissions = SharedAccessBlobPermissions.Add| SharedAccessBlobPermissions.Create| SharedAccessBlobPermissions.Delete| SharedAccessBlobPermissions.List| SharedAccessBlobPermissions.Read| SharedAccessBlobPermissions.Write,
SharedAccessStartTime = DateTime.UtcNow,
SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes()
});
}

从客户端浏览器直传文件到Storage的更多相关文章

  1. 直传文件到Azure Storage的Blob服务中

    (此文章同时发表在本人微信公众号“dotNET每日精华文章”,欢迎右边二维码来关注.) 题记:为了庆祝获得微信公众号赞赏功能,忙里抽闲分享一下最近工作的一点心得:如何直接从浏览器中上传文件到Azure ...

  2. HTML5+flash打造兼容各浏览器的文件上传方案

    上一篇文章介绍了HTML5版的文件上传插件,相比flash,采用HTML5的新技术无疑可以提升程序的加载速度.但是在目前的情况看来,HTML5的特性支持度不高,插件的可用性范围确实比较窄.例如,我在插 ...

  3. asp.net 客户端上传文件全路径获取方法

    asp.net  获取客户端上传文件全路径方法: eg:F:\test\1.doc 基于浏览器安全问题,浏览器将屏蔽获取客户端文件全路径的方法,只能获取到文件的文件名,如果需要获取全路径则需要另想其他 ...

  4. asp.net 浏览器下载文件的四种方式

    // 方法一:TransmitFile实现下载 protected void Button1_Click(object sender, EventArgs e) { Response.ContentT ...

  5. 页面加载异常 清除浏览器静态文件 js css 缓存 js动态加载js css文件,可以配置文件后辍,防止浏览器缓存

    js清除浏览器缓存的几种方法 - 兔老霸夏 - 博客园 https://www.cnblogs.com/Mr-Rocker/p/6031096.html js清除浏览器缓存的几种方法   一.CSS和 ...

  6. Axis 生成客户端client stub文件

    [转自] http://blog.csdn.net/qiao000_000/article/details/5568442 开发前,有个同事先给我们不熟悉Web Service的程序员进行了一些培训, ...

  7. 利用Beef劫持客户端浏览器

    利用Beef劫持客户端浏览器   环境: 1.Kali(使用beef生成恶意代码,IP:192.168.114.140) 2.一台web服务器(留言板存在XSS跨站脚本漏洞,IP:192.168.11 ...

  8. 使用Beef劫持客户端浏览器并进一步使用Beef+msf拿客户端shell

    环境: 1.Kali(使用beef生成恶意代码,IP:192.168.114.140) 2.一台web服务器(留言板存在XSS跨站脚本漏洞,IP:192.168.114.204) 3. 客户端(用于访 ...

  9. 利用form.submit提交表单导出文件到客户端浏览器, 提示下载!

    本来是想利用ajax提交json数据到服务端, 让服务端生成一个excel文件并提示客户端浏览器下载的. 但是搞了很久发现ajax方式是无法触发浏览器弹出文件下载的. 网上很多的方案都是说利用form ...

随机推荐

  1. 获取web页面xpath

    1. Open Chrome 2. Right click the element that you want to get xpath 3. select "Inspector" ...

  2. [整理]WebAPP开发的框架

    http://www.zhihu.com/question/27210335 http://ionicframework.com/getting-started/ http://cordova.apa ...

  3. CSS float浅析

    写在开篇: 浮动属性的设计初衷,只是为了实现文本环绕效果! 时刻牢记这一点,才能正确使用浮动. 由于浮动元素脱离文档流,它后面的块级元素会忽略它的存在,占据它原本的位置,但是这个块级元素中的内联元素, ...

  4. Maven部署dao工程到私服上——(十三)

    1.修改settings.xml 需要在客户端即(部署dao工程)的电脑上配置 maven环境,并修改 settings.xml 文件,配置连接私服的用户和密码 . 此用户名和密码用于私服校验,因为私 ...

  5. JAVA中Collection接口和Map接口的主要实现类

    Collection接口 Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements).一些Collection允许相同的元素 ...

  6. 基于FPGA(DDS)的正弦波发生器

    记录背景:昨晚快下班时,与同事rk聊起怎么用FPGA实现正弦波的输出.我第一反应是利用高频的PWM波去滤波,但感觉这样的波形精度肯定很差:后来想起之前由看过怎么用FPGA产生正弦波的技术,但怎么都想不 ...

  7. 安装Scrapy遇到的坑

    安装过程怕是要吐血,架梯子等等结果被setuptools的版本给坑了. 参考网址: http://blog.csdn.net/YHYR_YCY/article/details/78876148 htt ...

  8. 文字小于12px时,设置line-height不居中问题

    设置了文字了小于12px时,会存在设置了line-height的不生效的问题,主要是由于基线的问题,这篇文章解释的很清楚,有兴趣的可以看下https://blog.csdn.net/q12151634 ...

  9. Java 把异常传递给控制台

    最简答而又不用写多少代码就能保护异常信息的方法,就是把它们从main()传递到控制台,对于简单的程序可以像这样: package exceptions; //: exceptions/MainExce ...

  10. pyqt5-基础

    PyQt5是一套来自Digia的Qt5应用框架和Python的粘合剂.支持Python2.x和Python3.x版本. PyQt5以一套Python模块的形式来实现功能.它包含了超过620个类,600 ...