如何使用HTTP POST multipart/form-data将文件上传到服务器?

130
我正在开发Windows Phone 8应用程序。我想使用HTTP POST请求通过PHP web服务上传SQLite数据库,MIME类型为multipart/form-data,并使用名为"userid=SOME_ID"的字符串数据。
我不想使用第三方库,如HttpClient、RestSharp或MyToolkit。我尝试了下面的代码,但它不上传文件,也没有给我任何错误信息。它在Android、PHP等方面都很好运行,因此Web服务没有问题。以下是我提供的代码(适用于WP8)。哪里出了问题?
我已经谷歌过了,但没有找到针对WP8的具体解决方案。
async void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    var file = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync(DBNAME);
    //Below line gives me file with 0 bytes, why? Should I use 
    //IsolatedStorageFile instead of StorageFile
    //var file = await ApplicationData.Current.LocalFolder.GetFileAsync(DBNAME);
    byte[] fileBytes = null;
    using (var stream = await file.OpenReadAsync())
    {
        fileBytes = new byte[stream.Size];
        using (var reader = new DataReader(stream))
        {
            await reader.LoadAsync((uint)stream.Size);
            reader.ReadBytes(fileBytes);
        }
    }

    //var res = await HttpPost(Util.UPLOAD_BACKUP, fileBytes);
    HttpPost(fileBytes);
}

private void HttpPost(byte[] file_bytes)
{
    HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create("http://www.myserver.com/upload.php");
    httpWebRequest.ContentType = "multipart/form-data";
    httpWebRequest.Method = "POST";
    var asyncResult = httpWebRequest.BeginGetRequestStream((ar) => { GetRequestStreamCallback(ar, file_bytes); }, httpWebRequest);  
}

private void GetRequestStreamCallback(IAsyncResult asynchronousResult, byte[] postData)  
{
    //DON'T KNOW HOW TO PASS "userid=some_user_id"  
    HttpWebRequest request = (HttpWebRequest)asynchronousResult.AsyncState;  
    Stream postStream = request.EndGetRequestStream(asynchronousResult);  
    postStream.Write(postData, 0, postData.Length);  
    postStream.Close();  
    var asyncResult = request.BeginGetResponse(new AsyncCallback(GetResponseCallback), request);  
}  

private void GetResponseCallback(IAsyncResult asynchronousResult)  
{  
    HttpWebRequest request = (HttpWebRequest)asynchronousResult.AsyncState;  
    HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asynchronousResult);  
    Stream streamResponse = response.GetResponseStream();  
    StreamReader streamRead = new StreamReader(streamResponse);  
    string responseString = streamRead.ReadToEnd();  
    streamResponse.Close();  
    streamRead.Close();  
    response.Close();  
}  

我也尝试在Windows 8中解决我的问题,但它也没有起作用。
public async Task Upload(byte[] fileBytes)
{
    using (var client = new HttpClient())
    {
        using (var content = new MultipartFormDataContent("Upload----" + DateTime.Now.ToString(System.Globalization.CultureInfo.InvariantCulture)))
        {
            content.Add(new StreamContent(new MemoryStream(fileBytes)));
            //Not sure below line is true or not
            content.Add(new StringContent("userid=farhanW8"));
            using (var message = await client.PostAsync("http://www.myserver.com/upload.php", content))
            {
                var input = await message.Content.ReadAsStringAsync();
            }
        }
    }
}
16个回答

1

