Как принять файл POST

254

Я использую asp.net mvc 4 webapi beta для создания сервиса отдыха. Мне нужно иметь возможность принимать POSTed изображения / файлы из клиентских приложений. Возможно ли это с помощью вебапи? Ниже описано, как я сейчас использую действие. Кто-нибудь знает пример, как это должно работать?

[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, который имеет свойство Files. Его объект Request основан на классе HttpRequestMessage.
Фил

Ответы:

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 (в RTA WebApi). См asp.net/web-api/overview/working-with-http/...
Шрайка
5
Ребята, кто-нибудь из вас может пролить свет на то, почему мы не можем просто получить доступ к файлам с помощью HttpContext.Current.Request.Files и вместо этого должны использовать этот модный MultipartFormDataStreamProvider? Полный вопрос: stackoverflow.com/questions/17967544 .
13
7
Файлы сохраняются как BodyPart_8b77040b-354b-464c-bc15-b3591f98f30f . Разве они не должны быть сохранены как pic.jpg в точности так, как это было на клиенте?
Ибрагим
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
Хранение файлов в памяти может быть полезно, если вы не хотите тратить место на диске. Однако, если вы разрешаете загружать большие файлы, то их хранение в памяти означает, что ваш веб-сервер использует много памяти, которую нельзя тратить на хранение содержимого для других запросов. Это вызовет проблемы на серверах, которые работают под большой нагрузкой.
Виллем Мейнтс
21
@ W.Meints Я понимаю причины, по которым нужно хранить данные, но я не понимаю, почему кто-то захочет хранить загруженные данные на диске сервера. Вы всегда должны держать файловое хранилище изолированным от веб-сервера - даже для небольших проектов.
Глено
1
Убедитесь, что размер вашего опубликованного файла меньше 64 КБ, по умолчанию используется игнорирование запросов, в противном случае я застрял на этом время журнала.
Гэри Дэвис,
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 имеет значение null, когда WebAPI размещен в OWIN, который является автономным контейнером.
Зак
1
Исправлено это так: var httpRequest = System.Web.HttpContext.Current.Request;
msysmilu
7
Не используйте System.Web в WebAPI без крайней необходимости.
Кугель
3
Конечно, System.Web тесно связан с IIS. Если вы работаете в линейке OWIN или в .Net Core, эти API не будут доступны при работе в Linux или в автономном режиме.
Кугель
2
Отличный ответ. Только одна деталь: если вы загружаете со страницы HTML, тег <input type = "file" /> должен иметь атрибут "name", иначе файл не будет присутствовать в HttpContext.Current.Request.Files.
ГБУ
18

Основной способ ASP.NET теперь здесь :

[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. *.
Микаэль Дуй Болиндер
13

Я использовал ответ Майка Уоссона, прежде чем обновил все 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.

Стив Стоукс
источник
В RTA WebApi BodyPartFileNames был изменен на FileData. Смотрите обновленный пример на asp.net/web-api/overview/working-with-http/…
Марк ван Стратен
Почему бы просто не использовать коллекцию System.Web.HttpContext.Current.Request.Files?
ADOConnection
Я думаю об использовании вашего метода с двумя оговорками: 1) Разве это не пишет дважды: i) в ReadAsMultipartAsyncи ii) в File.Move? 2) Не могли бы вы сделать async File.Move?
Seebiscuit
1) У меня не было проблем с двумя записями, URL вызывается дважды? 2) вы можете сделать Task.Run (() => {File.Move (src, dest);});
Стив Стоукс
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!"));
    }
}

И пользовательский 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облачное хранилище.
Фил Купер
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);

    }
}
user3722373
источник
6
Я думаю, что пользователю нужно какое-то объяснение ...!
Камеш
4

Вот два способа принять файл. Один использует в памяти провайдера MultipartMemoryStreamProvider, а другой использует MultipartFormDataStreamProvider, который сохраняет на диск. Обратите внимание, это только для одной загрузки файла за раз. Вы можете наверняка расширить это, чтобы сохранить несколько файлов. Второй подход может поддерживать большие файлы. Я проверил файлы размером более 200 МБ, и он отлично работает. Использование в памяти не требует сохранения на диск, но выдает исключение из памяти, если вы превысите определенный лимит.

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

У меня была похожая проблема для предварительного просмотра веб-API. Еще не перенес эту часть в новый веб-API MVC 4, но, возможно, это поможет:

Загрузка файла REST с помощью HttpRequestMessage или Stream?

Пожалуйста, дайте мне знать, можете сесть завтра и попытаться реализовать это снова.

Remy
источник
1

На этот вопрос есть много хороших ответов даже для .Net Core. Я использовал обе платформы, предоставленные примеры кода работают нормально. Поэтому я не буду повторять это. В моем случае важно было использовать действия загрузки файла с Swagger следующим образом:

Кнопка загрузки файла в Swagger

Вот мое резюме:

ASP .Net WebAPI 2

.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

Дополняя ответ Мэтта Фреара - это будет альтернатива ASP NET Core для чтения файла непосредственно из Stream без сохранения и чтения с диска:

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}");
        }
    }
Педро Коэльо
источник