1

I'm having trouble uploading an image to a Web API that i'm running. I can retrieve data from the Web API when using GET requests, but I'm having trouble with POST requests. I need to upload an BMP image to the Web API and then send back a json string.

[HttpPost]
public IHttpActionResult TestByte()
{
    Log("TestByte function entered");
    //test to see if i get anything, not sure how to do this
    byte[] data = Request.Content.ReadAsByteArrayAsync().Result;
    byte[] test = Convert.FromBase64String(payload);

    if(test == null || test.Length <= 0)
    {
        Log("No Payload");
        return NotFound();
    }

    if (data == null || data.Length <= 0)
    {
        Log("No payload");
        return NotFound();
    }

    Log("Payload received");
    return Ok();

}

The MVC side that sends the image looks like this:

// Create a request using a URL that can receive a post. 
WebRequest request = WebRequest.Create(url);
// Set the Method property of the request to POST.
request.Method = "POST";

// Create POST data and convert it to a byte array.
byte[] byteArray = GetImageData(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, content, barcodeUri));
string base64String = Convert.ToBase64String(byteArray);
byte[] dataArray = Encoding.Default.GetBytes(base64String);

// Set the ContentType property of the WebRequest.
request.ContentType = "multipart/form-data";
// Set the ContentLength property of the WebRequest.
request.ContentLength = dataArray.Length;

// Get the request stream.
Stream dataStream = request.GetRequestStream();
// Write the data to the request stream.
dataStream.Write(dataArray, 0, dataArray.Length);
// Close the Stream object.
dataStream.Close();

// Get the response.
WebResponse response = request.GetResponse();
// Get the stream containing content returned by the server.
dataStream = response.GetResponseStream();
// Open the stream using a StreamReader for easy access.
StreamReader reader = new StreamReader(dataStream);
// Read the content.
string responseFromServer = reader.ReadToEnd();
// Clean up the streams.
reader.Close();
dataStream.Close();
response.Close();

For some reason I always get an 404 WebException on

WebResponse response = request.GetResponse();

I have checked that the URL should be right. Is it how I format the URL for post or am I making some other mistake?

Edit, added webconfig routing:

public static void Register(HttpConfiguration config)
{
    // Web API configuration and services

    // Web API routes
    config.MapHttpAttributeRoutes();

    config.Routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{action}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );
}
Muhammed Shevil KP
  • 1,404
  • 1
  • 16
  • 21
steuf
  • 11
  • 1
  • 1
  • 3
  • Have a look [here](http://stackoverflow.com/questions/10320232/how-to-accept-a-file-post-asp-net-mvc-4-webapi) – Wim Ombelets Jan 23 '17 at 12:33
  • You're setting the content as multipart/form-data but the stream isn't encoded as multipart, check this: http://stackoverflow.com/questions/566462/upload-files-with-httpwebrequest-multipart-form-data – Gusman Jan 23 '17 at 12:42

2 Answers2

5

You could use multipart/form-data to transmit the file. Here's an example of how you could read the contents of the uploaded file in your Web API action:

[HttpPost]
[Route("api/upload")]
public async Task<IHttpActionResult> Upload()
{
    if (!Request.Content.IsMimeMultipartContent())
    {
        return this.StatusCode(HttpStatusCode.UnsupportedMediaType);
    }

    var filesProvider = await Request.Content.ReadAsMultipartAsync();
    var fileContents = filesProvider.Contents.FirstOrDefault();
    if (fileContents == null)
    {
        return this.BadRequest("Missing file");
    }

    byte[] payload = await fileContents.ReadAsByteArrayAsync();
    // TODO: do something with the payload.
    // note that this method is reading the uploaded file in memory
    // which might not be optimal for large files. If you just want to
    // save the file to disk or stream it to another system over HTTP
    // you should work directly with the fileContents.ReadAsStreamAsync() stream

    return this.Ok(new
    {
        Result = "file uploaded successfully",
    });
}

and now writing a client is a trivial task using the HttpClient:

class Program
{
    private static readonly HttpClient client = new HttpClient();

    static void Main()
    {
        string responsePayload = Upload().GetAwaiter().GetResult();
        Console.WriteLine(responsePayload);
    }

    private static async Task<string> Upload()
    {
        var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost:8180/api/upload");
        var content = new MultipartFormDataContent();

        byte[] byteArray = ... get your image payload from somewhere
        content.Add(new ByteArrayContent(byteArray), "file", "file.jpg");
        request.Content = content;

        var response = await client.SendAsync(request);
        response.EnsureSuccessStatusCode();

        return await response.Content.ReadAsStringAsync();
    }
}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • For some reason I'm getting a 404 on `response.EnsureSuccessStatusCode();`. The URL I'm calling is `http://localhost:49289/api/upload`, I can see the localhost:49289 running in IIS express. I basically copied your code, no idea why I can't access the function in my web API. – steuf Jan 23 '17 at 13:02
  • Are you using attribute based routing on your Web API? In my example I did but this assumes that you have properly configured it. If you are using global routing then adapt the code to your scenario, don't just blindly copy-paste. – Darin Dimitrov Jan 23 '17 at 13:18
  • I have attribute based routing enabled in the Web API, I editted my original question to show my WebApiConfig – steuf Jan 23 '17 at 13:36
  • Try removing the global route from your config. You should not mix attribute with global routing. – Darin Dimitrov Jan 23 '17 at 13:38
  • I removed the global route and it's still not working. I still get a 404, but when I call the URL as a GET request from the browser I get `{"Message":"The requested resource does not support http method 'GET'."}`, it looks like it is there. – steuf Jan 23 '17 at 13:49
  • Hard to say why your code doesn't work. I have created a brand new Web API project, enabled attribute based routing and this code worked without issues for me. – Darin Dimitrov Jan 23 '17 at 14:11
0

Delete string payload parameter from the TestByte method. It causes error. You are getting the data by Request.Content.ReadAsByteArrayAsync method. You dont need payload object. If your routing is correct it has to work like this.

Edit: Can you change routeTemplate like this?

routeTemplate: "api/{controller}/{id}"
Django
  • 222
  • 1
  • 5
  • You are right about the payload parameter, I added it to test if it would change something, I removed it again. I'm using `Request.Content.ReadAsByteArrayAsync` but I think there is a problem with my routing. I'm not sure what to look for in my routing to resolve the issue. It should work, but when posting data I get a 404. – steuf Jan 23 '17 at 14:44