J'essaie de remplir un formulaire dans une application php à partir d'un client C # (complément Outlook). J'ai utilisé Fiddler pour voir la requête initiale depuis l'application php et le formulaire est transmis sous forme de multipart/form. Malheureusement, .Net n’a pas de support natif pour ce type de formulaire (WebClient n’a qu’une méthode de téléchargement de fichier) .. .. Quelqu'un connaît-il une bibliothèque ou dispose-t-il d’un code permettant d’y parvenir? Je veux poster différentes valeurs et en plus (mais seulement parfois) un fichier.
Merci pour votre aide, Sebastian
Ceci est coupé et collé à partir d'un exemple de code que j'ai écrit, espérons qu'il devrait en donner les bases. Il ne prend en charge que les données de fichier et les données de formulaire pour le moment.
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;
}
Pour envoyer les données, vous devez ensuite:
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();
J'espère que c'est clair, j'ai coupé et collé à partir de quelques sources pour obtenir cette rangée.
Merci pour les réponses, tout le monde! J'ai récemment dû faire en sorte que cela fonctionne et j'ai fortement utilisé vos suggestions. Cependant, il y avait quelques points délicats qui ne fonctionnaient pas comme prévu, principalement liés à l'inclusion du fichier (qui était une partie importante de la question). Il y a déjà beaucoup de réponses ici, mais je pense que cela pourrait être utile à quelqu'un à l'avenir (je n'ai pas trouvé beaucoup d'exemples clairs de cela en ligne). Je a écrit un billet de blog ça l'explique un peu plus.
Fondamentalement, j’ai d’abord essayé de transmettre les données du fichier sous forme de chaîne codée UTF8, mais j’avais des problèmes d’encodage des fichiers (cela fonctionnait bien pour un fichier texte brut, mais lors du téléchargement d’un document Word, par exemple, si j’essayais de le sauvegarder. le fichier qui a été transmis au formulaire posté à l'aide de Request.Files [0] .SaveAs (), l'ouverture du fichier dans Word ne fonctionnait pas correctement. J'ai constaté que si vous écrivez les données du fichier directement à l'aide d'un Stream (plutôt que de StringBuilder ), cela a fonctionné comme prévu.J'ai aussi apporté quelques modifications qui ont facilité ma compréhension.
En passant, = Demande de commentaires sur les formulaires multipartites et le Recommandation du W3C pour mulitpart/form-data sont quelques ressources utiles au cas où quelqu'un aurait besoin d'une référence pour la spécification.
J'ai modifié la classe WebHelpers afin qu'elle soit un peu plus petite et que ses interfaces soient plus simples. Elle s'appelle désormais FormUpload
. Si vous transmettez un FormUpload.FileParameter
, vous pouvez transmettre le contenu de l'octet [] avec un nom de fichier et un type de contenu. Si vous transmettez une chaîne, elle sera traitée comme une combinaison nom/valeur standard.
Voici la classe 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;
}
}
}
Voici le code d'appel, qui télécharge un fichier et quelques paramètres de publication normaux:
// 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);
Avec .NET 4.5, vous pouvez actuellement utiliser l’espace de noms System.Net.Http. Ci-dessous l'exemple de téléchargement d'un fichier unique à l'aide de données de formulaire en plusieurs parties.
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());
}
}
}
En s’appuyant sur l’exemple de dnolans, c’est la version que je pourrais réellement utiliser (il y avait quelques erreurs avec la limite, le codage n’était pas défini) :-)
Pour envoyer les données:
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", "[email protected]", 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();
La classe PostData devrait ressembler à ceci:
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;
}
Dans la version de .NET que j'utilise, vous devez également procéder comme suit:
System.Net.ServicePointManager.Expect100Continue = false;
Si vous ne le faites pas, la classe HttpWebRequest
ajoutera automatiquement l'en-tête de requête Expect:100-continue
qui encrasse tout.
J'ai aussi appris à la dure qu'il fallait avoir le bon nombre de tirets. tout ce que vous dites est la "limite" dans l'en-tête Content-Type
doit être précédé de deux tirets
--THEBOUNDARY
et à la fin
--THEBOUNDARY--
exactement comme dans l'exemple de code. Si votre limite est composée de nombreux tirets suivis d'un nombre, cette erreur ne sera pas évidente si vous examinez la requête http dans un serveur proxy.
Merci pour le code, cela m'a fait gagner beaucoup de temps (y compris l'erreur Except100!).
Quoi qu'il en soit, j'ai trouvé un bug dans le code, ici:
formDataStream.Write(encoding.GetBytes(postData), 0, postData.Length);
Si vos données POST sont au format utf-16, postData.Length, renverront le nombre de caractères et non le nombre d'octets. Cela tronquera les données envoyées (par exemple, si vous avez 2 caractères encodés en tant que utf-16, ils prennent 4 octets, mais postData.Length indiquera que cela prend 2 octets et que vous perdez les 2 derniers octets de l'élément Les données).
Solution - remplacez cette ligne par:
byte[] aPostData=encoding.GetBytes(postData);
formDataStream.Write(aPostData, 0, aPostData.Length);
En utilisant cela, la longueur est calculée par la taille de l'octet [], pas par la taille de la chaîne.
Une petite optimisation de la classe avant . Dans cette version, les fichiers ne sont pas totalement chargés en mémoire.
Conseil de sécurité: il manque une vérification de la limite. Si le fichier contient la limite, il se plantera.
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;
}
}
}
Ci-dessous le code que j'utilise
//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();
À partir du lien suivant, j'ai téléchargé le même code http://www.codeproject.com/KB/cs/multipart_request_C_.aspx
De l'aide
Je devais simuler une connexion de navigateur à un site Web pour obtenir un cookie de connexion, et le formulaire de connexion était multipart/form-data.
J'ai pris quelques indices des autres réponses ici, puis j'ai essayé de faire fonctionner mon propre scénario. Il a fallu un peu d’essais et d’erreurs frustrants avant que cela fonctionne, mais voici le code:
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();
}
}
}
Il ne gère pas les données de fichier, mais forme, car c'est tout ce dont j'avais besoin. J'ai appelé comme ça:
try
{
using (HttpWebResponse response = WebHelpers.MultipartFormDataPost(postUrl, UserAgentString, this.loginForm))
{
if (response != null)
{
Cookie loginCookie = response.Cookies["logincookie"];
.....
Ma mise en oeuvre
/// <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);
}
}