转: A guide to asynchronous file uploads in ASP.NET Web API RTM
原文:http://www.strathweb.com/2012/08/a-guide-to-asynchronous-file-uploads-in-asp-net-web-api-rtm/
he reason why body parts get saved on the disk with such random names is not an error or an accident. It has been designed like to that as a security measure – so that malicious uploaders wouldn’t compromise your system by uploading evil files disguised with friendly names.
Previously, one of the hacks I used, was to let the files save themselves with those randomly generated names, and then use File.Move to rename them. However, this is not the most efficient solution solution in the world by any stretch of the imagination.
However, now, you can easily derive from the default MultiPartFormDataStreamProvider and provide your own naming mechanism.
public class CustomMultipartFormDataStreamProvider : MultipartFormDataStreamProvider { public CustomMultipartFormDataStreamProvider(string path):base(path) { } public override string GetLocalFileName(System.Net.Http.Headers.HttpContentHeaders headers) { var name = !string.IsNullOrWhiteSpace(headers.ContentDisposition.FileName) ? headers.ContentDisposition.FileName : "NoName"; return name.Replace("\"", string.Empty); //this is here because Chrome submits files in quotation marks which get treated as part of the filename and get escaped // return base.GetLocalFileName(headers); } /* * In this case, we implement our own naming convention, and tell the framework to pull out the name of the file from * the Content-Disposition part of the request. this, in turn, will be the original name of the file as provided by the client. */ }
In this case, we implement our own naming convention, and tell the framework to pull out the name of the file from the Content-Disposition part of the request. this, in turn, will be the original name of the file as provided by the client.
This is the most basic example of all, but I hope you can imagine that you could do a lot more here – for exmaple implement your naming conventions and achieve file name patterns suitable for your application such as “UserA_avatar1.jpg” and so on.
You use it the same way as you would use the out-of-the-box MultipartFormDataStreamProvider:
Returning some meaningful info
Finally, you may want to return some information about the uploaded files to the client. If that’s the case, one way to do it is to use a helper class (which was used already in the older post):
public class FileDesc { public string Name { get; set; } public string Path { get; set; } public long Size { get; set; } public FileDesc(string n, string p, long s) { Name = n; Path = p; Size = s; } }
So now, instead of void we return a List<FileDesc> which can provide the client information about each of the uploaded files: its name, path and size.
public class UploadingController : ApiController { public Task<IEnumerable<FileDesc>> Post() { var folderName = "uploads"; var PATH = HttpContext.Current.Server.MapPath("~/"+folderName); var rootUrl = Request.RequestUri.AbsoluteUri.Replace(Request.RequestUri.AbsolutePath, String.Empty); if (Request.Content.IsMimeMultipartContent()) { var streamProvider = new CustomMultipartFormDataStreamProvider(PATH); var task = Request.Content.ReadAsMultipartAsync(streamProvider).ContinueWith<IEnumerable<FileDesc>>(t => { if (t.IsFaulted || t.IsCanceled) { throw new HttpResponseException(HttpStatusCode.InternalServerError); } var fileInfo = streamProvider.FileData.Select(i => { var info = new FileInfo(i.LocalFileName); return new FileDesc(info.Name, rootUrl+"/"+folderName+"/"+info.Name, info.Length / 1024); }); return fileInfo; }); return task; } else { throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "This request is not properly formatted")); } } }
client
var message = new HttpRequestMessage(); var content = new MultipartFormDataContent(); foreach (var file in files) { var filestream = new FileStream(file, FileMode.Open); var fileName = System.IO.Path.GetFileName(file); content.Add(new StreamContent(filestream), "file", fileName); } message.Method = HttpMethod.Post; message.Content = content; message.RequestUri = new Uri("http://localhost:3128/api/uploading/"); var client = new HttpClient(); client.SendAsync(message).ContinueWith(task => { if (task.Result.IsSuccessStatusCode) { //do something with response } });
If you are using Web Hosting within IIS context, you have to deal with maxAllowedContentLength and/or maxRequestLength – depending on the IIS version you are using.
Unless explicitly modified in web.config, the default maximum IIS 7 upload filesize is 30 000 000 bytes (28.6MB). If you try to upload a larger file, the response will be 404.13 error.
If you use IIS 7+, you can modify the maxAllowedContentLength setting, in the example below to 2GB:
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="2147483648" />
</requestFiltering>
</security>
</system.webServer>
Additionally, ASP.NET runtime has its own file size limit (which was also used by IIS 6, so if you use that version, that’s the only place you need to modify) – located under the httpRuntime element of the web.config.
By default it’s set to 4MB (4096kB). You need to change that as well (it’s set in kB not in bytes as the previous one!):
<system.web> <httpRuntime maxRequestLength="2097152" /> </system.web>
public class NoBufferPolicySelector : WebHostBufferPolicySelector { public override bool UseBufferedInputStream(object hostContext) { var context = hostContext as HttpContext; if (context != null) { if (string.Equals(context.Request.RequestContext.RouteData.Values["controller"].ToString(), "uploading", StringComparison.InvariantCultureIgnoreCase)) { return false; } } return true; //return base.UseBufferedInputStream(hostContext); } public override bool UseBufferedOutputStream(System.Net.Http.HttpResponseMessage response) { return base.UseBufferedOutputStream(response); } /* * In the case above, we check if the request is routed to the UploadingController, and if so, * we will not buffer the request. In all other cases, the requests remain buffered. You then need to register the new service in GlobalConfiguration * (note – this type of service needs to be global, and the setting will not work when set in * the per-controller configuration!): * GlobalConfiguration.Configuration.Services.Replace(typeof(IHostBufferPolicySelector), new WebAPI.Common.NoBufferPolicySelector()); */ }
That’s the service that makes a decision whether a given request will be buffered or not – on a per request basis. We could either implement the interface directly, or modify the only existing implementation – System.Web.Http.WebHost.WebHostBufferPolicySelector. Here’s an example:
In the case above, we check if the request is routed to the UploadingController, and if so, we will not buffer the request. In all other cases, the requests remain buffered.
You then need to register the new service in GlobalConfiguration (note – this type of service needs to be global, and the setting will not work when set in the per-controller configuration!):
GlobalConfiguration.Configuration.Services.Replace(typeof(IHostBufferPolicySelector), new WebAPI.Common.NoBufferPolicySelector());
“麻烦”是自己“处理”不当的结果
“困难”是自己“学习”不够的反射
“挫折”是自己“努力”不足的代价
浙公网安备 33010602011771号