对于那些在尝试使用多部分表单上传时遇到403禁止问题的人,以下内容可能有所帮助,因为根据服务器配置情况,您可能会由于不正确的MultipartFormDataContent头而得到MULTIPART_STRICT_ERROR“!@eq 0”的情况。请注意,imagetag / filename变量都包括引号(\")例如filename = "\" myfile.png \""。

    MultipartFormDataContent form = new MultipartFormDataContent();
    ByteArrayContent imageContent = new ByteArrayContent(fileBytes, 0, fileBytes.Length);
    imageContent.Headers.TryAddWithoutValidation("Content-Disposition", "form-data; name="+imagetag+"; filename="+filename);
    imageContent.Headers.TryAddWithoutValidation("Content-Type", "image / png");
    form.Add(imageContent, imagetag, filename);

1

我知道这是一篇旧文章,但在尝试了整整一个下午之后,我不得不分享我找到的解决方法。我的解决方案适用于Xamarin应用程序,但只要是C#代码,它仍然可以工作:

我的问题是我想使用与我的Angular应用程序相同的端点和有效负载。(以下是代码)。

    public class ApiService<T> where T : class
    {
        private string _webServiceUrl = Settings.WebServiceUrl;
        private HttpClient httpClient;

        public ApiService(string path = null)
        {
            _webServiceUrl = $"{_webServiceUrl}{path}/";

            httpClient = new HttpClient();
            httpClient.BaseAddress = new Uri(_webServiceUrl);
            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Settings.Token);
        }

        private void HandleException(Exception ex, string url = "")
        {
            Settings.TotalHttpRequests -= 1;

            Crashes.TrackError(ex, new Dictionary<string, string>{ { "URL", url }, });

            MessagingCenter.Send<Page, string>(new Page(), "Exception", ex.Message);

        }

        public async Task<T> PostFormDataAsync(string url, string filePath, T t)
        {
            using (var form = new MultipartFormDataContent())
            {
                using (var fs = File.OpenRead(filePath))
                {
                    byte[] bytes = new byte[fs.Length];

                    fs.Read(bytes, 0, Convert.ToInt32(fs.Length));
                    fs.Close();

                    var json = JsonConvert.SerializeObject(t);
                    var data = new StringContent(json);

                    form.Add(new ByteArrayContent(bytes, 0, bytes.Length), "file", fs.Name);
                    form.Add(data, "data");

                    Settings.TotalHttpRequests += 1;

                    var result = await httpClient.PostAsync(url, form);

                    Settings.TotalHttpRequests -= 1;

                    if (result.IsSuccessStatusCode)
                    {
                        var content = await result.Content.ReadAsStringAsync();
                        var item = JsonConvert.DeserializeObject<T>(content);

                        return item;
                    }
                    else
                    {
                        await RaiseErrorAsync(result);

                        return default(T);
                    }
                }
            }
        }

        private async Task RaiseErrorAsync(HttpResponseMessage result)
        {
            var message = await result.Content.ReadAsStringAsync();

            MessagingCenter.Send<Page, string>(new Page(), "Error", message);
        }
    }

我为了其他的Xamarin开发者,提供了一些代码。

我的API终端点如下:

        [DisableRequestSizeLimit]
        [Consumes("multipart/form-data")]
        [HttpPost]
        public async Task<ActionResult> Post()
        {
            try
            {
                var data = (Request.Form["data"]).ToString();
                var myObject = JsonConvert.DeserializeObject<MyClass>(data);
                var file = Request.Form.Files[0];
                
                myObject = await _service.AddAsync(myObject, file);

                var result = _mapper.Map<MyClassDto>(myObject);

                return Ok(result);
            }
            catch (Exception ex)
            {
                return BadRequest(ex.Message);
            }

        }

由此,我可以得到一个IFormFile实例作为我的文件。我还能够反序列化我发送的类型为T的对象t

ApiService可以修改以实现IApiService,这样您就可以从Dependency Service(在Xamarin中)中受益。

在Angular方面,这是我在组件上所拥有的:

  onSubmit() {
    this.submitted = true;
    
    if (this.form.invalid) {return;}

    if (this.fileToUpload == null) {
      Swal.fire('File', 'Please attach the file', 'error');
      return;
    }

    const formData = new FormData();
    formData.append('file', this.fileToUpload, this.fileToUpload.name);
    formData.append('data', JSON.stringify(this.form.value));

    this.service.postEndpoint('', formData).subscribe((data) => {
      //Do something here
    });
  }

