Cara Menerima File POST

254

Saya menggunakan asp.net mvc 4 webapi beta untuk membangun layanan lainnya. Saya harus dapat menerima gambar / file POST dari aplikasi klien. Apakah ini mungkin menggunakan webapi? Di bawah ini adalah tindakan yang saya gunakan saat ini. Adakah yang tahu contoh bagaimana ini seharusnya bekerja?

[HttpPost]
public string ProfileImagePost(HttpPostedFile profileImage)
{
    string[] extensions = { ".jpg", ".jpeg", ".gif", ".bmp", ".png" };
    if (!extensions.Any(x => x.Equals(Path.GetExtension(profileImage.FileName.ToLower()), StringComparison.OrdinalIgnoreCase)))
    {
        throw new HttpResponseException("Invalid file type.", HttpStatusCode.BadRequest);
    }

    // Other code goes here

    return "/path/to/image.png";
}
Phil
sumber
3
Itu hanya berfungsi dengan MVC bukan kerangka WebAPI.
Phil
Anda seharusnya dapat mengambil item dariRequest.Files
Tejs
7
ApiController tidak mengandung HttpRequestBase yang memiliki properti Files. Objek Permintaannya didasarkan pada kelas HttpRequestMessage.
Phil

Jawaban:

168

lihat http://www.asp.net/web-api/overview/formats-and-model-binding/html-forms-and-multipart-mime#multipartmime , meskipun saya pikir artikel ini membuatnya tampak sedikit lebih rumit daripada memang benar.

Pada dasarnya,

public Task<HttpResponseMessage> PostFile() 
{ 
    HttpRequestMessage request = this.Request; 
    if (!request.Content.IsMimeMultipartContent()) 
    { 
        throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); 
    } 

    string root = System.Web.HttpContext.Current.Server.MapPath("~/App_Data/uploads"); 
    var provider = new MultipartFormDataStreamProvider(root); 

    var task = request.Content.ReadAsMultipartAsync(provider). 
        ContinueWith<HttpResponseMessage>(o => 
    { 

        string file1 = provider.BodyPartFileNames.First().Value;
        // this is the file name on the server where the file was saved 

        return new HttpResponseMessage() 
        { 
            Content = new StringContent("File uploaded.") 
        }; 
    } 
    ); 
    return task; 
} 
Mike Wasson
sumber
5
Apa manfaat menggunakan Tugas untuk membaca hanya satu file? Pertanyaan asli, saya baru mulai menggunakan Tugas. Dari pemahaman saya saat ini, kode ini sangat cocok untuk ketika mengunggah lebih dari satu file yang benar?
Chris
48
MultipartFormDataStreamProvider tidak memiliki properti BodyPartFileNames lagi (di WebApi RTM). Lihat asp.net/web-api/overview/working-with-http/…
Shrike
5
Guys, dapatkah Anda menjelaskan mengapa kita tidak bisa hanya mengakses file menggunakan HttpContext.Current.Request.Files dan sebaliknya perlu menggunakan MultipartFormDataStreamProvider mewah ini? Pertanyaan lengkap: stackoverflow.com/questions/17967544 .
niaher
7
File sedang disimpan sebagai BodyPart_8b77040b-354b-464c-bc15-b3591f98f30f . Bukankah seharusnya mereka disimpan seperti pic.jpg persis seperti pada klien?
lbrahim
10
MultipartFormDataStreamProvidertidak mengekspos BodyPartFileNamesproperti lagi, saya FileData.First().LocalFileNamemalah menggunakannya .
Chtiwi Malek
374

Saya terkejut bahwa banyak dari Anda tampaknya ingin menyimpan file di server. Solusi untuk menyimpan semuanya dalam memori adalah sebagai berikut:

