Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET Core forward a local API form-data post request to remote API

I have an AJAX form which post a form-data to a local API url: /api/document. It contains a file and a custom Id. We simply want to take the exact received Request and forward it to a remote API at example.com:8000/document/upload.

Is there a simple way of achieve this "forward" (or proxy?) of the Request to a remote API using Asp.NET Core?

Below we had the idea to simply use Web API Http client to get the request and then resend it (by doing so we want to be able to for example append a private api key from the backend), but it seems not to work properly, the PostAsync doesn't accept the Request.

Raw request sent by Ajax

POST http://localhost:62640/api/document HTTP/1.1
Host: localhost:62640
Connection: keep-alive
Content-Length: 77424
Accept: application/json
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryn1BS5IFplQcUklyt
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.8,fr;q=0.6

------WebKitFormBoundaryn1BS5IFplQcUklyt
Content-Disposition: form-data; name="fileToUpload"; filename="test-document.pdf"
Content-Type: application/pdf
...
------WebKitFormBoundaryn1BS5IFplQcUklyt
Content-Disposition: form-data; name="id"

someid
------WebKitFormBoundaryn1BS5IFplQcUklyt--

Backend Code

Our .NET Core backend has a simple "forward to another API" purpose.

public class DocumentUploadResult
{
     public int errorCode;
     public string docId;
}

[Route("api/[controller]")]
public class DocumentController : Controller
{
    // POST api/document
    [HttpPost]
    public async Task<DocumentUploadResult> Post()
    {
        client.BaseAddress = new Uri("http://example.com:8000");

        client.DefaultRequestHeaders.Accept.Clear();
        HttpResponseMessage response = await client.PostAsync("/document/upload", Request.Form);
        if (response.IsSuccessStatusCode)
        {
            retValue = await response.Content.ReadAsAsync<DocumentUploadResult>();
        }
        return retValue;
    }
}

We have a GET request (not reproduced here) which works just fine. As it doesn't have to fetch data from locally POSTed data.

My question

How to simply pass the incoming local HttpPost request and forwarding it to the remote API?

I searched A LOT on stackoverflow or on the web but all are old resources talking about forwarding Request.Content to the remote.

But on Asp.NET Core 1.0, we don't have access to Content. We only are able to retrieve Request.Form (nor Request.Body) which is then not accepted as an argument of PostAsync method:

Cannot convert from Microsoft.AspNetCore.Http.IformCollection to System.Net.Http.HttpContent

I had the idea to directly pass the request to the postAsync:

Cannot convert from Microsoft.AspNetCore.Http.HttpRequest to System.Net.Http.HttpContent

I don't know how to rebuild expected HttpContent from the local request I receive.

Expected response

For information, When we post a valid form-data with the custom Id and the uploaded file, the remote (example.com) API response is:

{
  "errorCode": 0
  "docId": "585846a1afe8ad12e46a4e60"
}
like image 730
BlackHoleGalaxy Avatar asked Jan 06 '17 15:01

BlackHoleGalaxy


1 Answers

Ok first create a view model to hold form information. Since file upload is involved, include IFormFile in the model.

public class FormData {
    public int id { get; set; }
    public IFormFile fileToUpload { get; set; }
}

The model binder should pick up the types and populate the model with the incoming data.

Update controller action to accept the model and proxy the data forward by copying content to new request.

[Route("api/[controller]")]
public class DocumentController : Controller {
    // POST api/document
    [HttpPost]
    public async Task<IActionResult> Post(FormData formData) {
        if(formData != null && ModelState.IsValid) {
            client.BaseAddress = new Uri("http://example.com:8000");
            client.DefaultRequestHeaders.Accept.Clear();

            var multiContent = new MultipartFormDataContent();

            var file = formData.fileToUpload;
            if(file != null) {
                var fileStreamContent = new StreamContent(file.OpenReadStream());
                multiContent.Add(fileStreamContent, "fileToUpload", file.FileName);
            }

            multiContent.Add(new StringContent(formData.id.ToString()), "id");

            var response = await client.PostAsync("/document/upload", multiContent);
            if (response.IsSuccessStatusCode) {
               var retValue = await response.Content.ReadAsAsync<DocumentUploadResult>();
               return Ok(reyValue);
            }
        }
        //if we get this far something Failed.
        return BadRequest();
    }        
}

You can include the necessary exception handlers as needed but this is a minimal example of how to pass the form data forward.

like image 154
Nkosi Avatar answered Nov 08 '22 18:11

Nkosi