针对该服务:

import { ApiService } from '../api.service';

@Injectable({
  providedIn: 'root'
})
export class MyService extends ApiService<MyClass>{

  constructor(protected httpClient: HttpClient) {
    super(httpClient, 'MyApiController');
  }
}

ApiService(应用程序编程接口服务):
const httpOptions = {
  headers : new HttpHeaders({
    Authorization: 'Bearer ' + localStorage.getItem('token')
  })
};

export abstract class ApiService<T> {
  basePath = environment.apiPath;
  apiURL = `${this.basePath}api/`;

  constructor(protected httpClient: HttpClient, protected actionUrl: string) { }

  public postEndpoint(endPoint: string, model: any): Observable<any> {
    return this.httpClient.post(this.apiURL + `${this.actionUrl}/${endPoint}`, model, httpOptions);
  }
}

我希望这篇内容能够为大家节省一些时间!愉快编程!

1

我也想上传文件到服务器,这是一个Spring应用程序,最终我发现需要设置内容类型才能将其解释为文件。就像这样:

...
MultipartFormDataContent form = new MultipartFormDataContent();
var fileStream = new FileStream(uniqueTempPathInProject, FileMode.Open);
var streamContent = new StreamContent(fileStream);
streamContent.Headers.ContentType=new MediaTypeHeaderValue("application/zip");
form.Add(streamContent, "file",fileName);
...

这是我看到的最干净的解决方案。唯一可以改进的是在前三行中每个都加上 "using"。 - hongxu

0

它适用于Windows Phone 8.1。你可以试试看。

Dictionary<string, object> _headerContents = new Dictionary<string, object>();
const String _lineEnd = "\r\n";
const String _twoHyphens = "--";
const String _boundary = "*****";
private async void UploadFile_OnTap(object sender, System.Windows.Input.GestureEventArgs e)
{
   Uri serverUri = new Uri("http:www.myserver.com/Mp4UploadHandler", UriKind.Absolute);    
   string fileContentType = "multipart/form-data";       
   byte[] _boundarybytes = Encoding.UTF8.GetBytes(_twoHyphens + _boundary + _lineEnd);
   byte[] _trailerbytes = Encoding.UTF8.GetBytes(_twoHyphens + _boundary + _twoHyphens + _lineEnd);
   Dictionary<string, object> _headerContents = new Dictionary<string, object>();
   SetEndHeaders();  // to add some extra parameter if you need

   httpWebRequest = (HttpWebRequest)WebRequest.Create(serverUri);
   httpWebRequest.ContentType = fileContentType + "; boundary=" + _boundary;
   httpWebRequest.Method = "POST";
   httpWebRequest.AllowWriteStreamBuffering = false;  // get response after upload header part

   var fileName = Path.GetFileName(MediaStorageFile.Path);    
   Stream fStream = (await MediaStorageFile.OpenAsync(Windows.Storage.FileAccessMode.Read)).AsStream(); //MediaStorageFile is a storage file from where you want to upload the file of your device    
   string fileheaderTemplate = "Content-Disposition: form-data; name=\"{0}\"" + _lineEnd + _lineEnd + "{1}" + _lineEnd;    
   long httpLength = 0;
   foreach (var headerContent in _headerContents) // get the length of upload strem
   httpLength += _boundarybytes.Length + Encoding.UTF8.GetBytes(string.Format(fileheaderTemplate, headerContent.Key, headerContent.Value)).Length;

   httpLength += _boundarybytes.Length + Encoding.UTF8.GetBytes("Content-Disposition: form-data; name=\"uploadedFile\";filename=\"" + fileName + "\"" + _lineEnd).Length
                                       + Encoding.UTF8.GetBytes(_lineEnd).Length * 2 + _trailerbytes.Length;
   httpWebRequest.ContentLength = httpLength + fStream.Length;  // wait until you upload your total stream 

    httpWebRequest.BeginGetRequestStream((result) =>
    {
       try
       {
         HttpWebRequest request = (HttpWebRequest)result.AsyncState;
         using (Stream stream = request.EndGetRequestStream(result))
         {
            foreach (var headerContent in _headerContents)
            {
               WriteToStream(stream, _boundarybytes);
               WriteToStream(stream, string.Format(fileheaderTemplate, headerContent.Key, headerContent.Value));
             }

             WriteToStream(stream, _boundarybytes);
             WriteToStream(stream, "Content-Disposition: form-data; name=\"uploadedFile\";filename=\"" + fileName + "\"" + _lineEnd);
             WriteToStream(stream, _lineEnd);

             int bytesRead = 0;
             byte[] buffer = new byte[2048];  //upload 2K each time

             while ((bytesRead = fStream.Read(buffer, 0, buffer.Length)) != 0)
             {
               stream.Write(buffer, 0, bytesRead);
               Array.Clear(buffer, 0, 2048); // Clear the array.
              }

              WriteToStream(stream, _lineEnd);
              WriteToStream(stream, _trailerbytes);
              fStream.Close();
         }
         request.BeginGetResponse(a =>
         { //get response here
            try
            {
               var response = request.EndGetResponse(a);
               using (Stream streamResponse = response.GetResponseStream())
               using (var memoryStream = new MemoryStream())
               {   
                   streamResponse.CopyTo(memoryStream);
                   responseBytes = memoryStream.ToArray();  // here I get byte response from server. you can change depends on server response
               }    
              if (responseBytes.Length > 0 && responseBytes[0] == 1)
                 MessageBox.Show("Uploading Completed");
              else
                  MessageBox.Show("Uploading failed, please try again.");
            }
            catch (Exception ex)
            {}
          }, null);
      }
      catch (Exception ex)
      {
         fStream.Close();                             
      }
   }, httpWebRequest);
}

