วิธีรับไฟล์ POST


254

ฉันใช้ asp.net mvc 4 webapi รุ่นเบต้าเพื่อสร้างบริการส่วนที่เหลือ ฉันต้องสามารถยอมรับภาพ / ไฟล์ที่โพสต์จากแอปพลิเคชันไคลเอนต์ สามารถใช้ webapi ได้หรือไม่? ด้านล่างเป็นวิธีการกระทำที่ฉันใช้อยู่ในปัจจุบัน ไม่มีใครรู้ตัวอย่างว่ามันควรจะทำงานอย่างไร

[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";
}

3
ใช้งานได้กับ MVC ไม่ใช่กรอบงาน WebAPI
ฟิล

คุณน่าจะสามารถคว้าไอเท็มได้จากRequest.Files
Tejs

7
ApiController ไม่มี HttpRequestBase ซึ่งมีคุณสมบัติของไฟล์ วัตถุคำขอเป็นไปตามคลาส HttpRequestMessage
Phil

คำตอบ:


168

ดู http://www.asp.net/web-api/overview/formats-and-model-binding/html-forms-and-multipart-mime#multipartmimeแม้ว่าฉันคิดว่าบทความจะดูซับซ้อนกว่า มันคือเรื่องจริง

โดยทั่วไป

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

5
ประโยชน์ของการใช้งานในการอ่านเพียงไฟล์เดียวคืออะไร? คำถามของแท้ฉันเพิ่งเริ่มใช้งาน จากความเข้าใจปัจจุบันของฉันรหัสนี้เหมาะสำหรับการอัปโหลดมากกว่าหนึ่งไฟล์ถูกต้องหรือไม่
คริส

48
MultipartFormDataStreamProvider ไม่มีคุณสมบัติ BodyPartFileNames อีกต่อไป (ใน WebApi RTM) ดูasp.net/web-api/overview/working-with-http/…
Shrike

5
พวกคุณคนใดก็ได้โปรดอธิบายบางอย่างเกี่ยวกับสาเหตุที่เราไม่สามารถเข้าถึงไฟล์โดยใช้ HttpContext.Current.Request.Files แทนและจำเป็นต้องใช้ MultipartFormDataStreamProvider ที่เป็นแฟนซีแทนนี้หรือไม่ คำถามเต็ม: stackoverflow.com/questions/17967544
niaher

7
ไฟล์จะถูกบันทึกไว้เป็นBodyPart_8b77040b-354b-464c-bc15-b3591f98f30f พวกเขาไม่ควรได้รับการบันทึกเช่นpic.jpgเหมือนกับที่ลูกค้าเคยทำหรือไม่?
lbrahim

10
MultipartFormDataStreamProviderไม่เปิดเผยBodyPartFileNamesคุณสมบัติอีกต่อไปฉันใช้FileData.First().LocalFileNameแทน
Chtiwi Malek

374

ฉันประหลาดใจที่คุณหลายคนดูเหมือนจะต้องการบันทึกไฟล์บนเซิร์ฟเวอร์ วิธีแก้ไขปัญหาเพื่อเก็บทุกอย่างไว้ในหน่วยความจำมีดังนี้:

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

34
การเก็บไฟล์ไว้ในหน่วยความจำจะมีประโยชน์หากคุณไม่ต้องการใช้พื้นที่ดิสก์ อย่างไรก็ตามหากคุณอนุญาตให้อัปโหลดไฟล์ขนาดใหญ่แล้วเก็บไว้ในหน่วยความจำหมายความว่าเว็บเซิร์ฟเวอร์ของคุณจะใช้หน่วยความจำมากซึ่งไม่สามารถใช้ในการรักษาสิ่งต่าง ๆ สำหรับคำขออื่น ๆ สิ่งนี้จะทำให้เกิดปัญหากับเซิร์ฟเวอร์ที่ทำงานภายใต้ภาระงานสูง
Willem Meints

21
@ W.Meints ฉันเข้าใจถึงเหตุผลที่ต้องการเก็บข้อมูล แต่ฉันไม่เข้าใจว่าทำไมทุกคนต้องการเก็บข้อมูลที่อัปโหลดไว้ในพื้นที่ดิสก์ของเซิร์ฟเวอร์ คุณควรแยกที่เก็บไฟล์ออกจากเว็บเซิร์ฟเวอร์เสมอ - แม้แต่กับโครงการขนาดเล็ก
Gleno