[HttpPost("api/upload")]
public async Task<IHttpActionResult> Upload()
{
    if (!Request.Content.IsMimeMultipartContent())
        throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); 

    var provider = new MultipartMemoryStreamProvider();
    await Request.Content.ReadAsMultipartAsync(provider);
    foreach (var file in provider.Contents)
    {
        var filename = file.Headers.ContentDisposition.FileName.Trim('\"');
        var buffer = await file.ReadAsByteArrayAsync();
        //Do whatever you want with filename and its binary data.
    }

    return Ok();
}
Gleno
sumber
34
Menyimpan file dalam memori dapat berguna jika Anda tidak ingin menghabiskan ruang disk. Namun, jika Anda mengizinkan file besar untuk diunggah maka menyimpannya dalam memori berarti server web Anda akan menggunakan banyak memori, yang tidak bisa dihabiskan untuk menyimpan barang-barang untuk permintaan lainnya. Ini akan menyebabkan masalah pada server yang bekerja di bawah beban tinggi.
Willem Meints
21
@ W. Salam Saya mengerti alasan ingin menyimpan data, tetapi saya tidak mengerti mengapa ada orang yang ingin menyimpan data yang diunggah pada ruang disk server. Anda harus selalu menjaga agar penyimpanan file tetap terisolasi dari server web - bahkan untuk proyek yang lebih kecil.
Gleno
1
Pastikan ukuran file Anda yang diposting kurang dari 64k, perilaku default adalah mengabaikan permintaan sebaliknya, saya terjebak pada ini untuk waktu log.
Gary Davies
3
Sayangnya, MultipartMemoryStreamProvider tidak membantu jika Anda juga ingin membaca data formulir. Ingin membuat sesuatu seperti MultipartFormDataMemoryStreamProvider tetapi begitu banyak kelas dan kelas pembantu adalah internal di aspnetwebstack :(
martinoss
9
File.WriteAllBytes(filename, buffer);untuk menulisnya ke file
pomber
118

Lihat kode di bawah, diadaptasi dari artikel ini , yang menunjukkan contoh kode paling sederhana yang bisa saya temukan. Ini mencakup unggahan file dan memori (lebih cepat).

public HttpResponseMessage Post()
{
    var httpRequest = HttpContext.Current.Request;
    if (httpRequest.Files.Count < 1)
    {
        return Request.CreateResponse(HttpStatusCode.BadRequest);
    }

    foreach(string file in httpRequest.Files)
    {
        var postedFile = httpRequest.Files[file];
        var filePath = HttpContext.Current.Server.MapPath("~/" + postedFile.FileName);
        postedFile.SaveAs(filePath);
        // NOTE: To store in memory use postedFile.InputStream
    }

    return Request.CreateResponse(HttpStatusCode.Created);
}
Brent Matzelle
sumber
26
HttpContext.Current adalah null ketika WebAPI di-host di OWIN yang merupakan wadah hosting sendiri.
Zach
1
Memperbaikinya seperti ini: var httpRequest = System.Web.HttpContext.Current.Request;
msysmilu
7
Jangan gunakan System.Web di WebAPI kecuali Anda benar-benar harus.
Kugel
3
Tentu saja, System.Web sangat erat dengan IIS. Jika Anda bekerja di dalam OWIN piple line atau .Net Core, API tersebut tidak akan tersedia saat berjalan di linux atau di-hosting-sendiri.
Kugel
2
Jawaban yang bagus Hanya satu detail: jika Anda mengunggah dari halaman HTML, tag <input type = "file" /> harus memiliki atribut "name", atau file tidak akan ada di HttpContext.Current.Request.Files.
GBU
17

Cara ASP.NET Core sekarang ada di sini :

[HttpPost("UploadFiles")]
public async Task<IActionResult> Post(List<IFormFile> files)
{
    long size = files.Sum(f => f.Length);

    // full path to file in temp location
    var filePath = Path.GetTempFileName();

    foreach (var formFile in files)
    {
        if (formFile.Length > 0)
        {
            using (var stream = new FileStream(filePath, FileMode.Create))
            {
                await formFile.CopyToAsync(stream);
            }
        }
    }

    // process uploaded files
    // Don't rely on or trust the FileName property without validation.

    return Ok(new { count = files.Count, size, filePath});
}
Matt Frear
sumber
16

Ini adalah solusi cepat dan kotor yang mengambil konten file yang diunggah dari tubuh HTTP dan menulisnya ke file. Saya menyertakan cuplikan HTML / JS "telanjang tulang" untuk unggahan file.

Metode API Web:

[Route("api/myfileupload")]        
[HttpPost]
public string MyFileUpload()
{
    var request = HttpContext.Current.Request;
    var filePath = "C:\\temp\\" + request.Headers["filename"];
    using (var fs = new System.IO.FileStream(filePath, System.IO.FileMode.Create))
    {
        request.InputStream.CopyTo(fs);
    }
    return "uploaded";
}

Unggah File HTML:

<form>
    <input type="file" id="myfile"/>  
    <input type="button" onclick="uploadFile();" value="Upload" />
</form>
<script type="text/javascript">
    function uploadFile() {        
        var xhr = new XMLHttpRequest();                 
        var file = document.getElementById('myfile').files[0];
        xhr.open("POST", "api/myfileupload");
        xhr.setRequestHeader("filename", file.name);
        xhr.send(file);
    }
</script>
James Lawruk
sumber
Namun berhati-hatilah bahwa ini tidak akan berfungsi dengan unggahan formulir multi-bagian 'normal'
Tom
3
@ Tom, apa artinya itu?
Chazt3n
Itu berarti tidak kompatibel dengan browser di mana JavaScript dinonaktifkan / tidak ada, misalnya Netscape 1. *.
Mikael Dúi Bolinder
13

Saya menggunakan jawaban Mike Wasson sebelum saya memperbarui semua NuGets di proyek webapi mvc4 saya. Setelah melakukannya, saya harus menulis ulang tindakan unggah file:

    public Task<HttpResponseMessage> Upload(int id)
    {
        HttpRequestMessage request = this.Request;
        if (!request.Content.IsMimeMultipartContent())
        {
            throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.UnsupportedMediaType));
        }

        string root = System.Web.HttpContext.Current.Server.MapPath("~/App_Data/uploads");
        var provider = new MultipartFormDataStreamProvider(root);

        var task = request.Content.ReadAsMultipartAsync(provider).
            ContinueWith<HttpResponseMessage>(o =>
            {
                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("\"", "")));

                return new HttpResponseMessage()
                {
                    Content = new StringContent("File uploaded.")
                };
            }
        );
        return task;
    }