private static void WriteToStream(Stream s, string txt)
{
   byte[] bytes = Encoding.UTF8.GetBytes(txt);
   s.Write(bytes, 0, bytes.Length);
 }

 private static void WriteToStream(Stream s, byte[] bytes)
 {
   s.Write(bytes, 0, bytes.Length);
 }

 private void SetEndHeaders()
 {
   _headerContents.Add("sId", LocalData.currentUser.SessionId);
   _headerContents.Add("uId", LocalData.currentUser.UserIdentity);
   _headerContents.Add("authServer", LocalData.currentUser.AuthServerIP);
   _headerContents.Add("comPort", LocalData.currentUser.ComPort);
 }

0

基于 @Wolf5 的回答,这对我有用

var client = new WebClient();
client.Encoding = Encoding.UTF8;
var boundary = $"--------------------------{DateTime.Now.Ticks:x}";
client.Headers.Add("Content-Type", "multipart/form-data; boundary=" + boundary);
client.Headers.Add("Cookie", cookie); 

var start = $"--{boundary}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"{Path.GetFileName(fileName)}\"\r\nContent-Type: image/jpeg\r\n\r\n";
var end = $"\r\n--{boundary}--\r\n";

var lst = new List<byte>();
lst.AddRange(client.Encoding.GetBytes(start));
lst.AddRange(File.ReadAllBytes(fileName));
lst.AddRange(client.Encoding.GetBytes(end));

var resp = client.UploadData($"{ApiUrl}/api/upload/image", "POST", lst.ToArray());

0

回到@loop的答案。

我们在Asp.Net MVC中遇到了以下错误, 无法连接到远程服务器

解决方法: 在Web.Confing中添加以下代码后,我们的问题得到了解决

  <system.net>
    <defaultProxy useDefaultCredentials="true" >
    </defaultProxy>
  </system.net>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接