将文件从 ASP.NET Core Web api 发布到另一个 ASP.NET Core Web api

Posted

技术标签:

【中文标题】将文件从 ASP.NET Core Web api 发布到另一个 ASP.NET Core Web api【英文标题】:Post files from ASP.NET Core web api to another ASP.NET Core web api 【发布时间】:2017-01-16 18:15:26 【问题描述】:

我们正在构建一个由 Angular2 前端、一个 ASP.NET Core Web api 公共后端和一个 ASP.NET Core Web api 私有后端组成的 Web 应用程序。

将文件从 Angular2 上传到公共后端是可行的。但我们更愿意将它们转发到私有后端。

当前工作代码

[HttpPost]
public StatusCodeResult Post(IFormFile file)

  ...

从那里我可以使用 file.CopyTo(fileStream); 将文件保存到磁盘

但是,我想重新发送该文件或那些文件,或者理想情况下,将整个请求重新发送到我的第二个 Web api 核心。

我不确定如何使用 asp.net 核心的 HttpClient 类来实现这一点。

我尝试过各种各样的事情,例如

StreamContent ss = new StreamContent(HttpContext.Request.Body);
var result = client.PostAsync("api/Values", ss).Result;

但是我的第二个后端得到了一个空的 IFormFile。

我感觉可以将文件作为流发送并在另一端重建它们,但无法使其正常工作。

解决方案必须使用两个 web api 核心。

【问题讨论】:

【参考方案1】:

解决方案

DMZ 中的公共后端

[HttpPost]
public StatusCodeResult Post(IFormFile file)

    try
    
        if (file != null && file.Length > 0)
        
            using (var client = new HttpClient())
            
                try
                
                    client.BaseAddress = new Uri(currentPrivateBackendAddress);
                    
                    byte[] data;
                    using (var br = new BinaryReader(file.OpenReadStream()))
                        data = br.ReadBytes((int)file.OpenReadStream().Length);

                    ByteArrayContent bytes = new ByteArrayContent(data);

                    
                    MultipartFormDataContent multiContent = new MultipartFormDataContent();
                    
                    multiContent.Add(bytes, "file", file.FileName);

                    var result = client.PostAsync("api/Values", multiContent).Result;
                    

                    return StatusCode((int)result.StatusCode); //201 Created the request has been fulfilled, resulting in the creation of a new resource.
                                                
                
                catch (Exception)
                
                    return StatusCode(500); // 500 is generic server error
                
            
        

        return StatusCode(400); // 400 is bad request

    
    catch (Exception)
    
        return StatusCode(500); // 500 is generic server error
    

私人后端

[HttpPost]
public void Post()

    //Stream bodyStream = HttpContext.Request.Body;

    if (Request.HasFormContentType)
    
        var form = Request.Form;
        foreach (var formFile in form.Files)
        
            var targetDirectory = Path.Combine(_appEnvironment.WebRootPath, "uploads");

            var fileName = GetFileName(formFile);

            var savePath = Path.Combine(targetDirectory, fileName);

            using (var fileStream = new FileStream(savePath, FileMode.Create))
            
                formFile.CopyTo(fileStream);
                               
        
              

【讨论】:

这个解决方案不是很好,因为您将整个内容加载到公共后端的内存中 - 想象一下,如果您的文件超出 RAM 可以容纳的范围...... 这是唯一对我有用的解决方案。 @GeorgeAnisimov 你知道替代方案吗? 似乎很适合我的网关文件通过。感谢您的解决方案。 不需要转换为 ByteArrayContent,您可以添加 StreamContent 对象,例如:multiContent.Add(new StreamContent(file.OpenReadStream(), "file", file.FileName)); 嗨。我还想发送一个带有此参数的参数,特别是对于我正在上传此文件的用户。我需要用这个请求发送 userId。你也可以帮忙吗?还有一件事。我如何使用邮递员来点击这个特定的上传 api?它的身体会是什么?【参考方案2】:

您好,我遇到了同样的问题,这对我有用:

我的设置是 netCore MVC netCoreApi。

我的 MVC 控制器看起来像:

[HttpPost("UploadFiles")]
public async Task<IActionResult> Post(List<IFormFile> files)

    Sp4RestClient dataPovider = new Sp4RestClient("http://localhost:60077/");

    long size = files.Sum(f => f.Length);

    foreach (var file in files)
    
       await dataPovider.ImportFile(file);
    

    return Ok();

数据提供者方法:

public async Task ImportFile(IFormFile file)
    
        RestClient restClient = new RestClient(_queryBulder.BuildImportFileRequest());

        using (var content = new MultipartFormDataContent())
        
            content.Add(new StreamContent(file.OpenReadStream())
            
                Headers =
                
                    ContentLength = file.Length,
                    ContentType = new MediaTypeHeaderValue(file.ContentType)
                
            , "File", "FileImport");

            var response = await restClient.Post<IFormFile>(content);
        
    