Rupanya BodyPartFileNames tidak lagi tersedia dalam MultipartFormDataStreamProvider.

Steve Stokes
sumber
Di WebApi RTM, BodyPartFileNames telah diubah menjadi FileData. Lihat contoh yang diperbarui di asp.net/web-api/overview/working-with-http/…
Mark van Straten
Mengapa tidak menggunakan koleksi System.Web.HttpContext.Current.Request.Files?
ADOConnection
Saya sedang berpikir untuk menggunakan metode Anda dengan 2 pemesanan: 1) Tidakkah ini menulis dua kali: i) masuk ReadAsMultipartAsyncdan ii) Masuk File.Move? 2) Bisakah kamu melakukannya async File.Move?
seebiscuit
1) Saya tidak memiliki masalah dengan dua penulisan, apakah url dipanggil dua kali? 2) Anda dapat melakukan Task.Run (() => {File.Move (src, dest);});
Steve Stokes
10

Menuju arah yang sama ini, saya memposting snipet klien dan server yang mengirim File Excel menggunakan WebApi, c # 4:

public static void SetFile(String serviceUrl, byte[] fileArray, String fileName)
{
    try
    {
        using (var client = new HttpClient())
        {
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                using (var content = new MultipartFormDataContent())
                {
                    var fileContent = new ByteArrayContent(fileArray);//(System.IO.File.ReadAllBytes(fileName));
                    fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
                    {
                        FileName = fileName
                    };
                    content.Add(fileContent);
                    var result = client.PostAsync(serviceUrl, content).Result;
                }
        }
    }
    catch (Exception e)
    {
        //Log the exception
    }
}

