使用 flow.js + ng-flow 将文件上传到 WebAPI 2
Uploading files with flow.js + ng-flow to WebAPI 2
我正在尝试使用 flow.js (https://github.com/flowjs/flow.js) via its Angular wrapper (https://github.com/flowjs/ng-flow/tree/master/samples/basic) 将文件上传到 ASP.NET WebAPI 2 服务器。无论如何,当我 select 一个文件上传到我的网站时 API 刚收到第一个块 GET 请求然后什么也没发生:没有 POST 完成,似乎 flow.js 做了未开始上传。
当我 select 一个文件时触发的初始 GET 是:
GET http://localhost:49330/api/upload?flowChunkNumber=1&flowChunkSize=1048576&flowCurrentChunkSize=4751&flowTotalSize=4751&flowIdentifier=4751-ElmahMySqlsql&flowFilename=Elmah.MySql.sql&flowRelativePath=Elmah.MySql.sql&flowTotalChunks=1 HTTP/1.1
Host: localhost:49330
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36
Accept: */*
Referer: http://localhost:49330/
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8,it;q=0.6
响应是:
HTTP/1.1 202 Accepted
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcUHJvamVjdHNcNDViXFRlc3RcVXBUZXN0XFVwVGVzdFxhcGlcdXBsb2Fk?=
X-Powered-By: ASP.NET
Date: Fri, 17 Apr 2015 08:02:56 GMT
Content-Length: 0
然后,不再发出请求。
似乎没有最新的 WebAPI 示例,只有零散的帖子,我为像我这样的新手创建了一个虚拟重现解决方案,您可以从 http://1drv.ms/1CSF5jq 下载:它是ASP.NET WebAPI 2 解决方案,在添加相应的 API 控制器后,我将上传代码放在主页视图中。只需按 F5 并尝试上传文件。您可以在 UploadController.cs
.
中找到 API 控制器
相关代码部分为:
a) client side: 类似ng-flow页面快速启动示例的页面:
<div class="row">
<div class="col-md-12">
<div flow-init="{target: '/api/upload'}"
flow-files-submitted="$flow.upload()"
flow-file-success="$file.msg = $message">
<input type="file" flow-btn />
<ol>
<li ng-repeat="file in $flow.files">{{file.name}}: {{file.msg}}</li>
</ol>
</div>
</div>
</div>
对应的代码本质上是一个空的TS骨架,模块初始化:
module Up {
export interface IMainScope {
}
export class MainController {
public static $inject = ["$scope"];
constructor(private $scope: IMainScope) {
}
}
var app = angular.module("app", ["flow"]);
app.controller("mainController", MainController);
}
b) 服务器端:我为所需的脚本和以下控制器添加了一些绑定,根据我在 How to upload file in chunks in ASP.NET using ng-Flow 找到的示例代码进行了修改。请注意,在 GET Upload
方法中,我使用绑定模型更改了签名(否则我们会收到 404,因为路由不匹配),当找不到块时,我 return a 202 - Accepted
代码而不是 404,因为 flow.js 文档说 200 对应于 "The chunk was accepted and correct. No need to re-upload",而 404 取消整个上传,任何其他代码(如此处的 202)告诉上传者重试。
[RoutePrefix("api")]
public class UploadController : ApiController
{
private readonly string _sRoot;
public UploadController()
{
_sRoot = HostingEnvironment.MapPath("~/App_Data/Uploads");
}
[Route("upload"), AcceptVerbs("GET")]
public IHttpActionResult Upload([FromUri] UploadBindingModel model)
{
if (IsChunkHere(model.FlowChunkNumber, model.FlowIdentifier)) return Ok();
return ResponseMessage(new HttpResponseMessage(HttpStatusCode.Accepted));
}
[Route("upload"), AcceptVerbs("POST")]
public async Task<IHttpActionResult> Upload()
{
// ensure that the request contains multipart/form-data
if (!Request.Content.IsMimeMultipartContent())
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
if (!Directory.Exists(_sRoot)) Directory.CreateDirectory(_sRoot);
MultipartFormDataStreamProvider provider =
new MultipartFormDataStreamProvider(_sRoot);
try
{
await Request.Content.ReadAsMultipartAsync(provider);
int nChunkNumber = Convert.ToInt32(provider.FormData["flowChunkNumber"]);
int nTotalChunks = Convert.ToInt32(provider.FormData["flowTotalChunks"]);
string sIdentifier = provider.FormData["flowIdentifier"];
string sFileName = provider.FormData["flowFilename"];
// rename the generated file
MultipartFileData chunk = provider.FileData[0]; // Only one file in multipart message
RenameChunk(chunk, nChunkNumber, sIdentifier);
// assemble chunks into single file if they're all here
TryAssembleFile(sIdentifier, nTotalChunks, sFileName);
return Ok();
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
private string GetChunkFileName(int chunkNumber, string identifier)
{
return Path.Combine(_sRoot,
String.Format(CultureInfo.InvariantCulture, "{0}_{1}",
identifier, chunkNumber));
}
private void RenameChunk(MultipartFileData chunk, int chunkNumber, string identifier)
{
string sGeneratedFileName = chunk.LocalFileName;
string sChunkFileName = GetChunkFileName(chunkNumber, identifier);
if (File.Exists(sChunkFileName)) File.Delete(sChunkFileName);
File.Move(sGeneratedFileName, sChunkFileName);
}
private string GetFileName(string identifier)
{
return Path.Combine(_sRoot, identifier);
}
private bool IsChunkHere(int chunkNumber, string identifier)
{
string sFileName = GetChunkFileName(chunkNumber, identifier);
return File.Exists(sFileName);
}
private bool AreAllChunksHere(string identifier, int totalChunks)
{
for (int nChunkNumber = 1; nChunkNumber <= totalChunks; nChunkNumber++)
if (!IsChunkHere(nChunkNumber, identifier)) return false;
return true;
}
private void TryAssembleFile(string identifier, int totalChunks, string filename)
{
if (!AreAllChunksHere(identifier, totalChunks)) return;
// create a single file
string sConsolidatedFileName = GetFileName(identifier);
using (Stream destStream = File.Create(sConsolidatedFileName, 15000))
{
for (int nChunkNumber = 1; nChunkNumber <= totalChunks; nChunkNumber++)
{
string sChunkFileName = GetChunkFileName(nChunkNumber, identifier);
using (Stream sourceStream = File.OpenRead(sChunkFileName))
{
sourceStream.CopyTo(destStream);
}
} //efor
destStream.Close();
}
// rename consolidated with original name of upload
// strip to filename if directory is specified (avoid cross-directory attack)
filename = Path.GetFileName(filename);
Debug.Assert(filename != null);
string sRealFileName = Path.Combine(_sRoot, filename);
if (File.Exists(filename)) File.Delete(sRealFileName);
File.Move(sConsolidatedFileName, sRealFileName);
// delete chunk files
for (int nChunkNumber = 1; nChunkNumber <= totalChunks; nChunkNumber++)
{
string sChunkFileName = GetChunkFileName(nChunkNumber, identifier);
File.Delete(sChunkFileName);
} //efor
}
}
200 状态并不是唯一被视为成功的状态。 201和202也是。
了解 successStatuses
的选项:
https://github.com/flowjs/flow.js/blob/master/dist/flow.js#L91
所以你只需要改变 return 204 状态,这意味着 No Content
.
我正在尝试使用 flow.js (https://github.com/flowjs/flow.js) via its Angular wrapper (https://github.com/flowjs/ng-flow/tree/master/samples/basic) 将文件上传到 ASP.NET WebAPI 2 服务器。无论如何,当我 select 一个文件上传到我的网站时 API 刚收到第一个块 GET 请求然后什么也没发生:没有 POST 完成,似乎 flow.js 做了未开始上传。
当我 select 一个文件时触发的初始 GET 是:
GET http://localhost:49330/api/upload?flowChunkNumber=1&flowChunkSize=1048576&flowCurrentChunkSize=4751&flowTotalSize=4751&flowIdentifier=4751-ElmahMySqlsql&flowFilename=Elmah.MySql.sql&flowRelativePath=Elmah.MySql.sql&flowTotalChunks=1 HTTP/1.1
Host: localhost:49330
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36
Accept: */*
Referer: http://localhost:49330/
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8,it;q=0.6
响应是:
HTTP/1.1 202 Accepted
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcUHJvamVjdHNcNDViXFRlc3RcVXBUZXN0XFVwVGVzdFxhcGlcdXBsb2Fk?=
X-Powered-By: ASP.NET
Date: Fri, 17 Apr 2015 08:02:56 GMT
Content-Length: 0
然后,不再发出请求。
似乎没有最新的 WebAPI 示例,只有零散的帖子,我为像我这样的新手创建了一个虚拟重现解决方案,您可以从 http://1drv.ms/1CSF5jq 下载:它是ASP.NET WebAPI 2 解决方案,在添加相应的 API 控制器后,我将上传代码放在主页视图中。只需按 F5 并尝试上传文件。您可以在 UploadController.cs
.
相关代码部分为:
a) client side: 类似ng-flow页面快速启动示例的页面:
<div class="row">
<div class="col-md-12">
<div flow-init="{target: '/api/upload'}"
flow-files-submitted="$flow.upload()"
flow-file-success="$file.msg = $message">
<input type="file" flow-btn />
<ol>
<li ng-repeat="file in $flow.files">{{file.name}}: {{file.msg}}</li>
</ol>
</div>
</div>
</div>
对应的代码本质上是一个空的TS骨架,模块初始化:
module Up {
export interface IMainScope {
}
export class MainController {
public static $inject = ["$scope"];
constructor(private $scope: IMainScope) {
}
}
var app = angular.module("app", ["flow"]);
app.controller("mainController", MainController);
}
b) 服务器端:我为所需的脚本和以下控制器添加了一些绑定,根据我在 How to upload file in chunks in ASP.NET using ng-Flow 找到的示例代码进行了修改。请注意,在 GET Upload
方法中,我使用绑定模型更改了签名(否则我们会收到 404,因为路由不匹配),当找不到块时,我 return a 202 - Accepted
代码而不是 404,因为 flow.js 文档说 200 对应于 "The chunk was accepted and correct. No need to re-upload",而 404 取消整个上传,任何其他代码(如此处的 202)告诉上传者重试。
[RoutePrefix("api")]
public class UploadController : ApiController
{
private readonly string _sRoot;
public UploadController()
{
_sRoot = HostingEnvironment.MapPath("~/App_Data/Uploads");
}
[Route("upload"), AcceptVerbs("GET")]
public IHttpActionResult Upload([FromUri] UploadBindingModel model)
{
if (IsChunkHere(model.FlowChunkNumber, model.FlowIdentifier)) return Ok();
return ResponseMessage(new HttpResponseMessage(HttpStatusCode.Accepted));
}
[Route("upload"), AcceptVerbs("POST")]
public async Task<IHttpActionResult> Upload()
{
// ensure that the request contains multipart/form-data
if (!Request.Content.IsMimeMultipartContent())
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
if (!Directory.Exists(_sRoot)) Directory.CreateDirectory(_sRoot);
MultipartFormDataStreamProvider provider =
new MultipartFormDataStreamProvider(_sRoot);
try
{
await Request.Content.ReadAsMultipartAsync(provider);
int nChunkNumber = Convert.ToInt32(provider.FormData["flowChunkNumber"]);
int nTotalChunks = Convert.ToInt32(provider.FormData["flowTotalChunks"]);
string sIdentifier = provider.FormData["flowIdentifier"];
string sFileName = provider.FormData["flowFilename"];
// rename the generated file
MultipartFileData chunk = provider.FileData[0]; // Only one file in multipart message
RenameChunk(chunk, nChunkNumber, sIdentifier);
// assemble chunks into single file if they're all here
TryAssembleFile(sIdentifier, nTotalChunks, sFileName);
return Ok();
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
private string GetChunkFileName(int chunkNumber, string identifier)
{
return Path.Combine(_sRoot,
String.Format(CultureInfo.InvariantCulture, "{0}_{1}",
identifier, chunkNumber));
}
private void RenameChunk(MultipartFileData chunk, int chunkNumber, string identifier)
{
string sGeneratedFileName = chunk.LocalFileName;
string sChunkFileName = GetChunkFileName(chunkNumber, identifier);
if (File.Exists(sChunkFileName)) File.Delete(sChunkFileName);
File.Move(sGeneratedFileName, sChunkFileName);
}
private string GetFileName(string identifier)
{
return Path.Combine(_sRoot, identifier);
}
private bool IsChunkHere(int chunkNumber, string identifier)
{
string sFileName = GetChunkFileName(chunkNumber, identifier);
return File.Exists(sFileName);
}
private bool AreAllChunksHere(string identifier, int totalChunks)
{
for (int nChunkNumber = 1; nChunkNumber <= totalChunks; nChunkNumber++)
if (!IsChunkHere(nChunkNumber, identifier)) return false;
return true;
}
private void TryAssembleFile(string identifier, int totalChunks, string filename)
{
if (!AreAllChunksHere(identifier, totalChunks)) return;
// create a single file
string sConsolidatedFileName = GetFileName(identifier);
using (Stream destStream = File.Create(sConsolidatedFileName, 15000))
{
for (int nChunkNumber = 1; nChunkNumber <= totalChunks; nChunkNumber++)
{
string sChunkFileName = GetChunkFileName(nChunkNumber, identifier);
using (Stream sourceStream = File.OpenRead(sChunkFileName))
{
sourceStream.CopyTo(destStream);
}
} //efor
destStream.Close();
}
// rename consolidated with original name of upload
// strip to filename if directory is specified (avoid cross-directory attack)
filename = Path.GetFileName(filename);
Debug.Assert(filename != null);
string sRealFileName = Path.Combine(_sRoot, filename);
if (File.Exists(filename)) File.Delete(sRealFileName);
File.Move(sConsolidatedFileName, sRealFileName);
// delete chunk files
for (int nChunkNumber = 1; nChunkNumber <= totalChunks; nChunkNumber++)
{
string sChunkFileName = GetChunkFileName(nChunkNumber, identifier);
File.Delete(sChunkFileName);
} //efor
}
}
200 状态并不是唯一被视为成功的状态。 201和202也是。
了解 successStatuses
的选项:
https://github.com/flowjs/flow.js/blob/master/dist/flow.js#L91
所以你只需要改变 return 204 状态,这意味着 No Content
.