C# Client의 멀티 파트 양식
-
03-07-2019 - |
문제
C# 클라이언트 (Outlook Addin)의 PHP 응용 프로그램에서 양식을 작성하려고합니다. Fiddler를 사용하여 PHP 응용 프로그램 내에서 원래 요청을 보았고 양식은 멀티 파트/양식으로 전송됩니다. 불행히도 .NET은 이러한 유형의 양식에 대한 기본 지원을 제공하지 않습니다 (WebClient에는 파일 업로드 방법 만 있습니다). 도서관을 아는 사람이 있습니까? 아니면이를 달성하기위한 코드가 있습니까? 다른 값과 추가로 파일을 게시하고 싶습니다.
도와 주셔서 감사합니다, 세바스찬
해결책
이것은 내가 쓴 일부 샘플 코드에서 잘라서 붙여 넣습니다. 기본 사항을 제공해야합니다. 현재 파일 데이터와 양식 데이터 만 지원합니다.
public class PostData
{
private List<PostDataParam> m_Params;
public List<PostDataParam> Params
{
get { return m_Params; }
set { m_Params = value; }
}
public PostData()
{
m_Params = new List<PostDataParam>();
// Add sample param
m_Params.Add(new PostDataParam("email", "MyEmail", PostDataParamType.Field));
}
/// <summary>
/// Returns the parameters array formatted for multi-part/form data
/// </summary>
/// <returns></returns>
public string GetPostData()
{
// Get boundary, default is --AaB03x
string boundary = ConfigurationManager.AppSettings["ContentBoundary"].ToString();
StringBuilder sb = new StringBuilder();
foreach (PostDataParam p in m_Params)
{
sb.AppendLine(boundary);
if (p.Type == PostDataParamType.File)
{
sb.AppendLine(string.Format("Content-Disposition: file; name=\"{0}\"; filename=\"{1}\"", p.Name, p.FileName));
sb.AppendLine("Content-Type: text/plain");
sb.AppendLine();
sb.AppendLine(p.Value);
}
else
{
sb.AppendLine(string.Format("Content-Disposition: form-data; name=\"{0}\"", p.Name));
sb.AppendLine();
sb.AppendLine(p.Value);
}
}
sb.AppendLine(boundary);
return sb.ToString();
}
}
public enum PostDataParamType
{
Field,
File
}
public class PostDataParam
{
public PostDataParam(string name, string value, PostDataParamType type)
{
Name = name;
Value = value;
Type = type;
}
public string Name;
public string FileName;
public string Value;
public PostDataParamType Type;
}
데이터를 보내려면 다음을 수행해야합니다.
HttpWebRequest oRequest = null;
oRequest = (HttpWebRequest)HttpWebRequest.Create(oURL.URL);
oRequest.ContentType = "multipart/form-data";
oRequest.Method = "POST";
PostData pData = new PostData();
byte[] buffer = encoding.GetBytes(pData.GetPostData());
// Set content length of our data
oRequest.ContentLength = buffer.Length;
// Dump our buffered postdata to the stream, booyah
oStream = oRequest.GetRequestStream();
oStream.Write(buffer, 0, buffer.Length);
oStream.Close();
// get the response
oResponse = (HttpWebResponse)oRequest.GetResponse();
희망이 분명합니다. 나는 그 깔끔한 소스를 몇 가지 소스에서 자르고 붙여 넣었습니다.
다른 팁
답변 해 주셔서 감사합니다. 나는 최근에 이것을 작동시켜야했고 당신의 제안을 많이 사용했습니다. 그러나 예상대로 작동하지 않는 몇 가지 까다로운 부분이 있었으며, 실제로는 실제로 파일을 포함하여 (질문의 중요한 부분)와 관련이있었습니다. 여기에는 이미 많은 답이 있지만, 이것이 미래의 누군가에게 유용 할 수 있다고 생각합니다 (온라인에 대한 명확한 예를 많이 찾을 수 없었습니다). 나 블로그 게시물을 썼습니다 그것은 조금 더 설명합니다.
기본적으로 파일 데이터를 UTF8 인코딩 된 문자열로 전달하려고했지만 파일 인코딩에 문제가있었습니다 (예 : 저장하려고 시도한 경우 Word 문서를 업로드 할 때는 파일을 인코딩하는 데 문제가있었습니다. request.files.files [0] .seaveas ()를 사용하여 게시 된 양식으로 전달 된 파일은 Word에서 파일을 열면 제대로 작동하지 않습니다. StringBuilder가 아닌 스트림을 사용하여 파일 데이터를 직접 작성하면 ), 그것은 예상대로 작동했습니다. 또한, 나는 몇 가지 수정을 통해 이해하기 쉽게 만들었습니다.
그건 그렇고, 멀티 파트 양식 의견 요청 그리고 Mulitpart/Form-Data에 대한 W3C 권장 사항 누구나 사양에 대한 참조가 필요한 경우 유용한 리소스 몇 가지입니다.
WebHelpers 클래스를 조금 더 작게 변경하고 인터페이스가 더 간단한 것으로 변경되었습니다. FormUpload
. 당신이 통과하는 경우 FormUpload.FileParameter
파일 이름 및 컨텐츠 유형과 함께 바이트 [] 내용을 전달할 수 있으며 문자열을 전달하면 표준 이름/값 조합으로 취급됩니다.
다음은 Formupload 클래스입니다.
// Implements multipart/form-data POST in C# http://www.ietf.org/rfc/rfc2388.txt
// http://www.briangrinstead.com/blog/multipart-form-post-in-c
public static class FormUpload
{
private static readonly Encoding encoding = Encoding.UTF8;
public static HttpWebResponse MultipartFormDataPost(string postUrl, string userAgent, Dictionary<string, object> postParameters)
{
string formDataBoundary = String.Format("----------{0:N}", Guid.NewGuid());
string contentType = "multipart/form-data; boundary=" + formDataBoundary;
byte[] formData = GetMultipartFormData(postParameters, formDataBoundary);
return PostForm(postUrl, userAgent, contentType, formData);
}
private static HttpWebResponse PostForm(string postUrl, string userAgent, string contentType, byte[] formData)
{
HttpWebRequest request = WebRequest.Create(postUrl) as HttpWebRequest;
if (request == null)
{
throw new NullReferenceException("request is not a http request");
}
// Set up the request properties.
request.Method = "POST";
request.ContentType = contentType;
request.UserAgent = userAgent;
request.CookieContainer = new CookieContainer();
request.ContentLength = formData.Length;
// You could add authentication here as well if needed:
// request.PreAuthenticate = true;
// request.AuthenticationLevel = System.Net.Security.AuthenticationLevel.MutualAuthRequested;
// request.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(System.Text.Encoding.Default.GetBytes("username" + ":" + "password")));
// Send the form data to the request.
using (Stream requestStream = request.GetRequestStream())
{
requestStream.Write(formData, 0, formData.Length);
requestStream.Close();
}
return request.GetResponse() as HttpWebResponse;
}
private static byte[] GetMultipartFormData(Dictionary<string, object> postParameters, string boundary)
{
Stream formDataStream = new System.IO.MemoryStream();
bool needsCLRF = false;
foreach (var param in postParameters)
{
// Thanks to feedback from commenters, add a CRLF to allow multiple parameters to be added.
// Skip it on the first parameter, add it to subsequent parameters.
if (needsCLRF)
formDataStream.Write(encoding.GetBytes("\r\n"), 0, encoding.GetByteCount("\r\n"));
needsCLRF = true;
if (param.Value is FileParameter)
{
FileParameter fileToUpload = (FileParameter)param.Value;
// Add just the first part of this param, since we will write the file data directly to the Stream
string header = string.Format("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{2}\";\r\nContent-Type: {3}\r\n\r\n",
boundary,
param.Key,
fileToUpload.FileName ?? param.Key,
fileToUpload.ContentType ?? "application/octet-stream");
formDataStream.Write(encoding.GetBytes(header), 0, encoding.GetByteCount(header));
// Write the file data directly to the Stream, rather than serializing it to a string.
formDataStream.Write(fileToUpload.File, 0, fileToUpload.File.Length);
}
else
{
string postData = string.Format("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}",
boundary,
param.Key,
param.Value);
formDataStream.Write(encoding.GetBytes(postData), 0, encoding.GetByteCount(postData));
}
}
// Add the end of the request. Start with a newline
string footer = "\r\n--" + boundary + "--\r\n";
formDataStream.Write(encoding.GetBytes(footer), 0, encoding.GetByteCount(footer));
// Dump the Stream into a byte[]
formDataStream.Position = 0;
byte[] formData = new byte[formDataStream.Length];
formDataStream.Read(formData, 0, formData.Length);
formDataStream.Close();
return formData;
}
public class FileParameter
{
public byte[] File { get; set; }
public string FileName { get; set; }
public string ContentType { get; set; }
public FileParameter(byte[] file) : this(file, null) { }
public FileParameter(byte[] file, string filename) : this(file, filename, null) { }
public FileParameter(byte[] file, string filename, string contenttype)
{
File = file;
FileName = filename;
ContentType = contenttype;
}
}
}
다음은 파일과 몇 가지 일반적인 게시물 매개 변수를 업로드하는 호출 코드입니다.
// Read file data
FileStream fs = new FileStream("c:\\people.doc", FileMode.Open, FileAccess.Read);
byte[] data = new byte[fs.Length];
fs.Read(data, 0, data.Length);
fs.Close();
// Generate post objects
Dictionary<string, object> postParameters = new Dictionary<string, object>();
postParameters.Add("filename", "People.doc");
postParameters.Add("fileformat", "doc");
postParameters.Add("file", new FormUpload.FileParameter(data, "People.doc", "application/msword"));
// Create request and receive response
string postURL = "http://localhost";
string userAgent = "Someone";
HttpWebResponse webResponse = FormUpload.MultipartFormDataPost(postURL, userAgent, postParameters);
// Process response
StreamReader responseReader = new StreamReader(webResponse.GetResponseStream());
string fullResponse = responseReader.ReadToEnd();
webResponse.Close();
Response.Write(fullResponse);
.NET 4.5를 사용하면 현재 System.net.http 네임 스페이스를 사용할 수 있습니다. Multipart Form Data를 사용하여 단일 파일을 업로드하기위한 예 아래에 있습니다.
using System;
using System.IO;
using System.Net.Http;
namespace HttpClientTest
{
class Program
{
static void Main(string[] args)
{
var client = new HttpClient();
var content = new MultipartFormDataContent();
content.Add(new StreamContent(File.Open("../../Image1.png", FileMode.Open)), "Image", "Image.png");
content.Add(new StringContent("Place string content here"), "Content-Id in the HTTP");
var result = client.PostAsync("https://hostname/api/Account/UploadAvatar", content);
Console.WriteLine(result.Result.ToString());
}
}
}
Dnolans 예를 바탕으로, 이것은 실제로 작동 할 수있는 버전입니다 (경계에 약간의 오류가 있었고 인코딩이 설정되지 않았습니다) :-)
데이터를 보내려면 :
HttpWebRequest oRequest = null;
oRequest = (HttpWebRequest)HttpWebRequest.Create("http://you.url.here");
oRequest.ContentType = "multipart/form-data; boundary=" + PostData.boundary;
oRequest.Method = "POST";
PostData pData = new PostData();
Encoding encoding = Encoding.UTF8;
Stream oStream = null;
/* ... set the parameters, read files, etc. IE:
pData.Params.Add(new PostDataParam("email", "example@example.com", PostDataParamType.Field));
pData.Params.Add(new PostDataParam("fileupload", "filename.txt", "filecontents" PostDataParamType.File));
*/
byte[] buffer = encoding.GetBytes(pData.GetPostData());
oRequest.ContentLength = buffer.Length;
oStream = oRequest.GetRequestStream();
oStream.Write(buffer, 0, buffer.Length);
oStream.Close();
HttpWebResponse oResponse = (HttpWebResponse)oRequest.GetResponse();
사후 데이터는 다음과 같아야합니다.
public class PostData
{
// Change this if you need to, not necessary
public static string boundary = "AaB03x";
private List<PostDataParam> m_Params;
public List<PostDataParam> Params
{
get { return m_Params; }
set { m_Params = value; }
}
public PostData()
{
m_Params = new List<PostDataParam>();
}
/// <summary>
/// Returns the parameters array formatted for multi-part/form data
/// </summary>
/// <returns></returns>
public string GetPostData()
{
StringBuilder sb = new StringBuilder();
foreach (PostDataParam p in m_Params)
{
sb.AppendLine("--" + boundary);
if (p.Type == PostDataParamType.File)
{
sb.AppendLine(string.Format("Content-Disposition: file; name=\"{0}\"; filename=\"{1}\"", p.Name, p.FileName));
sb.AppendLine("Content-Type: application/octet-stream");
sb.AppendLine();
sb.AppendLine(p.Value);
}
else
{
sb.AppendLine(string.Format("Content-Disposition: form-data; name=\"{0}\"", p.Name));
sb.AppendLine();
sb.AppendLine(p.Value);
}
}
sb.AppendLine("--" + boundary + "--");
return sb.ToString();
}
}
public enum PostDataParamType
{
Field,
File
}
public class PostDataParam
{
public PostDataParam(string name, string value, PostDataParamType type)
{
Name = name;
Value = value;
Type = type;
}
public PostDataParam(string name, string filename, string value, PostDataParamType type)
{
Name = name;
Value = value;
FileName = filename;
Type = type;
}
public string Name;
public string FileName;
public string Value;
public PostDataParamType Type;
}
.NET 버전에서 나는 당신을 사용하고 있습니다. 또한 다음을 수행해야합니다.
System.Net.ServicePointManager.Expect100Continue = false;
그렇지 않으면 HttpWebRequest
클래스는 자동으로 추가됩니다 Expect:100-continue
모든 것을 파울 한 헤더를 요청하십시오.
또한 나는 당신이 올바른 수의 대시를 가져야하는 어려운 방법을 배웠습니다. 당신이 말하는 것은 Content-Type
헤더에는 두 개의 대시가 선행되어야합니다
--THEBOUNDARY
그리고 결국
--THEBOUNDARY--
예제 코드에서와 같이 정확히. 경계가 많은 대시와 숫자가 많은 경우 프록시 서버에서 HTTP 요청을 보면이 실수가 분명하지 않습니다.
코드에 감사드립니다. 제외한 오류 포함!).
어쨌든, 나는 코드에서 버그를 찾았습니다.
formDataStream.Write(encoding.GetBytes(postData), 0, postData.Length);
게시물 데이터가 UTF-16 인 경우, postdata.length는 바이트 수가 아닌 문자 수를 반환합니다. 이것은 게시되는 데이터를 자르게됩니다 (예 : UTF-16으로 인코딩 된 2 개의 숯이있는 경우 4 바이트가 필요하지만 사후 데이터가 필요하면 2 바이트가 필요하다고 말하면 게시 된 2 개의 최종 바이트가 느슨해집니다. 데이터).
솔루션 - 해당 라인을 다음과 같이 바꿉니다.
byte[] aPostData=encoding.GetBytes(postData);
formDataStream.Write(aPostData, 0, aPostData.Length);
이것을 사용하여 길이는 문자열 크기가 아닌 바이트 []의 크기로 계산됩니다.
전에 수업의 약간의 최적화. 이 버전에서는 파일이 메모리에 완전히로드되지 않습니다.
보안 조언 : 파일에 경계가 포함 된 경우 경계에 대한 점검이 누락되었습니다.
namespace WindowsFormsApplication1
{
public static class FormUpload
{
private static string NewDataBoundary()
{
Random rnd = new Random();
string formDataBoundary = "";
while (formDataBoundary.Length < 15)
{
formDataBoundary = formDataBoundary + rnd.Next();
}
formDataBoundary = formDataBoundary.Substring(0, 15);
formDataBoundary = "-----------------------------" + formDataBoundary;
return formDataBoundary;
}
public static HttpWebResponse MultipartFormDataPost(string postUrl, IEnumerable<Cookie> cookies, Dictionary<string, string> postParameters)
{
string boundary = NewDataBoundary();
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(postUrl);
// Set up the request properties
request.Method = "POST";
request.ContentType = "multipart/form-data; boundary=" + boundary;
request.UserAgent = "PhasDocAgent 1.0";
request.CookieContainer = new CookieContainer();
foreach (var cookie in cookies)
{
request.CookieContainer.Add(cookie);
}
#region WRITING STREAM
using (Stream formDataStream = request.GetRequestStream())
{
foreach (var param in postParameters)
{
if (param.Value.StartsWith("file://"))
{
string filepath = param.Value.Substring(7);
// Add just the first part of this param, since we will write the file data directly to the Stream
string header = string.Format("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{2}\";\r\nContent-Type: {3}\r\n\r\n",
boundary,
param.Key,
Path.GetFileName(filepath) ?? param.Key,
MimeTypes.GetMime(filepath));
formDataStream.Write(Encoding.UTF8.GetBytes(header), 0, header.Length);
// Write the file data directly to the Stream, rather than serializing it to a string.
byte[] buffer = new byte[2048];
FileStream fs = new FileStream(filepath, FileMode.Open);
for (int i = 0; i < fs.Length; )
{
int k = fs.Read(buffer, 0, buffer.Length);
if (k > 0)
{
formDataStream.Write(buffer, 0, k);
}
i = i + k;
}
fs.Close();
}
else
{
string postData = string.Format("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}\r\n",
boundary,
param.Key,
param.Value);
formDataStream.Write(Encoding.UTF8.GetBytes(postData), 0, postData.Length);
}
}
// Add the end of the request
byte[] footer = Encoding.UTF8.GetBytes("\r\n--" + boundary + "--\r\n");
formDataStream.Write(footer, 0, footer.Length);
request.ContentLength = formDataStream.Length;
formDataStream.Close();
}
#endregion
return request.GetResponse() as HttpWebResponse;
}
}
}
로그인 쿠키를 얻으려면 웹 사이트에 브라우저 로그인을 시뮬레이션해야했으며 로그인 양식은 Multipart/Form-Data였습니다.
나는 여기서 다른 답변에서 몇 가지 단서를 얻은 다음 내 시나리오를 작동 시키려고 노력했습니다. 제대로 작동하기 전에 약간 실망스러운 시행 착오가 필요했지만 코드는 다음과 같습니다.
public static class WebHelpers
{
/// <summary>
/// Post the data as a multipart form
/// </summary>
public static HttpWebResponse MultipartFormDataPost(string postUrl, string userAgent, Dictionary<string, string> values)
{
string formDataBoundary = "---------------------------" + WebHelpers.RandomHexDigits(12);
string contentType = "multipart/form-data; boundary=" + formDataBoundary;
string formData = WebHelpers.MakeMultipartForm(values, formDataBoundary);
return WebHelpers.PostForm(postUrl, userAgent, contentType, formData);
}
/// <summary>
/// Post a form
/// </summary>
public static HttpWebResponse PostForm(string postUrl, string userAgent, string contentType, string formData)
{
HttpWebRequest request = WebRequest.Create(postUrl) as HttpWebRequest;
if (request == null)
{
throw new NullReferenceException("request is not a http request");
}
// Add these, as we're doing a POST
request.Method = "POST";
request.ContentType = contentType;
request.UserAgent = userAgent;
request.CookieContainer = new CookieContainer();
// We need to count how many bytes we're sending.
byte[] postBytes = Encoding.UTF8.GetBytes(formData);
request.ContentLength = postBytes.Length;
using (Stream requestStream = request.GetRequestStream())
{
// Push it out there
requestStream.Write(postBytes, 0, postBytes.Length);
requestStream.Close();
}
return request.GetResponse() as HttpWebResponse;
}
/// <summary>
/// Generate random hex digits
/// </summary>
public static string RandomHexDigits(int count)
{
Random random = new Random();
StringBuilder result = new StringBuilder();
for (int i = 0; i < count; i++)
{
int digit = random.Next(16);
result.AppendFormat("{0:x}", digit);
}
return result.ToString();
}
/// <summary>
/// Turn the key and value pairs into a multipart form
/// </summary>
private static string MakeMultipartForm(Dictionary<string, string> values, string boundary)
{
StringBuilder sb = new StringBuilder();
foreach (var pair in values)
{
sb.AppendFormat("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}\r\n", boundary, pair.Key, pair.Value);
}
sb.AppendFormat("--{0}--\r\n", boundary);
return sb.ToString();
}
}
}
파일 데이터를 처리하지 않고 제가 필요한 전부이므로 양식 만 양식합니다. 나는 다음과 같이 불렀다 :
try
{
using (HttpWebResponse response = WebHelpers.MultipartFormDataPost(postUrl, UserAgentString, this.loginForm))
{
if (response != null)
{
Cookie loginCookie = response.Cookies["logincookie"];
.....
아래는 내가 사용하는 코드입니다
//This URL not exist, it's only an example.
string url = "http://myBox.s3.amazonaws.com/";
//Instantiate new CustomWebRequest class
CustomWebRequest wr = new CustomWebRequest(url);
//Set values for parameters
wr.ParamsCollection.Add(new ParamsStruct("key", "${filename}"));
wr.ParamsCollection.Add(new ParamsStruct("acl", "public-read"));
wr.ParamsCollection.Add(new ParamsStruct("success_action_redirect", "http://www.yahoo.com"));
wr.ParamsCollection.Add(new ParamsStruct("x-amz-meta-uuid", "14365123651274"));
wr.ParamsCollection.Add(new ParamsStruct("x-amz-meta-tag", ""));
wr.ParamsCollection.Add(new ParamsStruct("AWSAccessKeyId", "zzzz"));
wr.ParamsCollection.Add(new ParamsStruct("Policy", "adsfadsf"));
wr.ParamsCollection.Add(new ParamsStruct("Signature", "hH6lK6cA="));
//For file type, send the inputstream of selected file
StreamReader sr = new StreamReader(@"file.txt");
wr.ParamsCollection.Add(new ParamsStruct("file", sr, ParamsStruct.ParamType.File, "file.txt"));
wr.PostData();
다음 링크에서 동일한 코드를 다운로드했습니다http://www.codeproject.com/kb/cs/multipart_request_c_.aspx
모든 도움
내 구현
/// <summary>
/// Sending file via multipart\form-data
/// </summary>
/// <param name="url">URL for send</param>
/// <param name="file">Local file path</param>
/// <param name="paramName">Request file param</param>
/// <param name="contentType">Content-Type file headr</param>
/// <param name="nvc">Additional post params</param>
private static string httpUploadFile(string url, string file, string paramName, string contentType, NameValueCollection nvc)
{
//delimeter
var boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");
//creating request
var wr = (HttpWebRequest)WebRequest.Create(url);
wr.ContentType = "multipart/form-data; boundary=" + boundary;
wr.Method = "POST";
wr.KeepAlive = true;
//sending request
using(var requestStream = wr.GetRequestStream())
{
using (var requestWriter = new StreamWriter(requestStream, Encoding.UTF8))
{
//params
const string formdataTemplate = "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}";
foreach (string key in nvc.Keys)
{
requestWriter.Write(boundary);
requestWriter.Write(String.Format(formdataTemplate, key, nvc[key]));
}
requestWriter.Write(boundary);
//file header
const string headerTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: {2}\r\n\r\n";
requestWriter.Write(String.Format(headerTemplate, paramName, file, contentType));
//file content
using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read))
{
fileStream.CopyTo(requestStream);
}
requestWriter.Write("\r\n--" + boundary + "--\r\n");
}
}
//reading response
try
{
using (var wresp = (HttpWebResponse)wr.GetResponse())
{
if (wresp.StatusCode == HttpStatusCode.OK)
{
using (var responseStream = wresp.GetResponseStream())
{
if (responseStream == null)
return null;
using (var responseReader = new StreamReader(responseStream))
{
return responseReader.ReadToEnd();
}
}
}
throw new ApplicationException("Error while upload files. Server status code: " + wresp.StatusCode.ToString());
}
}
catch (Exception ex)
{
throw new ApplicationException("Error while uploading file", ex);
}
}