Pytanie Metoda WebAPI, która pobiera plik i dodatkowe argumenty


Chcę przesłać plik i wysłać wraz z plikiem kilka dodatkowych informacji, powiedzmy string foo i int bar.

Jak napisać metodę kontrolera ASP.NET WebAPI, która odbiera plik do przesłania, ciąg znaków i int?

Mój JavaScript:

var fileInput = document.querySelector("#filePicker");
var formData = new FormData();
formData.append("file", fileInput.files[0]);
formData.append("foo", "hello world!");
formData.append("bar", 42);

var options = {
   url: "/api/foo/upload",
   data: formData,
   processData: false // Prevents JQuery from transforming the data into a query string
};
$.ajax(options);

Mój kontroler WebAPI może uzyskać dostęp do pliku w następujący sposób:

public async Task<HttpResponseMessage> Upload()
{
    var streamProvider = new MultipartMemoryStreamProvider();
    await Request.Content.ReadAsMultipartAsync(streamProvider);
    var fileStream = await streamProvider.Contents[0].ReadAsStreamAsync();
}

Ale nie jest dla mnie jasne, w jaki sposób mogę uzyskać mój ciąg i int. Sądzę, że prawdopodobnie mogę powiedzieć streamProvider.Content [1] lub cokolwiek innego, ale to jest bardzo nieprzyjemne.

W jaki sposób napisać działanie WebAPI, które akceptuje przesłanie pliku, ciąg znaków i int?


14
2018-05-19 03:51


pochodzenie


Prawdopodobnie brakuje mi czegoś ... Dlaczego nie możesz po prostu tworzyć parametrów foo i bar w twojej metodzie wysyłania i pozwalać na wiązanie magii? - Scrappydog
Skoncentrowałem się na wysyłaniu danych w FormData (np. Jako dane w $ .post (url, formData), segregator nigdy ich nie znalazł.Okazuje się, mogę wysłać plik jako dane formularza, a następnie umieścić pozostałe argumenty w adresie URL. - Judah Himango


Odpowiedzi:


Możesz stworzyć swój własny MultipartFileStreamProvider aby uzyskać dostęp do dodatkowych argumentów.

W ExecutePostProcessingAsync przechodzimy przez każdy plik w formie wieloczęściowej i ładujemy dane niestandardowe (jeśli masz tylko jeden plik, będziesz miał tylko jeden obiekt w CustomData lista).

class MyCustomData
{
    public int Foo { get; set; }
    public string Bar { get; set; }
}

class CustomMultipartFileStreamProvider : MultipartMemoryStreamProvider
{
    public List<MyCustomData> CustomData { get; set; }

    public CustomMultipartFileStreamProvider()
    {
        CustomData = new List<MyCustomData>();
    }

    public override Task ExecutePostProcessingAsync()
    {
        foreach (var file in Contents)
        {
            var parameters = file.Headers.ContentDisposition.Parameters;
            var data = new MyCustomData
            {
                Foo = int.Parse(GetNameHeaderValue(parameters, "Foo")),
                Bar = GetNameHeaderValue(parameters, "Bar"),
            };

            CustomData.Add(data);
        }

        return base.ExecutePostProcessingAsync();
    }

    private static string GetNameHeaderValue(ICollection<NameValueHeaderValue> headerValues, string name)
    {
        var nameValueHeader = headerValues.FirstOrDefault(
            x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase));

        return nameValueHeader != null ? nameValueHeader.Value : null;
    }
}

Następnie w kontrolerze:

class UploadController : ApiController
{
    public async Task<HttpResponseMessage> Upload()
    {
        var streamProvider = new CustomMultipartFileStreamProvider();
        await Request.Content.ReadAsMultipartAsync(streamProvider);

        var fileStream = await streamProvider.Contents[0].ReadAsStreamAsync();
        var customData = streamProvider.CustomData;

        return Request.CreateResponse(HttpStatusCode.Created);
    }
}

15
2018-05-21 15:16



Dzięki. Skończyłem właśnie przekazywanie argumentów za pomocą "prostych" argumentów, np. $ .post ("/ api / foo? id = 23 & bar = hello", fileData). To działa ... pod warunkiem, że nie przekazujesz złożonych obiektów lub tablic. Bardziej podoba mi się twoje rozwiązanie, więc zaznaczam to jako odpowiedź. - Judah Himango
Próbując tego, otrzymujemy nazwę parametru w części wartości pary klucz / wartość - pole klucza proste zawiera "nazwę". Czy coś się zmieniło od maja? - codeputer


Myślę, że odpowiedzi tutaj są doskonałe. Inni mogą zobaczyć prosty przykład przesyłania danych poza plikiem w formie podsumowania, w tym funkcja JavaScript, która wywołuje WebAPI do kontrolera FileUpload, oraz fragment z kontrolera FileUpload (w VB.net) odczytuje dodatkowe dane przekazane z Javascript.