1
ตรวจสอบให้แน่ใจว่าขนาดไฟล์ที่โพสต์ของคุณน้อยกว่า 64k พฤติกรรมเริ่มต้นคือการเพิกเฉยต่อคำขอเป็นอย่างอื่นผมติดอยู่กับเรื่องนี้เป็นเวลาบันทึก
Gary Davies

3
น่าเสียดาย MultipartMemoryStreamProvider ไม่ได้ช่วยถ้าคุณต้องการอ่านข้อมูลในแบบฟอร์มเช่นกัน ต้องการสร้างบางสิ่งบางอย่างเช่น MultipartFormDataMemoryStreamProvider แต่มีคลาสและตัวช่วยมากมายอยู่ภายในใน aspnetwebstack :(
martinoss

9
File.WriteAllBytes(filename, buffer);เพื่อเขียนลงไฟล์
pomber

118

ดูรหัสด้านล่างดัดแปลงมาจากบทความนี้ซึ่งแสดงตัวอย่างรหัสที่ง่ายที่สุดที่ฉันสามารถหาได้ มันมีทั้งการอัพโหลดไฟล์และหน่วยความจำ (เร็วกว่า)

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

26
HttpContext.Current เป็นโมฆะเมื่อ WebAPI โฮสต์ใน OWIN ซึ่งเป็นคอนเทนเนอร์ที่โฮสต์ด้วยตนเอง
ซัค

1
แก้ไขได้เช่น: var httpRequest = System.Web.HttpContext.Current.Request;
msysmilu

7
อย่าใช้ System.Web ใน WebAPI เว้นแต่คุณจะต้อง
Kugel

3
แน่นอนว่า System.Web นั้นเชื่อมต่อกับ IIS อย่างแน่นหนา หากคุณทำงานภายใน OWIN piple line หรือ. Net Core API เหล่านั้นจะไม่สามารถใช้ได้เมื่อทำงานภายใต้ linux หรือโฮสต์ด้วยตนเอง
Kugel

2
คำตอบที่ดี เพียงรายละเอียดเดียว: หากคุณกำลังอัปโหลดจากหน้า HTML แท็ก <input type = "file" /> จะต้องมีแอตทริบิวต์ "name" มิฉะนั้นไฟล์จะไม่ปรากฏใน HttpContext.Current.Request.Files
GBU

17

วิธี ASP.NET Core ขณะนี้อยู่ที่นี่ :

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

16

นี่คือวิธีแก้ปัญหาที่รวดเร็วและสกปรกซึ่งนำเนื้อหาไฟล์ที่อัปโหลดจากเนื้อหา HTTP และเขียนไปยังไฟล์ ฉันรวมตัวอย่างโค้ด HTML / JS สำหรับการอัพโหลดไฟล์

วิธีใช้เว็บ API:

[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";
}

อัพโหลดไฟล์ 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>

ระวังแม้ว่าสิ่งนี้จะไม่สามารถใช้ได้กับการอัปโหลดรูปแบบหลายส่วน 'ปกติ'
ทอม

3
@ นั่นหมายความว่าอย่างไร
Chazt3n

หมายความว่ามันเข้ากันไม่ได้กับเบราว์เซอร์ที่ปิดใช้งาน JavaScript / ไม่มีอยู่จริงเช่น Netscape 1 *
Mikael Dúi Bolinder

13

ฉันใช้คำตอบของ Mike Wasson ก่อนที่ฉันจะอัพเดท NuGets ทั้งหมดในโครงการ webapi mvc4 ของฉัน เมื่อฉันทำฉันต้องเขียนการกระทำการอัปโหลดไฟล์อีกครั้ง:

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

เห็นได้ชัดว่า BodyPartFileNames ไม่สามารถใช้งานได้ใน MultipartFormDataStreamProvider อีกต่อไป


ใน WebApi RTM BodyPartFileNames ถูกเปลี่ยนเป็น FileData ดูตัวอย่างอัปเดตได้ที่asp.net/web-api/overview/working-with-http/ ......
Mark van Straten

ทำไมไม่ใช้คอลเลกชัน System.Web.HttpContext.Current.Request.Files?
ADOConnection

ฉันกำลังคิดที่จะใช้วิธีการของคุณด้วยการจอง 2 ครั้ง: 1) นี่ไม่ได้เขียนสองครั้ง: i) ในReadAsMultipartAsyncและ ii) ในFile.Move? 2) คุณสามารถทำasync File.Moveอะไรได้บ้าง
seebiscuit

