首页 > 解决方案 > 尝试使用 HttpWebRequest 方法将文件从控制器上传到具有 [FromForm] IFormFile 文件参数的 API 操作接收 null

问题描述

我正在尝试将文件从 Controller 上传到 asp.net core mvc 中的 API。接收文件的 API 方法如下:

[Route("api/[controller]")]
public class ReservationController : Controller
{
    ...

    [HttpPost]
    public void UploadFile ([FromForm] IFormFile file)
    {
       ....
    }
}

现在在我的另一个项目中有一个包含文件上传控件的视图:

<form method="post" enctype="multipart/form-data">
    <input type="file" name="file" />
    <div class="text-center panel-body">
        <button type="submit" class="btn btn-sm btn-primary">Add</button>
    </div>
</form>

现在在提交按钮上,被调用的控制器操作是:

[HttpPost]
public void AddFile(IFormFile file)
{
    HttpWebRequest apiRequest = WebRequest.Create("http://localhost:8888/api/Reservation") as HttpWebRequest;
    apiRequest.Method = "Post";
    apiRequest.ContentType = "application/octet-stream";
    string apiResponse = "";
    using (HttpWebResponse response = apiRequest.GetResponse() as HttpWebResponse)
        {
            StreamReader reader = new StreamReader(response.GetResponseStream());
            apiResponse = reader.ReadToEnd();
        }
  } 

IFormFile 文件获取文件。到目前为止,它工作正常。

问题

现在我正在创建 HttpWebRequest 的对象以使用 API 方法上传我的文件。

API方法被调用,但我恢复了空值,即在下面的方法中,“文件”参数为空?

[HttpPost]
public void UploadFile ([FromForm] IFormFile file)
{
}

为什么会这样?请帮忙?

笔记

如果我只是将视图更改为将表单上的操作属性包含到我的 API 的 URL,那么它可以工作:

<form method="post" enctype="multipart/form-data" action="http://localhost:8888/api/Reservation/UploadFile">
    <input type="file" name="file" />
    <div class="text-center panel-body">
        <button type="submit" class="btn btn-sm btn-primary">Add</button>
    </div>
</form>

由于我想从控制器执行此操作并获取响应,因此我想从控制器进行 API 调用。

标签: asp.netasp.net-mvcasp.net-web-apiasp.net-core

解决方案


IFormFile特别只适用于multipart/form-data编码,这就是为什么它在你的第一个动作中很好。但是,在您在那里提出的请求中,最大的问题是您实际上甚至没有首先在请求中发送文件。然后,您还需要使用multipart/form-data而不是application/octet-stream.

处理这个通过WebRequest实际上是相当复杂的,并且HttpClient无论如何都是发出请求的首选方式。与HttpClient

string apiResponse;
using (var httpClient = new HttpClient())
{
    var form = new MultipartFormDataContent();
    using (var fileStream = file.OpenReadStream())
    {
        form.Add(new StreamContent(fileStream), "file", file.Filename);
        using (var response = await httpClient.PostAsync(url, form))
        {
            response.EnsureSuccessStatusCode();
            apiResponse = await response.Content.ReadAsStringAsync();
        }
     }
 }

我想故意使示例简单,但实际上您不应该这样使用HttpClient。在大多数情况下,它应该被视为一个单例,幸运的是,通过IHttpClientFactory. 首先,您将需要某种包装类,这在您使用 API 时是个好主意:

public class ReservationService
{
    private readonly HttpClient _client;

    public ReservationService(HttpClient client)
    {
        _client = client ?? throw new ArgumentNullException(nameof(client));
    }

    public async Task<string> AddFileAsync(IFormFile file)
    {
        var form = new MultipartFormDataContent();
        using (var fileStream = file.OpenReadStream())
        {
            form.Add(new StreamContent(fileStream), "file", file.Filename);
            using (var response = await httpClient.PostAsync("", form))
            {
                response.EnsureSuccessStatusCode();
                return await response.Content.ReadAsStringAsync();
            }
        }
    }
}

然后,在ConfigureServicesStartup.cs

services.AddScoped<ReservationService>();

services.AddHttpClient<ReservationService>(c =>
{
    c.BaseAddress = new Uri("http://localhost:8888/api/Reservation");
});

最后,在您的控制器中:

public class FooController : Controller
{
    private readonly ReservationService _service;

    public FooController(ReservationService service)
    {
        _service = service ?? throw new ArgumentNullException(nameof(service));
    }

    ...

    [HttpPost]
    public async Task<IActionResult> AddFile(IFormFile file)
    {
        var apiResponse = await _service.AddFileAsync(file);
        // whatever else you want to do
    }
}

推荐阅读