JavaScript:

            function uploadImage(files) {
            var data = new FormData();
            if (files.length > 0) {
                data.append("UploadedImage", files[0]);
                data.append("Source", "1")
                var ajaxRequest = $.ajax({
                    type: "POST",
                    url: "/api/fileupload/uploadfile",
                    contentType: false,
                    processData: false,
                    data: data
                });

Kontroler przesyłania plików:

        <HttpPost> _
    Public Function UploadFile() As KeyValuePair(Of Boolean, String)
        Try
            If HttpContext.Current.Request.Files.AllKeys.Any() Then
                Dim httpPostedFile = HttpContext.Current.Request.Files("UploadedImage")
                Dim source = HttpContext.Current.Request.Form("Source").ToString()

Jak widać w JavaScript, dodatkowe dane przekazane to klucz "Source", a wartość to "1". I jak Chandrika odpowiedział powyżej, kontroler odczytuje to przekazane dane przez "System.Web.HttpContext.Current.Request.Form (" Source "). ToString ()".

Zauważ, że Form ("Source") używa () (vs. []), ponieważ kod kontrolera znajduje się w VB.net.

Mam nadzieję że to pomoże.


4
2017-10-30 20:24





Możesz to zrobić w następujący sposób: Metoda JQuery:

    var data = new FormData();

    data.append("file", filesToUpload[0].rawFile);
    var doc = {};            
    doc.DocumentId = 0; 
    $.support.cors = true;
    $.ajax({
        url: '/api/document/uploaddocument',
        type: 'POST',
        contentType: 'multipart/form-data',
        data: data,
        cache: false,
        contentType: false,
        processData: false,
        success: function (response) {
            docId = response.split('|')[0];
            doc.DocumentId = docId;
            $.post('/api/document/metadata', doc)
                .done(function (response) {
                });
          alert('Document save successfully!');
        },
        error: function (e) {
            alert(e);
        }
    });

zadzwoń do api web "UploadDocuement"

[Route("api/document/uploaddocument"), HttpPost]
        [UnhandledExceptionFilter]
        [ActionName("UploadDocument")]
        public Task<HttpResponseMessage> UploadDocument()
        {
            // Check if the request contains multipart/form-data.
            if (!Request.Content.IsMimeMultipartContent())
            {
                Task<HttpResponseMessage> mytask = new Task<HttpResponseMessage>(delegate()
                {
                    return new HttpResponseMessage()
                    {
                        StatusCode = HttpStatusCode.BadRequest,
                        Content = "In valid file & request content type!".ToStringContent()
                    };
                });
                return mytask;
            }


            string root = HttpContext.Current.Server.MapPath("~/Documents");
            if (System.IO.Directory.Exists(root))
            {
                System.IO.Directory.CreateDirectory(root);
            }
            var provider = new MultipartFormDataStreamProvider(root);

            var task = Request.Content.ReadAsMultipartAsync(provider).
            ContinueWith<HttpResponseMessage>(o =>
            {
                if (o.IsFaulted || o.IsCanceled)
                    throw new HttpResponseException(HttpStatusCode.InternalServerError);

                FileInfo finfo = new FileInfo(provider.FileData.First().LocalFileName);

                string guid = Guid.NewGuid().ToString();

                File.Move(finfo.FullName, Path.Combine(root, guid + "_" + provider.FileData.First().Headers.ContentDisposition.FileName.Replace("\"", "")));

                string sFileName = provider.FileData.First().Headers.ContentDisposition.FileName.Replace("\"", "");

                FileInfo FInfos = new FileInfo(Path.Combine(root, guid + "_" + provider.FileData.First().Headers.ContentDisposition.FileName.Replace("\"", "")));

                Document dbDoc = new Document()
                {
                    DocumentID = 0                

                };

                context.DocumentRepository.Insert(dbDoc);
                context.Save();

                return new HttpResponseMessage()
                {
                    Content = new StringContent(string.Format("{0}|File uploaded.", dbDoc.DocumentID))
                };
            }
           );
            return task;

        }

Zadzwoń do swojego webowego interfejsu metadanych w następujący sposób:

[Route("api/document/metadata"), HttpPost]
        [ActionName("Metadata")]
        public Task<HttpResponseMessage> Metadata(Document doc)
        {
            int DocId = Convert.ToInt32(System.Web.HttpContext.Current.Request.Form["DocumentId"].ToString());

            Task<HttpResponseMessage> mytask = new Task<HttpResponseMessage>(delegate()
            {
                return new HttpResponseMessage()
                {
                    Content = new StringContent("metadata updated")
                };
            });
            return mytask;
        }

2
2018-05-19 14:07



Tak więc, jeśli dobrze rozumiem, wykonujesz 2 wywołania: jeden dla pliku, drugi dla metadanych (np. "Dodatkowe argumenty" część mojego pytania). Czy istnieje sposób, aby to zrobić w jednym wywołaniu? - Judah Himango
wypróbuj ten w aplecie apletu "UploadDocument" "System.Web.HttpContext.Current.Request.Form [" DocumentId "]. ToString ()" - Chandrika Prajapati
w powyższym przykładzie wykonuję 2 wywołania, jeden dla pliku, a drugi dla metadanych, możesz uzyskać go za pomocą jednego połączenia, tak jak po wypróbowaniu tego w aplecie sieci web UploadDocument "System.Web.HttpContext.Current.Request.Form [" DocumentId " ] .ToString () " - Chandrika Prajapati


W ten sposób można wyodrębnić wiele plików i wiele atrybutów:

public async Task<HttpResponseMessage> Post()
{
    Dictionary<string,string> attributes = new Dictionary<string, string>();
    Dictionary<string, byte[]> files = new Dictionary<string, byte[]>();

    var provider = new MultipartMemoryStreamProvider();
    await Request.Content.ReadAsMultipartAsync(provider);
    foreach (var file in provider.Contents)
    {
        if (file.Headers.ContentDisposition.FileName != null)
        {
            var filename = file.Headers.ContentDisposition.FileName.Trim('\"');
            var buffer = await file.ReadAsByteArrayAsync();
            files.Add(filename, buffer);
        } else
        {
            foreach(NameValueHeaderValue p in file.Headers.ContentDisposition.Parameters)
            {
                string name = p.Value;
                if (name.StartsWith("\"") && name.EndsWith("\"")) name = name.Substring(1, name.Length - 2);
                string value = await file.ReadAsStringAsync();
                attributes.Add(name, value);
            }
        }
    }
    //Your code here  
    return new HttpResponseMessage(HttpStatusCode.OK);
}

2
2018-02-21 12:09