0

I use .net core2 ASP.NET Boilerplate.
Because my webapi is dynamic created from application. In application architecture there is no “return File()” method because of not inherit from .net core mvc controller.
So I want implement downloadFile by Writing data to HttpContext Response. In .net4.x, I use this code writing data to response, and it work well. but when I use it in .netcore, it does not work.

    public static void WriteWorkBookToResponse(this IWorkbook book, HttpContext httpContext, string fileName)
    {
        var response = httpContext.Response;

        using (var exportData = new MemoryStream())
        {
            response.Clear();
            book.Write(exportData);
            response.ContentType = "application/vnd.ms-excel";
            response.Headers.Add("Content-Disposition", $"attachment;filename={fileName}.xls");
            response.Body.Write(exportData.GetBuffer());
        }
    }

In logs,this is a error "System.InvalidOperationException: StatusCode cannot be set because the response has already started".
"at Abp.AspNetCore.Security.AbpSecurityHeadersMiddleware.Invoke(HttpContext httpContext) in D:\Github\aspnetboilerplate\src\Abp.AspNetCore\AspNetCore\Security\AbpSecurityHeadersMiddleware.cs:line 26".

I read .net core mvc source code and edit my code. like below:

    public static async void WriteToResponse2(this IWorkbook book, HttpContext httpContext, string templateName)
    {
        var response = httpContext.Response;

        using (var exportData = new MemoryStream())
        {
            book.Write(exportData);
            // response.ContentLength = exportData.GetBuffer().Length;
            response.ContentType = "application/vnd.ms-excel";
            SetContentDispositionHeader(httpContext, templateName);
            await StreamCopyOperation.CopyToAsync(exportData, response.Body, count: null, bufferSize: 64 * 1024, cancel: httpContext.RequestAborted);
        }
    }

    private static void SetContentDispositionHeader(HttpContext httpContext, string fileName)
    {
        if (!string.IsNullOrEmpty(fileName))
        {
            var contentDisposition = new Microsoft.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
            contentDisposition.SetHttpFileName(fileName);
            httpContext.Response.Headers[HeaderNames.ContentDisposition] = contentDisposition.ToString();
        }
    }

Then the api return "406 Error:Not Acceptable", the file downloaded can not open, is damaged.

I need help, I will be thankful for you helping me.

ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
wangzg
  • 13
  • 7

1 Answers1

0

change the WriteToResponse2 method to be :

public static void WriteToResponse2(this IWorkbook book, HttpContext httpContext, string templateName)
{
    var response = httpContext.Response;
    response.ContentType = "application/vnd.ms-excel";
    SetContentDispositionHeader(httpContext, templateName);
    book.Write(response.Body);
}

When used in an action method , be careful not to modify the response again .

public void Test2() {
    var newFile = @"newbook.core.docx";
    using (var fs = new FileStream(newFile, FileMode.Create, FileAccess.Write))
    {
        IWorkbook workbook = CreateBook();
        workbook.WriteToResponse2(contextAccessor.HttpContext,"ss.xlsx");
    }
}

Here's the screenshot it works :

enter image description here

itminus
  • 23,772
  • 2
  • 53
  • 88
  • I use visual studio creating a new core2 web project, I test this code ,find it work well. But in ASP.NET Boilerplate, it failed. I am so depressed and does not known why. It throw a error "System.InvalidOperationException: StatusCode cannot be set because the response has already started" in Logs. Would you help me to test it ? https://aspnetboilerplate.com/Templates. – wangzg Sep 10 '18 at 07:04
  • @wangzg Which boilerplate do you choose ? Or could you please upload your boilerplate project somewhere and remove it after I download it ? – itminus Sep 10 '18 at 07:15
  • @wangzg I cann't find a controller which produces the error of `System.InvalidOperationException: StatusCode cannot be set because the response has already started` . – itminus Sep 10 '18 at 08:12
  • @wangzg It's better to update your code that can reproduce the same error described above . – itminus Sep 10 '18 at 08:19
  • I don`t know what the error means, and How to resolve this error. – wangzg Sep 10 '18 at 08:20
  • @wangzg check your action of controller . make sure it will return `void` . – itminus Sep 10 '18 at 08:25
  • the controller was dynamic created by application method. even if in web.core layer test, it throw same error also. – wangzg Sep 10 '18 at 08:50
  • @wangzg The error means that you're trying to modify the response after the response has already started . In other words , the default behavior of ABP will modify the response after we write the excel file to Response.Body . – itminus Sep 10 '18 at 09:17
  • @wangzg I've never used ABP before . However , it's possible to solve this problem in two ways . 1. Never write the response body directly . Just return a new `FileContentResult` and let the ABP process it instead . That means you cannot write anything to Response.Body . 2. Write a Custom Controller that will return void and let the `IWorkbook.WriteToResponse2` deal with the response . – itminus Sep 10 '18 at 09:25
  • yes, It seems there is no other way to solve this problem. thanks a lot. – wangzg Sep 11 '18 at 00:30