Lecture des entrées de fichier à partir d'un poste multipart/form-data

je poste un fichier dans un service de repos de la WCF au moyen d'un formulaire HTML, avec enctype réglé à multipart/form-data et un seul composant: <input type="file" name="data"> . Le flux résultant étant lu par le serveur contient ce qui suit:

------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 de fichier du flux. Je dois le faire afin d'écrire le fichier sur le disque.

37
demandé sur rafale 2011-09-18 11:21:18

9 réponses

vous pouvez jeter un oeil au post de blog suivant qui illustre une technique qui pourrait être utilisé pour analyser multipart/form-data sur le serveur en utilisant le multipart Parser :

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 compatibilité aspnet et d'utiliser HttpContext.Current.Request mais ce n'est pas une façon très WCFish.

29
répondu Darin Dimitrov 2017-05-23 12:09:28

désolé de rejoindre la partie en retard, mais il y a un moyen de le faire avec Microsoft public API .

voici ce dont vous avez besoin:

  1. System.Net.Http.dll
  2. System.Net.Http.Formatting.dll

Note les paquets Nuget sont livrés avec plus d'assemblages, mais au moment de la rédaction vous n'avez besoin que de ce qui précède.

une fois les assemblages référencés, 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);
        }
    }
}

As pour l'usage, dites que vous avez la méthode de repos WCF suivante:

[OperationContract]
[WebInvoke(Method = WebRequestMethods.Http.Post, UriTemplate = "/Upload")]
void Upload(Stream data);

, Vous pouvez la mettre en œuvre comme

public void Upload(Stream data)
{
    MultipartParser.ParseFiles(
           data, 
           WebOperationContext.Current.IncomingRequest.ContentType, 
           MyProcessMethod);
}
36
répondu Ohad Schneider 2014-02-10 22:36:31

j'ai eu quelques problèmes avec l'analyseur qui sont basés sur l'analyse de chaîne de caractères en particulier avec les grands fichiers, j'ai trouvé qu'il manquerait de mémoire et ne parviendrait pas à analyser les données binaires.

pour faire face À ces questions, je suis open source de ma propre tentative de C# multipart/form-data parser ici

Dispose:

  • gère très bien les fichiers de très grande taille. (Les données sont transmises et diffusées en lisant)
  • Peut gérer plusieurs téléchargements de fichiers et détecte automatiquement si une section est un fichier ou pas.
  • renvoie des fichiers comme un flux et non comme un octet[] (bon pour les gros fichiers).
  • Complet de la documentation pour la bibliothèque, y compris un MSDN de style d'un site web généré.
  • essais à l'unité complète.

Restrictions:

  • ne traite pas les données non multiparties.
  • Le Code
  • est plus compliqué que celui de Lorenzo."

il suffit d'utiliser la classe MultipartFormDataParser comme suit:

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 cadre d'un service de la 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 ça (un peu plus lent, mais plus le code de l'environnement):

public ResponseClass MyMethod(Stream multipartData)
{
    var parser = new MultipartFormDataParser(data, Encoding.UTF8);
}

la Documentation est également disponible, lorsque vous clonez le dépôt tout simplement naviguer à HttpMultipartParserDocumentation/Help/index.html

22
répondu Jake Woods 2013-02-03 22:55:41

je open-source en C# Http formulaire de parser ici .

c'est un peu plus flexible que l'autre mentionné qui est sur CodePlex, puisque vous pouvez l'utiliser à la fois pour Multipart et non-Multipart form-data , et aussi il vous donne d'autres paramètres de forme formaté dans un Dictionary objet.

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);
    }
}
14
répondu Lorenzo Polidori 2014-04-12 13:27:31

une autre façon serait d'utiliser .Net parser pour HttpRequest. Pour ce faire, vous devez utiliser un peu de réflexion et de classe simple pour WorkerRequest.

première classe create qui dérive de HttpWorkerRequest (pour la simplicité, 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 ayez le flux de messages créer et l'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 puis créer HttpRequest en utilisant activator et non public constructor

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.

2
répondu Lukasz Salamon 2014-01-01 17:21:43

le gars qui a résolu ce problème l'a posté comme LGPL et vous n'êtes pas autorisé à le modifier. Je n'ai même pas cliqué dessus quand j'ai vu ça. Voici ma version. Ce doit être testé. Il y a probablement des bugs. Veuillez afficher les mises à jour. Aucune garantie. Vous pouvez modifier ce que vous voulez, appelez votre propre, de l'imprimer sur une feuille de papier et l'utiliser pour chenil de la ferraille, de ... n'avez pas de soins.

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();
        }


    }
}
1
répondu Bluebaron 2012-02-15 19:56:26

j'ai implémenté MultipartReader paquet NuGet pour ASP.NET 4 pour la lecture de données de forme multipart. Il est basé sur multipart form Data Parser , mais il prend en charge plus d'un fichier.

1
répondu Václav Dajbych 2012-12-25 10:51:47

un peu de Regex?

j'ai écrit ce texte, un fichier, mais je crois que cela pourrait fonctionner pour vous

(dans le cas où votre fichier texte contient la ligne commençant exactement avec les "correspondants" ci - dessous-simplement adapter 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;
    }
1
répondu mork 2013-12-16 15:01:45

j'ai traité WCF avec le gros fichier (Server GB) upload où les données de stockage en mémoire n'est pas une option. Ma solution est de stocker le flux de messages à un fichier temporaire et l'utilisation de chercher à trouver le début et la fin des données binaires.

0
répondu Yang Zhang 2013-09-16 05:16:21