至少我的 WebApi 控制器:

[HttpPost]
[Route("ImportData")]
public IActionResult Import(IFormFile file)
         
    return Ok();

在这里查看完整代码是我的 RestClient Post 方法:

public async Task<RestResult<T>> Post<T>(HttpContent content)
    
        using (HttpClient httpClient = new HttpClient())
        
            HttpResponseMessage response = await httpClient.PostAsync(Endpoint, content);
            if (response.StatusCode == HttpStatusCode.Created)
            
                T result = JsonConvert.DeserializeObject<T>(await response.Content.ReadAsStringAsync());
                return new RestResult<T>  Result = result, ResultCode = HttpStatusCode.OK ;
            
            RestResult<T> nonOkResult =
                new RestResult<T>  Result = default(T), ResultCode = response.StatusCode, Message = await response.Content.ReadAsStringAsync() ;
            return nonOkResult;
        
    

// 是的,我知道我没有得到 HttpStatusCode.Created ;)

快乐编码 ;)

【讨论】:

【参考方案3】:

API 代码

 [Route("api/upload/id")]
    [HttpPost]
    public async Task<IActionResult> Post(string id)
    
        var filePath = @"D:\" + id; //+ Guid.NewGuid() + ".png";
        if (Request.HasFormContentType)
        
            var form = Request.Form;
            foreach (var formFile in form.Files)
            
                if (formFile.Length > 0)
                
                    using (var stream = new FileStream(filePath, FileMode.Create))
                    
                        await formFile.CopyToAsync(stream);
                    
                
            
        
        return Ok(new  Path = filePath );
    

后端

        [Route("home/UploadFile")]
    [HttpPost]
    public IActionResult UploadFile(IFormFile file)
    
        if (file == null || file.Length == 0)
            return Content("file not selected");

        var client = new HttpClient();

        byte[] data;
        using (var br = new BinaryReader(file.OpenReadStream()))
            data = br.ReadBytes((int)file.OpenReadStream().Length);
        ByteArrayContent bytes = new ByteArrayContent(data);
        MultipartFormDataContent multiContent = new MultipartFormDataContent
        
             bytes, "file", file.FileName 
        ;
        var result = client.PostAsync("http://localhost:2821/api/upload/" + file.FileName, multiContent).Result;
        return RedirectToAction("file");
    

Download Source

【讨论】:

【参考方案4】:

我也遇到过类似的情况——我需要一个代理方法来转发文件以及 JSON 数据等等。我不想对代理中的数据进行任何分析,让最终接收者处理它。

因此,在@Anton Tykhyy 的帮助下,我找到了以下可行的解决方案:

byte[] arr = null;
using (var mems = new MemoryStream())

    // read entire body into memory first because it might be chunked with unknown length
    await request.Body.CopyToAsync(mems);
    await mems.FlushAsync(); // not sure if needed after CopyToAsync - better safe then sorry
    arr = mems.ToArray();


msg.Content = new ByteArrayContent(arr);
msg.Content.Headers.ContentLength = arr.Length;

// keep content-type header "as is" to preserve multipart boundaries etc.
msg.Content.Headers.TryAddWithoutValidation("Content-Type", request.ContentType);

var response = await _httpClient.SendAsync(msg);


我用复杂的请求对其进行了测试,该请求包含带有 JSON 字段和多个附件的多部分表单数据,并且所有数据都没有任何问题地到达我的后端服务器。

【讨论】:

【参考方案5】:

调用私有后端API时忽略HttpClient,能否从公共Core API项目中引用私有Core API项目,直接从Core API项目中调用控制器?查看请求仍然为空/空。如果请求带有一个值,那么问题在于使用 HttpClient。

理想情况下,您希望为您想要分发给消费客户端的私有核心 API 创建一个包库(一种 SDK)。这就像一个包装器/代理。通过这种方式,您可以隔离私有后端系统,并且可以单独对其进行故障排除。因此,您的公共核心 API 项目(即私有后端客户端)可以将其作为 nuget 包引用。

【讨论】:

以上是关于将文件从 ASP.NET Core Web api 发布到另一个 ASP.NET Core Web api的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 fetch 将 FormData 从 javascript 发送到 ASP.NET Core 2.1 Web API

在 ASP.NET MVC 5 控制器中使用 POST 从 dotnet Core Web API 下载文件

在 ASP.NET Core Web API 中一起接收文件和其他表单数据(基于边界的请求解析)

使用 ASP.NET Core Web API 将图像上传到 Cloudianary 时出现空文件错误

我正在将我的 asp.net web api 迁移到 asp.net core。 Cors 迁移

在 ASP.NET Core Web API 中上传文件和 JSON