Dan pengontrol webapi server:

public Task<IEnumerable<string>> Post()
{
    if (Request.Content.IsMimeMultipartContent())
    {
        string fullPath = HttpContext.Current.Server.MapPath("~/uploads");
        MyMultipartFormDataStreamProvider streamProvider = new MyMultipartFormDataStreamProvider(fullPath);
        var task = Request.Content.ReadAsMultipartAsync(streamProvider).ContinueWith(t =>
        {
            if (t.IsFaulted || t.IsCanceled)
                    throw new HttpResponseException(HttpStatusCode.InternalServerError);

            var fileInfo = streamProvider.FileData.Select(i =>
            {
                var info = new FileInfo(i.LocalFileName);
                return "File uploaded as " + info.FullName + " (" + info.Length + ")";
            });
            return fileInfo;

        });
        return task;
    }
    else
    {
        throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "Invalid Request!"));
    }
}

Dan Custom MyMultipartFormDataStreamProvider, diperlukan untuk menyesuaikan Nama File:

PS: Saya mengambil kode ini dari posting lain http://www.codeguru.com/csharp/.net/uploading-files-asynchronously-using-asp.net-web-api .htm

public class MyMultipartFormDataStreamProvider : MultipartFormDataStreamProvider
{
    public MyMultipartFormDataStreamProvider(string path)
        : base(path)
    {

    }

    public override string GetLocalFileName(System.Net.Http.Headers.HttpContentHeaders headers)
    {
        string fileName;
        if (!string.IsNullOrWhiteSpace(headers.ContentDisposition.FileName))
        {
            fileName = headers.ContentDisposition.FileName;
        }
        else
        {
            fileName = Guid.NewGuid().ToString() + ".data";
        }
        return fileName.Replace("\"", string.Empty);
    }
}
Daniel Melo
sumber
Bisakah Anda menunjukkan bagaimana Anda memanggil Anda static method SetFiledi Controller Anda?
Ini jawaban yang bagus. Memperluas penyedia basis seperti ini juga memungkinkan Anda untuk mengontrol aliran dan memberi Anda lebih banyak fleksibilitas daripada hanya menyediakan pathpenyimpanan cloud.
Phil Cooper
6
[HttpPost]
public JsonResult PostImage(HttpPostedFileBase file)
{
    try
    {
        if (file != null && file.ContentLength > 0 && file.ContentLength<=10485760)
        {
            var fileName = Path.GetFileName(file.FileName);                                        

            var path = Path.Combine(Server.MapPath("~/") + "HisloImages" + "\\", fileName);

            file.SaveAs(path);
            #region MyRegion
            ////save imag in Db
            //using (MemoryStream ms = new MemoryStream())
            //{
            //    file.InputStream.CopyTo(ms);
            //    byte[] array = ms.GetBuffer();
            //} 
            #endregion
            return Json(JsonResponseFactory.SuccessResponse("Status:0 ,Message: OK"), JsonRequestBehavior.AllowGet);
        }
        else
        {
            return Json(JsonResponseFactory.ErrorResponse("Status:1 , Message: Upload Again and File Size Should be Less Than 10MB"), JsonRequestBehavior.AllowGet);
        }
    }
    catch (Exception ex)
    {

        return Json(JsonResponseFactory.ErrorResponse(ex.Message), JsonRequestBehavior.AllowGet);

    }
}
pengguna3722373
sumber
6
Saya pikir pengguna perlu penjelasan ...!
kamesh
4

Berikut adalah dua cara untuk menerima file. Satu menggunakan penyedia memori MultipartMemoryStreamProvider dan satu menggunakan MultipartFormDataStreamProvider yang menyimpan ke disk. Catatan, ini hanya untuk satu file unggahan sekaligus. Anda dapat dengan pasti memperpanjang ini untuk menyimpan banyak file. Pendekatan kedua dapat mendukung file besar. Saya sudah menguji file lebih dari 200MB dan berfungsi dengan baik. Menggunakan dalam pendekatan memori tidak mengharuskan Anda untuk menyimpan ke disk, tetapi akan membuang pengecualian memori jika Anda melebihi batas tertentu.

