22

I am not able to figure out, how to write a .NET Core Web API to support File Upload. Please note I am not using ASP.NET Core MVC form for file upload but via a Servlet/JSP container. Here is how my project.json is defined,

{
  "dependencies": {
    "Microsoft.NETCore.App": {
      "version": "1.0.1",
      "type": "platform"
    },
    "Microsoft.AspNetCore.Mvc": "1.0.1",
    "Microsoft.AspNetCore.Routing": "1.0.1",
    "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
    "Microsoft.AspNetCore.Server.Kestrel": "1.0.1",
    "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",
    "Microsoft.Extensions.Configuration.FileExtensions": "1.0.0",
    "Microsoft.Extensions.Configuration.Json": "1.0.0",
    "Microsoft.Extensions.Logging": "1.0.0",
    "Microsoft.Extensions.Logging.Console": "1.0.0",
    "Microsoft.Extensions.Logging.Debug": "1.0.0",
    "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0",
    "Npgsql": "3.1.9",
    "CoreCompat.Newtonsoft.Json": "9.0.2-beta001",
    "Newtonsoft.Json": "9.0.1"
  },

  "tools": {
    "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final"
  },

  "frameworks": {
    "netcoreapp1.0": {
      "imports": [
        "dotnet5.6",
        "portable-net45+win8"
      ]
    }
  },

  "buildOptions": {
    "emitEntryPoint": true,
    "preserveCompilationContext": true
  },

  "runtimeOptions": {
    "configProperties": {
      "System.GC.Server": true
    }
  },

  "publishOptions": {
    "include": [
      "wwwroot",
      "**/*.cshtml",
      "appsettings.json",
      "web.config"
    ]
  },

  "scripts": {
    "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
  }
}

Here is how my Startup is defined,

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

using QCService.Models;

namespace QCService
{
    public class Startup
    {
        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                .AddEnvironmentVariables();
            Configuration = builder.Build();
        }

        public IConfigurationRoot Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Add framework services.
            services.AddMvc();

            //Add SurveysRepository for Dependency Injection
            services.AddSingleton<ISurveysRepository, SurveysRepository>();
            //Add FeedbacksRepository for Dependency Injection
            services.AddSingleton<IFeedbacksRepository, FeedbacksRepository>();
            //Add AttachmentsRepository for Dependency Injection
            services.AddSingleton<IAttachmentsRepository, AttachmentsRepository>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            app.UseMvc();
        }
    }
}

Finally here is how my Controller is defined,

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
//Including model classes for Attachments
using QCService.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Hosting;
using System.IO;
using Microsoft.Net.Http.Headers;

// For more information on enabling Web API for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860

namespace QCService.Controllers
{
    [Route("api/[controller]")]
    public class AttachmentsController : Controller
    {
        public IAttachmentsRepository AttachmentItems { get; set; }
        public AttachmentsController(IAttachmentsRepository attachmentItems)
        {
            AttachmentItems = attachmentItems;
        }

        // GET: api/Attachments
        [HttpGet] /*http://localhost:52770/api/Attachments*/
        public IEnumerable<Attachments> Get()
        {
            return AttachmentItems.GetAllAttachments();
        }

        // GET api/Attachments/5
        [HttpGet("{id}")] /*http://localhost:52770/api/Attachments/{AttachmentID}*/
        public Attachments Get(int id)
        {
            return AttachmentItems.GetAttachment(id);
        }

        // GET api/Attachments/5
        [HttpGet("Feedback/{id}")] /*http://localhost:52770/api/Attachments/Feedback/{FeedbackID}*/
        public IEnumerable<Attachments> GetFeedbackAttachments(int id)
        {
            return AttachmentItems.GetFeedbackAttachments(id);
        }

        // POST api/Attachments
        [HttpPost]/*http://localhost:52770/api/Attachments/*/
        public async Task<IActionResult> PostFiles(ICollection<IFormFile> files)
        {
            try
            {
                System.Console.WriteLine("You received the call!");
                WriteLog("PostFiles call received!", true);
                //We would always copy the attachments to the folder specified above but for now dump it wherver....
                long size = files.Sum(f => f.Length);

                // full path to file in temp location
                var filePath = Path.GetTempFileName();
                var fileName = Path.GetTempFileName();

                foreach (var formFile in files)
                {
                    if (formFile.Length > 0)
                    {
                        using (var stream = new FileStream(filePath, FileMode.Create))
                        {
                            await formFile.CopyToAsync(stream);
                            //formFile.CopyToAsync(stream);
                        }
                    }
                }

                // process uploaded files
                // Don't rely on or trust the FileName property without validation.
                //Displaying File Name for verification purposes for now -Rohit

                return Ok(new { count = files.Count, fileName, size, filePath });
            }
            catch (Exception exp)
            {
                System.Console.WriteLine("Exception generated when uploading file - " + exp.Message);
                WriteLog("Exception generated when uploading file - " + exp.Message, true);
                string message = $"file / upload failed!";
                return Json(message);
            }
        }