1) ฉันไม่มีปัญหาในการเขียนสองครั้ง URL นั้นถูกเรียกสองครั้งหรือไม่ 2) คุณสามารถทำ Task.Run (() => {File.Move (src, dest);});
Steve Stokes

10

ในทิศทางเดียวกันฉันกำลังโพสต์ลูกค้าและเซิร์ฟเวอร์ที่ส่งไฟล์ Excel โดยใช้ 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
    }
}

และตัวควบคุม webapi ของเซิร์ฟเวอร์:

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!"));
    }
}

และ Custom MyMultipartFormDataStreamProvider จำเป็นต้องปรับแต่งชื่อไฟล์:

PS: ฉันเอารหัสนี้จากโพสต์อื่น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);
    }
}

คุณสามารถแสดงได้อย่างไรว่าคุณโทรหาคุณstatic method SetFileในคอนโทรลเลอร์ของคุณ?

นี่เป็นคำตอบที่ดี การขยายผู้ให้บริการพื้นฐานเช่นนี้ช่วยให้คุณสามารถควบคุมสตรีมและให้ความยืดหยุ่นมากกว่าการให้บริการpathพื้นที่เก็บข้อมูลบนคลาวด์
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);

    }
}

6
ฉันคิดว่าผู้ใช้ต้องการคำอธิบาย ... !
kamesh

4

วิธีรับไฟล์มีสองวิธี หนึ่งในผู้ให้บริการหน่วยความจำMultipartMemoryStreamProviderและอีกคนหนึ่งใช้MultipartFormDataStreamProviderซึ่งบันทึกลงดิสก์ หมายเหตุนี่เป็นเพียงการอัปโหลดไฟล์ครั้งละหนึ่งไฟล์เท่านั้น คุณสามารถขยายสิ่งนี้เพื่อบันทึกหลายไฟล์ได้อย่างแน่นอน วิธีที่สองสามารถรองรับไฟล์ขนาดใหญ่ ฉันได้ทดสอบไฟล์มากกว่า 200MB และใช้งานได้ดี การใช้หน่วยความจำแบบเข้าใกล้ไม่ต้องการให้คุณบันทึกลงดิสก์ แต่จะทิ้งข้อยกเว้นหน่วยความจำหากคุณใช้เกินขีด จำกัด

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

1

ฉันมีปัญหาที่คล้ายกันสำหรับ Web API แสดงตัวอย่าง ยังไม่ได้ทำการเชื่อมต่อส่วนนั้นกับ MVC 4 Web API ใหม่ แต่นี่อาจช่วยได้:

อัพโหลดไฟล์ REST ด้วย HttpRequestMessage หรือ Stream?

โปรดแจ้งให้เราทราบสามารถนั่งลงในวันพรุ่งนี้และลองนำไปใช้อีกครั้ง


1

คำถามนี้มีคำตอบที่ดีมากมายสำหรับ. Net Core ฉันใช้ทั้ง Frameworks ตัวอย่างโค้ดที่ให้มาใช้ได้ดี ดังนั้นฉันจะไม่ทำซ้ำ ในกรณีของฉันสิ่งสำคัญคือวิธีใช้การอัปโหลดไฟล์กับSwaggerเช่นนี้:

ปุ่มอัพโหลดไฟล์ใน Swagger

นี่คือสรุปของฉัน:

ASP .Net WebAPI 2

  • ในการอัปโหลดไฟล์ให้ใช้: MultipartFormDataStreamProviderดูคำตอบได้ที่นี่
  • วิธีใช้กับ Swagger

.NET Core


1

ตัวควบคุม 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);
}

0

การตอบสนองของ Matt Frear - นี่จะเป็นทางเลือก ASP NET Core สำหรับการอ่านไฟล์โดยตรงจากสตรีมโดยไม่ต้องบันทึกและอ่านจากดิสก์:

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}");
        }
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.