private async Task<Stream> ReadStream()
{
    Stream stream = null;
    var provider = new MultipartMemoryStreamProvider();
    await Request.Content.ReadAsMultipartAsync(provider);
    foreach (var file in provider.Contents)
    {
        var buffer = await file.ReadAsByteArrayAsync();
        stream = new MemoryStream(buffer);
    }

    return stream;
}

private async Task<Stream> ReadLargeStream()
{
    Stream stream = null;
    string root = Path.GetTempPath();
    var provider = new MultipartFormDataStreamProvider(root);
    await Request.Content.ReadAsMultipartAsync(provider);
    foreach (var file in provider.FileData)
    {
        var path = file.LocalFileName;
        byte[] content = File.ReadAllBytes(path);
        File.Delete(path);
        stream = new MemoryStream(content);
    }

    return stream;
}
Filix Mogilevsky
sumber
1

Saya memiliki masalah serupa untuk API Web pratinjau. Belum port bagian itu ke API Web MVC 4 baru, tapi mungkin ini membantu:

Unggah file REST dengan HttpRequestMessage atau Stream?

Tolong beri tahu saya, bisa duduk besok dan mencoba menerapkannya lagi.

Remy
sumber
1

Pertanyaan ini memiliki banyak jawaban bagus bahkan untuk .Net Core. Saya menggunakan kedua Kerangka sampel kode yang disediakan berfungsi dengan baik. Jadi saya tidak akan mengulanginya. Dalam kasus saya yang penting adalah bagaimana menggunakan tindakan unggah File dengan Swagger seperti ini:

Tombol unggah file di Swagger

Inilah rekap saya:

ASP .Net WebAPI 2

.NET Core

Utama
sumber
1

Pengontrol API:

[HttpPost]
public HttpResponseMessage Post()
{
    var httpRequest = System.Web.HttpContext.Current.Request;

    if (System.Web.HttpContext.Current.Request.Files.Count < 1)
    {
        //TODO
    }
    else
    {

    try
    { 
        foreach (string file in httpRequest.Files)
        { 
            var postedFile = httpRequest.Files[file];
            BinaryReader binReader = new BinaryReader(postedFile.InputStream);
            byte[] byteArray = binReader.ReadBytes(postedFile.ContentLength);

        }

    }
    catch (System.Exception e)
    {
        //TODO
    }

    return Request.CreateResponse(HttpStatusCode.Created);
}
Tiago Medici
sumber
0

Melengkapi jawaban Matt Frear - Ini akan menjadi alternatif ASP NET Core untuk membaca file langsung dari Stream, tanpa menyimpan & membacanya dari disk:

public ActionResult OnPostUpload(List<IFormFile> files)
    {
        try
        {
            var file = files.FirstOrDefault();
            var inputstream = file.OpenReadStream();

            XSSFWorkbook workbook = new XSSFWorkbook(stream);

            var FIRST_ROW_NUMBER = {{firstRowWithValue}};

            ISheet sheet = workbook.GetSheetAt(0);
            // Example: var firstCellRow = (int)sheet.GetRow(0).GetCell(0).NumericCellValue;

            for (int rowIdx = 2; rowIdx <= sheet.LastRowNum; rowIdx++)
               {
                  IRow currentRow = sheet.GetRow(rowIdx);

                  if (currentRow == null || currentRow.Cells == null || currentRow.Cells.Count() < FIRST_ROW_NUMBER) break;

                  var df = new DataFormatter();                

                  for (int cellNumber = {{firstCellWithValue}}; cellNumber < {{lastCellWithValue}}; cellNumber++)
                      {
                         //business logic & saving data to DB                        
                      }               
                }
        }
        catch(Exception ex)
        {
            throw new FileFormatException($"Error on file processing - {ex.Message}");
        }
    }
Pedro Coelho
sumber