        /// <summary>
        /// Writes a log entry to the local file system
        /// </summary>
        /// <param name="Message">Message to be written to the log file</param>
        /// <param name="InsertNewLine">Inserts a new line</param>
        public void WriteLog(string Message, bool InsertNewLine)
        {
            LogActivity ologObject = null;
            try
            {
                string MessageString = (InsertNewLine == true ? Environment.NewLine : "") + Message;
                if (ologObject == null)
                    ologObject = LogActivity.GetLogObject();
                ologObject.WriteLog(Message, InsertNewLine);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Unable to write to the log file : " + ex.Message);
                Console.WriteLine("Stack Trace : " + ex.StackTrace);
            }
        }
    }
}

I have gone through this link, https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads but just can't make it work! Any help is appreciated!!!

I am using the Google's Advanced Rest Client to post the data as follows, This is how I am sending the POST request

I keep getting response failed with following message... Status: 500: Internal Server Error Loading time: 62 ms Response headers 5 Request headers 2 Redirects 0 Timings Content-Type: multipart/form-data; boundary=----WebKitFormBoundary0RKUhduCjSNZOEMN Content-Length: 9106 Source message

POST /api/Attachments HTTP/1.1 HOST: localhost:52770 content-type: multipart/form-data; boundary=----WebKitFormBoundary0RKUhduCjSNZOEMN content-length: 9106

------WebKitFormBoundary0RKUhduCjSNZOEMN Content-Disposition: form-data; name="fileUpload1"; filename="1i4ymeoyov_In_the_Wild_Testing.png" Content-Type: image/png

�PNG

IHDR,I�3(tEXtSoftwareAdobe ImageReadyq�e< iTXtXML:com.adobe.xmp �8 ^2IDATx��] pU����@ a�H� Pe�P8 ��Ȉ��b�̌3�p�q�*�7"�h�+Yf��O�atD��(<�h¦DLXdOH ������=}���}ov8_U�..... ------WebKitFormBoundary0RKUhduCjSNZOEMN--

Jordan Ryder
  • 2,336
  • 1
  • 24
  • 29
Rohit
  • 245
  • 1
  • 2
  • 6

4 Answers4

14

This is the root cause: form-data; name="fileUpload1"

Your name "fileUpload1" has to be "files" which matches the declaration of the action PostFiles(ICollection< IFormFile> files)

Bodom Vu
  • 181
  • 2
  • 5
2

Hi Change the contentype from "multipart/form-data" to "application/octet-stream". Moved inside code block without error. Try the code below that sends the file as binary type:

byte[] fileToSend = File.ReadAllBytes(filePath);
HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
httpWebRequest.Method = "POST";
httpWebRequest.ContentType = "application/octet-stream";
httpWebRequest.ContentLength = fileToSend.Length;              

using (Stream requestStream = httpWebRequest.GetRequestStream())
{
    requestStream.Write(fileToSend, 0, fileToSend.Length);
    requestStream.Close();
}

Hope it will work ..Thanks

It Grunt
  • 3,300
  • 3
  • 21
  • 35
madan
  • 81
  • 6
0

In the request, your URL is not correct. Check the route on the controller, it is [Route("api/[controller]")] and the action method name is PostFiles. So, the correct URL will be http://localhost:52770/api/Attachments/PostFiles

Either correct the URL or rename the action method as Index

Pradeep Kumar
  • 1,281
  • 7
  • 9
  • I tried both ways but no luck. If I use the URL as 'http://localhost:52770/api/Attachments/PostFiles' I get HTTP 404 as response. If I redefine the action method as Index then I get '500: Internal Server Error' as response. Please note I have updated the name of the parameter to files when selecting files in 'Google's ARC' used for posting the request to the .NET Core Web API. – Rohit Feb 16 '17 at 18:31
  • If you use the action method name as Index, are you able to get to the action method for debugging? One more thing, Path.GetTempFileName() creates a temporary file in the user’s temporary folder. The App might not have permission to do so. Try a different path var uploads = Path.Combine(_environment.WebRootPath, "uploads"); foreach (var file in files) { if (file.Length > 0) { using (var fileStream = new FileStream(Path.Combine(uploads, file.FileName), FileMode.Create)) { await file.CopyToAsync(fileStream);} } } – Pradeep Kumar Feb 17 '17 at 04:00
  • 1
    Actually, `api/[controller]` indicates that the action is *not* required and is, in fact, ignored. Otherwise, it would say `api/[controller]/[action]`. Typically with web api's you differentiate between CRUD requests based on the HTTP verb (i.e. GET = read, POST = create, etc.) as opposed to having different routes, so if you come from an MVC background, this may be a bit confusing. – Richard Marskell - Drackir Sep 07 '17 at 21:35
0

I think your issue is due to a bug in chrome 56.

ARC can't send files on new Chrome version: 56.0.2924.76

This issue affects both postman and ChromeRestClient

The simple workaround is enable the Use XHR option and remove the header Content-Type: multipart/form-data.

This works for both postman and chrome

Khalil
  • 1,047
  • 4
  • 17
  • 34