Je poste un fichier sur un service WCF REST via un formulaire HTML, avec enctype
défini sur multipart/form-data
et un seul composant: <input type="file" name="data">
. Le flux résultant en cours de lecture par le serveur contient les éléments suivants:
------WebKitFormBoundary
Content-Disposition: form-data; name="data"; filename="DSCF0001.JPG"
Content-Type: image/jpeg
<file bytes>
------WebKitFormBoundary--
Le problème est que je ne sais pas comment extraire les octets du fichier à partir du flux. Je dois le faire pour écrire le fichier sur le disque.
Vous pouvez consulter l'article suivant sur le blog , qui illustre une technique permettant d'analyser multipart/form-data
sur le serveur à l'aide de l'analyseur Multipart :
public void Upload(Stream stream)
{
MultipartParser parser = new MultipartParser(stream);
if (parser.Success)
{
// Save the file
SaveFile(parser.Filename, parser.ContentType, parser.FileContents);
}
}
Une autre possibilité est d'activer la compatibilité aspnet et d'utiliser HttpContext.Current.Request
, mais ce n'est pas une méthode très WCFish.
Désolé de vous être joint à la soirée en retard, mais il existe un moyen de le faire avec API publique Microsoft.
Voici ce dont vous avez besoin:
System.Net.Http.dll
System.Net.Http.Formatting.dll
Note Les paquets Nuget sont livrés avec plus d'assemblages, mais au moment de l'écriture, vous n'avez besoin que de ce qui précède.
Une fois que vous avez référencé les assemblys, le code peut ressembler à ceci (en utilisant .NET 4.5 pour plus de commodité):
public static async Task ParseFiles(
Stream data, string contentType, Action<string, Stream> fileProcessor)
{
var streamContent = new StreamContent(data);
streamContent.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType);
var provider = await streamContent.ReadAsMultipartAsync();
foreach (var httpContent in provider.Contents)
{
var fileName = httpContent.Headers.ContentDisposition.FileName;
if (string.IsNullOrWhiteSpace(fileName))
{
continue;
}
using (Stream fileContents = await httpContent.ReadAsStreamAsync())
{
fileProcessor(fileName, fileContents);
}
}
}
En ce qui concerne l'utilisation, supposons que vous utilisiez la méthode WCF REST suivante:
[OperationContract]
[WebInvoke(Method = WebRequestMethods.Http.Post, UriTemplate = "/Upload")]
void Upload(Stream data);
Vous pouvez le mettre en œuvre comme si
public void Upload(Stream data)
{
MultipartParser.ParseFiles(
data,
WebOperationContext.Current.IncomingRequest.ContentType,
MyProcessMethod);
}
J'ai eu quelques problèmes avec l'analyseur basé sur l'analyse des chaînes, en particulier avec les gros fichiers.
Pour faire face à ces problèmes, j'ai ouvert la source de ma propre tentative d'un analyseur C # multipart/form-data ici
Caractéristiques:
Restrictions
Utilisez simplement la classe MultipartFormDataParser comme ceci:
Stream data = GetTheStream();
// Boundary is auto-detected but can also be specified.
var parser = new MultipartFormDataParser(data, Encoding.UTF8);
// The stream is parsed, if it failed it will throw an exception. Now we can use
// your data!
// The key of these maps corresponds to the name field in your
// form
string username = parser.Parameters["username"].Data;
string password = parser.Parameters["password"].Data
// Single file access:
var file = parser.Files.First();
string filename = file.FileName;
Stream data = file.Data;
// Multi-file access
foreach(var f in parser.Files)
{
// Do stuff with each file.
}
Dans le contexte d'un service WCF, vous pouvez l'utiliser comme ceci:
public ResponseClass MyMethod(Stream multipartData)
{
// First we need to get the boundary from the header, this is sent
// with the HTTP request. We can do that in WCF using the WebOperationConext:
var type = WebOperationContext.Current.IncomingRequest.Headers["Content-Type"];
// Now we want to strip the boundary out of the Content-Type, currently the string
// looks like: "multipart/form-data; boundary=---------------------124123qase124"
var boundary = type.Substring(type.IndexOf('=')+1);
// Now that we've got the boundary we can parse our multipart and use it as normal
var parser = new MultipartFormDataParser(data, boundary, Encoding.UTF8);
...
}
Ou comme ceci (légèrement plus lent mais plus favorable au code):
public ResponseClass MyMethod(Stream multipartData)
{
var parser = new MultipartFormDataParser(data, Encoding.UTF8);
}
La documentation est également disponible. Lorsque vous clonez le référentiel, accédez simplement à HttpMultipartParserDocumentation/Help/index.html
.
J'ai ouvert-source un analyseur de formulaire C # Http ici .
Ceci est légèrement plus flexible que l’autre mentionné qui est sur CodePlex, puisque vous pouvez l’utiliser pour form-data
multipart et non multipart, et vous donne également d’autres paramètres de formulaire formatés dans un objet Dictionary
.
Ceci peut être utilisé comme suit:
non-multipart
public void Login(Stream stream)
{
string username = null;
string password = null;
HttpContentParser parser = new HttpContentParser(stream);
if (parser.Success)
{
username = HttpUtility.UrlDecode(parser.Parameters["username"]);
password = HttpUtility.UrlDecode(parser.Parameters["password"]);
}
}
multipart
public void Upload(Stream stream)
{
HttpMultipartParser parser = new HttpMultipartParser(stream, "image");
if (parser.Success)
{
string user = HttpUtility.UrlDecode(parser.Parameters["user"]);
string title = HttpUtility.UrlDecode(parser.Parameters["title"]);
// Save the file somewhere
File.WriteAllBytes(FILE_PATH + title + FILE_EXT, parser.FileContents);
}
}
Une autre solution consiste à utiliser un analyseur .Net pour HttpRequest. Pour ce faire, vous devez utiliser un peu de réflexion et une classe simple pour WorkerRequest.
Commencez par créer une classe dérivée de HttpWorkerRequest (pour simplifier, vous pouvez utiliser SimpleWorkerRequest):
public class MyWorkerRequest : SimpleWorkerRequest
{
private readonly string _size;
private readonly Stream _data;
private string _contentType;
public MyWorkerRequest(Stream data, string size, string contentType)
: base("/app", @"c:\", "aa", "", null)
{
_size = size ?? data.Length.ToString(CultureInfo.InvariantCulture);
_data = data;
_contentType = contentType;
}
public override string GetKnownRequestHeader(int index)
{
switch (index)
{
case (int)HttpRequestHeader.ContentLength:
return _size;
case (int)HttpRequestHeader.ContentType:
return _contentType;
}
return base.GetKnownRequestHeader(index);
}
public override int ReadEntityBody(byte[] buffer, int offset, int size)
{
return _data.Read(buffer, offset, size);
}
public override int ReadEntityBody(byte[] buffer, int size)
{
return ReadEntityBody(buffer, 0, size);
}
}
Ensuite, où que vous soyez, vous créez un flux de messages et une instance de cette classe. Je le fais comme ça dans WCF Service:
[WebInvoke(Method = "POST",
ResponseFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Bare)]
public string Upload(Stream data)
{
HttpWorkerRequest workerRequest =
new MyWorkerRequest(data,
WebOperationContext.Current.IncomingRequest.ContentLength.
ToString(CultureInfo.InvariantCulture),
WebOperationContext.Current.IncomingRequest.ContentType
);
Et créez ensuite HttpRequest à l'aide d'un activateur et d'un constructeur non public
var r = (HttpRequest)Activator.CreateInstance(
typeof(HttpRequest),
BindingFlags.Instance | BindingFlags.NonPublic,
null,
new object[]
{
workerRequest,
new HttpContext(workerRequest)
},
null);
var runtimeField = typeof (HttpRuntime).GetField("_theRuntime", BindingFlags.Static | BindingFlags.NonPublic);
if (runtimeField == null)
{
return;
}
var runtime = (HttpRuntime) runtimeField.GetValue(null);
if (runtime == null)
{
return;
}
var codeGenDirField = typeof(HttpRuntime).GetField("_codegenDir", BindingFlags.Instance | BindingFlags.NonPublic);
if (codeGenDirField == null)
{
return;
}
codeGenDirField.SetValue(runtime, @"C:\MultipartTemp");
Après cela, dans r.Files
, vous aurez des fichiers de votre flux.
Que diriez-vous de Regex?
J'ai écrit ceci pour un texte dans un fichier, mais je crois que cela pourrait fonctionner pour vous
(Si votre fichier texte contient des lignes commençant exactement par celles "correspondantes" ci-dessous, adaptez simplement votre regex)
private static List<string> fileUploadRequestParser(Stream stream)
{
//-----------------------------111111111111111
//Content-Disposition: form-data; name="file"; filename="data.txt"
//Content-Type: text/plain
//...
//...
//-----------------------------111111111111111
//Content-Disposition: form-data; name="submit"
//Submit
//-----------------------------111111111111111--
List<String> lstLines = new List<string>();
TextReader textReader = new StreamReader(stream);
string sLine = textReader.ReadLine();
Regex regex = new Regex("(^-+)|(^content-)|(^$)|(^submit)", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline);
while (sLine != null)
{
if (!regex.Match(sLine).Success)
{
lstLines.Add(sLine);
}
sLine = textReader.ReadLine();
}
return lstLines;
}
J'ai implémenté MultipartReader NuGet pour ASP.NET 4 pour la lecture de données de formulaire en plusieurs parties. Il est basé sur Multipart Form Data Parser , mais il prend en charge plusieurs fichiers.
Le gars qui a résolu ce problème l'a affiché comme LGPL et vous n'êtes pas autorisé à le modifier. Je n'ai même pas cliqué dessus quand j'ai vu que… .. Voici ma version. Cela doit être testé. Il y a probablement des bugs. S'il vous plaît poster des mises à jour. Aucune garantie. Vous pouvez modifier cela à votre guise, l'appeler comme vous-même, l'imprimer sur un morceau de papier et l'utiliser comme débris de chenil,.
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Text;
using System.Web;
namespace DigitalBoundaryGroup
{
class HttpNameValueCollection
{
public class File
{
private string _fileName;
public string FileName { get { return _fileName ?? (_fileName = ""); } set { _fileName = value; } }
private string _fileData;
public string FileData { get { return _fileData ?? (_fileName = ""); } set { _fileData = value; } }
private string _contentType;
public string ContentType { get { return _contentType ?? (_contentType = ""); } set { _contentType = value; } }
}
private NameValueCollection _post;
private Dictionary<string, File> _files;
private readonly HttpListenerContext _ctx;
public NameValueCollection Post { get { return _post ?? (_post = new NameValueCollection()); } set { _post = value; } }
public NameValueCollection Get { get { return _ctx.Request.QueryString; } }
public Dictionary<string, File> Files { get { return _files ?? (_files = new Dictionary<string, File>()); } set { _files = value; } }
private void PopulatePostMultiPart(string post_string)
{
var boundary_index = _ctx.Request.ContentType.IndexOf("boundary=") + 9;
var boundary = _ctx.Request.ContentType.Substring(boundary_index, _ctx.Request.ContentType.Length - boundary_index);
var upper_bound = post_string.Length - 4;
if (post_string.Substring(2, boundary.Length) != boundary)
throw (new InvalidDataException());
var current_string = new StringBuilder();
for (var x = 4 + boundary.Length; x < upper_bound; ++x)
{
if (post_string.Substring(x, boundary.Length) == boundary)
{
x += boundary.Length + 1;
var post_variable_string = current_string.Remove(current_string.Length - 4, 4).ToString();
var end_of_header = post_variable_string.IndexOf("\r\n\r\n");
if (end_of_header == -1) throw (new InvalidDataException());
var filename_index = post_variable_string.IndexOf("filename=\"", 0, end_of_header);
var filename_starts = filename_index + 10;
var content_type_starts = post_variable_string.IndexOf("Content-Type: ", 0, end_of_header) + 14;
var name_starts = post_variable_string.IndexOf("name=\"") + 6;
var data_starts = end_of_header + 4;
if (filename_index != -1)
{
var filename = post_variable_string.Substring(filename_starts, post_variable_string.IndexOf("\"", filename_starts) - filename_starts);
var content_type = post_variable_string.Substring(content_type_starts, post_variable_string.IndexOf("\r\n", content_type_starts) - content_type_starts);
var file_data = post_variable_string.Substring(data_starts, post_variable_string.Length - data_starts);
var name = post_variable_string.Substring(name_starts, post_variable_string.IndexOf("\"", name_starts) - name_starts);
Files.Add(name, new File() { FileName = filename, ContentType = content_type, FileData = file_data });
}
else
{
var name = post_variable_string.Substring(name_starts, post_variable_string.IndexOf("\"", name_starts) - name_starts);
var value = post_variable_string.Substring(data_starts, post_variable_string.Length - data_starts);
Post.Add(name, value);
}
current_string.Clear();
continue;
}
current_string.Append(post_string[x]);
}
}
private void PopulatePost()
{
if (_ctx.Request.HttpMethod != "POST" || _ctx.Request.ContentType == null) return;
var post_string = new StreamReader(_ctx.Request.InputStream, _ctx.Request.ContentEncoding).ReadToEnd();
if (_ctx.Request.ContentType.StartsWith("multipart/form-data"))
PopulatePostMultiPart(post_string);
else
Post = HttpUtility.ParseQueryString(post_string);
}
public HttpNameValueCollection(ref HttpListenerContext ctx)
{
_ctx = ctx;
PopulatePost();
}
}
}
J'ai traité WCF avec le téléchargement de fichiers volumineux (plusieurs fichiers), où stocker des données en mémoire n'est pas une option. Ma solution consiste à stocker le flux de messages dans un fichier temporaire et à utiliser find pour connaître le début et la fin des données binaires.