Search code examples
asp.netasp.net-coreasp.net-core-mvcmultipartform-datamultipart

How to Create a Multipart HTTP Response With ASP.NET Core


I would like to create an action method in my ASP.NET Core controller which returns a Multipart HTTP Response containing several files. I know that using a .zip file is the recommended approach for websites but I am considering using such a request for an API.

The examples I have been able to find in the ASP.NET Core samples are to do with multipart HTTP requests when uploading files. In my case, I want to download files.

UPDATE

I've raised the following GitHub issue: #4933


Solution

  • I've written a more generic MultipartResult class which just inherits from ActionResult:

    Usage Example

    [Route("[controller]")]
    public class MultipartController : Controller
    {
        private readonly IHostingEnvironment hostingEnvironment;
    
        public MultipartController(IHostingEnvironment hostingEnvironment)
        {
            this.hostingEnvironment = hostingEnvironment;
        }
    
        [HttpGet("")]
        public IActionResult Get()
        {
            return new MultipartResult()
            {
                new MultipartContent()
                {
                    ContentType = "text/plain",
                    FileName = "File.txt",
                    Stream = this.OpenFile("File.txt")
                },
                new MultipartContent()
                {
                    ContentType = "application/json",
                    FileName = "File.json",
                    Stream = this.OpenFile("File.json")
                }
            };
        }
    
        private Stream OpenFile(string relativePath)
        {
            return System.IO.File.Open(
                Path.Combine(this.hostingEnvironment.WebRootPath, relativePath),
                FileMode.Open,
                FileAccess.Read);
        }
    }
    

    Implementation

    public class MultipartContent
    {
        public string ContentType { get; set; }
    
        public string FileName { get; set; }
    
        public Stream Stream { get; set; }
    }
    
    public class MultipartResult : Collection<MultipartContent>, IActionResult
    {
        private readonly System.Net.Http.MultipartContent content;
    
        public MultipartResult(string subtype = "byteranges", string boundary = null)
        {
            if (boundary == null)
            {
                this.content = new System.Net.Http.MultipartContent(subtype);
            }
            else
            {
                this.content = new System.Net.Http.MultipartContent(subtype, boundary);
            }
        }
    
        public async Task ExecuteResultAsync(ActionContext context)
        {
            foreach (var item in this)
            {
                if (item.Stream != null)
                {
                    var content = new StreamContent(item.Stream);
    
                    if (item.ContentType != null)
                    {
                        content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(item.ContentType);
                    }
    
                    if (item.FileName != null)
                    {
                        var contentDisposition = new ContentDispositionHeaderValue("attachment");
                        contentDisposition.SetHttpFileName(item.FileName);
                        content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
                        content.Headers.ContentDisposition.FileName = contentDisposition.FileName;
                        content.Headers.ContentDisposition.FileNameStar = contentDisposition.FileNameStar;
                    }
    
                    this.content.Add(content);
                }
            }
    
            context.HttpContext.Response.ContentLength = content.Headers.ContentLength;
            context.HttpContext.Response.ContentType = content.Headers.ContentType.ToString();
    
            await content.CopyToAsync(context.HttpContext.Response.Body);
        }
    }