简单的Demo,用于了解WebAPI如何同时接收文件及数据,同时提供HttpClient模拟如何同时上传文件和数据的Demo,下面是HttpClient上传的Demo界面
1、HttpClient部分:
HttpClient通过PostAsync提交数据时,第二个请求参数为抽象类HttpContent,当前我们需要通过multipart/form-data的方式模拟请求,multipart对应的请求HttpContent为MultipartContent及其子类MultipartFormDataContent,按名字明显可以看出MultipartFormDataContent对应multipart/form-data,MultipartFormDataContent可以通过Add方法添加具体的HttpContent,这里当然是添加ByteArrayContent了
下面是分别获取文件及键值对集合对应ByteArrayContent集合的代码
///
/// 获取文件集合对应的ByteArrayContent集合
///
///
///
private List GetFileByteArrayContent(HashSet files)
{
List list = new List();
foreach (var file in files)
{
var fileContent = new ByteArrayContent(File.ReadAllBytes(file));
fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = Path.GetFileName(file)
};
list.Add(fileContent);
}
return list;
}
///
/// 获取键值集合对应的ByteArrayContent集合
///
///
///
private List GetFormDataByteArrayContent(NameValueCollection collection)
{
List list = new List();
foreach (var key in collection.AllKeys)
{
var dataContent = new ByteArrayContent(Encoding.UTF8.GetBytes(collection[key]));
dataContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
Name = key
};
list.Add(dataContent);
}
return list;
}
然后提交Api部分的代码如下(如需完整代码,请至底部点击源代码下载链接)
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/" + this.cmbResponseContentType.Text.ToLower()));//设定要响应的数据格式
using (var content = new MultipartFormDataContent())//表明是通过multipart/form-data的方式上传数据
{
var formDatas = this.GetFormDataByteArrayContent(this.GetNameValueCollection(this.gv_FormData));//获取键值集合对应的ByteArrayContent集合
var files = this.GetFileByteArrayContent(this.GetHashSet(this.gv_File));//获取文件集合对应的ByteArrayContent集合
Action> act = (dataContents) =>
{//声明一个委托,该委托的作用就是将ByteArrayContent集合加入到MultipartFormDataContent中
foreach (var byteArrayContent in dataContents)
{
content.Add(byteArrayContent);
}
};
act(formDatas);//执行act
act(files);//执行act
try
{
var result = client.PostAsync(this.txtUrl.Text, content).Result;//post请求
this.txtResponse.Text = result.Content.ReadAsStringAsync().Result;//将响应结果显示在文本框内
}
catch (Exception ex)
{
this.txtResponse.Text = ex.ToString();//将异常信息显示在文本框内
}
}
}
2、WebAPI部分
其实WebAPI这部分真的没什么,完全是参考了国外大牛的代码,不过某些不明了的地方在方法内有备注,有时间会去研究下如何才能实现无需保存文件至硬盘,即可获取相应的数据流
[HttpPost]
public async Task> Post(int id = 0)
{
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
Dictionary dic = new Dictionary();
string root = HttpContext.Current.Server.MapPath("~/App_Data");//指定要将文件存入的服务器物理位置
var provider = new MultipartFormDataStreamProvider(root);
try
{
// Read the form data.
await Request.Content.ReadAsMultipartAsync(provider);
// This illustrates how to get the file names.
foreach (MultipartFileData file in provider.FileData)
{//接收文件
Trace.WriteLine(file.Headers.ContentDisposition.FileName);//获取上传文件实际的文件名
Trace.WriteLine("Server file path: " + file.LocalFileName);//获取上传文件在服务上默认的文件名
}//TODO:这样做直接就将文件存到了指定目录下,暂时不知道如何实现只接收文件数据流但并不保存至服务器的目录下,由开发自行指定如何存储,比如通过服务存到图片服务器
foreach (var key in provider.FormData.AllKeys)
{//接收FormData
dic.Add(key, provider.FormData[key]);
}
}
catch
{
throw;
}
return dic;
}
github源代码下载,运行Demo时请先调试服务端,然后开启客户端,如果缺少HttpClient对应的dll,请通过NuGet下载
PS:补充由服务端指定文件存储位置博客地址
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
public class MultipartFormDataMemoryStreamProvider : MultipartFormDataStreamProvider
{
public MultipartFormDataMemoryStreamProvider()
: base("/")
{
}
public override Stream GetStream(HttpContent parent, HttpContentHeaders headers)
{
if (parent == null)
{
throw new ArgumentNullException("parent");
}
if (headers == null)
{
throw new ArgumentNullException("headers");
}
MemoryStream stream = new MemoryStream();
if (IsFileContent(parent, headers))
{
MultipartFileData item = new MultipartFileDataStream(headers, string.Empty, stream);
this.FileData.Add(item);
}
return stream;
}
private bool IsFileContent(HttpContent parent, HttpContentHeaders headers)
{
ContentDispositionHeaderValue contentDisposition = headers.ContentDisposition;
if (contentDisposition == null)
{
throw new InvalidOperationException("Content-Disposition error");
}
return !string.IsNullOrEmpty(contentDisposition.FileName);
}
}
public class MultipartFileDataStream : MultipartFileData, IDisposable
{
///
/// file content stream
///
public Stream Stream { get; private set; }
public MultipartFileDataStream(HttpContentHeaders headers, string localFileName, Stream stream)
: base(headers, localFileName)
{
if (stream == null)
{
throw new ArgumentNullException("stream");
}
this.Stream = stream;
}
public void Dispose()
{
this.Stream.Dispose();
}
}
使用方式基本没变化,只是取Stream的地方有所调整
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
Dictionary dic = new Dictionary();
#region 原来使用MultipartFormDataStreamProvider的方式
//string root = HttpContext.Current.Server.MapPath("~/App_Data");//指定要将文件存入的服务器物理位置
//var provider = new MultipartFormDataStreamProvider(root);
#endregion
string root = AppDomain.CurrentDomain.BaseDirectory;
var provider = new MultipartFormDataMemoryStreamProvider();
try
{
// Read the form data.
await Request.Content.ReadAsMultipartAsync(provider);
// This illustrates how to get the file names.
foreach (MultipartFileData file in provider.FileData)
{//接收文件
Trace.WriteLine(file.Headers.ContentDisposition.FileName);//获取上传文件实际的文件名
Trace.WriteLine("Server file path: " + file.LocalFileName);//获取上传文件在服务上默认的文件名
var stream = ((MultipartFileDataStream)file).Stream;
using (StreamWriter sw = new StreamWriter(Path.Combine(root, file.Headers.ContentDisposition.FileName)))
{
stream.CopyTo(sw.BaseStream);
sw.Flush();
}
}
foreach (var key in provider.FormData.AllKeys)
{//接收FormData
dic.Add(key, provider.FormData[key]);
Console.WriteLine($"Key:{key} Value:{provider.FormData[key]}");
}
}
catch(Exception ex)
{
throw;
}
2018-05-02 Note:本篇内容不适用于MVC Core,在Core中应当使用IFormFile来获取文件,上传文件方式不同,可以通过Action参数(对应multipart/form-data)或者Request.Forms.Files(对应ajax)方式获取