Como posso apresentar um arquivo para download de um controlador MVC?

109

Em WebForms, normalmente teria um código como este para permitir que o navegador apresentasse um pop-up "Baixar arquivo" com um tipo de arquivo arbitrário, como um PDF, e um nome de arquivo:

Response.Clear()
Response.ClearHeaders()
''# Send the file to the output stream
Response.Buffer = True

Response.AddHeader("Content-Length", pdfData.Length.ToString())
Response.AddHeader("Content-Disposition", "attachment; filename= " & Server.HtmlEncode(filename))

''# Set the output stream to the correct content type (PDF).
Response.ContentType = "application/pdf"

''# Output the file
Response.BinaryWrite(pdfData)

''# Flushing the Response to display the serialized data
''# to the client browser.
Response.Flush()
Response.End()

Como faço para realizar a mesma tarefa no ASP.NET MVC?

Mgnoonan
fonte

Respostas:

181

Retorne a FileResultou FileStreamResultda sua ação, dependendo se o arquivo existe ou se você o cria rapidamente.

public ActionResult GetPdf(string filename)
{
    return File(filename, "application/pdf", Server.UrlEncode(filename));
}
Tvanfosson
fonte
14
Este é um ótimo exemplo de por que a ASP.NET MVC é incrível. O que você tinha que fazer anteriormente em 9 linhas de código de aparência confusa pode ser feito em uma linha. Muito mais fácil!
Jon Kruger
Obrigado tvanfosson, Procurei a melhor solução para fazer isso, e isso é ótimo.
Mark Kadlec
1
Isso requer uma extensão de arquivo no nome do arquivo, caso contrário, ele ignorará completamente o nome do arquivo e o tipo de conteúdo e apenas tentará transmitir o arquivo para o navegador. Ele também usará apenas o nome da página da Web se o navegador não reconhecer o tipo de conteúdo (ou seja, fluxo de octeto) quando forçar o download e não terá nenhuma extensão.
RichC
62

Para forçar o download de um arquivo PDF, em vez de ser manipulado pelo plug-in PDF do navegador:

public ActionResult DownloadPDF()
{
    return File("~/Content/MyFile.pdf", "application/pdf", "MyRenamedFile.pdf");
}

Se você quiser que o navegador funcione por padrão (plugin ou download), basta enviar dois parâmetros.

public ActionResult DownloadPDF()
{
    return File("~/Content/MyFile.pdf", "application/pdf");
}

Você precisará usar o terceiro parâmetro para especificar um nome para o arquivo na caixa de diálogo do navegador.

ATUALIZAÇÃO: Charlino está certo, ao passar o terceiro parâmetro (nome do arquivo de download) Content-Disposition: attachment;é adicionado ao Cabeçalho de Resposta Http. Minha solução foi enviar application\force-downloadcomo mime-type, mas isso gera um problema com o nome do arquivo do download, então o terceiro parâmetro é necessário para enviar um bom nome de arquivo, eliminando assim a necessidade de forçar um download .

guzart
fonte
6
Tecnicamente, não é isso que está acontecendo. Tecnicamente, quando você adiciona o terceiro parâmetro, a estrutura MVC adiciona o cabeçalho content-disposition: attachment; filename=MyRenamedFile.pdf- é isso que força o download. Eu sugiro que você coloque o tipo MIME de volta application/pdf.
Charlino 01 de
2
Obrigado Charlino, não percebi que o terceiro parâmetro estava fazendo isso, pensei que fosse apenas para alterar o nome do arquivo.
guzart 01 de
2
+1 para atualizar sua resposta e explicar o terceiro parâmetro + Content-Disposition: attachment;relação.
Charlino 01 de
7

Você pode fazer o mesmo no Razor ou no Controller, assim ..

@{
    //do this on the top most of your View, immediately after `using` statement
    Response.ContentType = "application/pdf";
    Response.AddHeader("Content-Disposition", "attachment; filename=receipt.pdf");
}

Ou no controlador ..

public ActionResult Receipt() {
    Response.ContentType = "application/pdf";
    Response.AddHeader("Content-Disposition", "attachment; filename=receipt.pdf");

    return View();
}

Eu tentei isso no Chrome e no IE9, ambos estão baixando o arquivo pdf.

Provavelmente, devo acrescentar que estou usando o RazorPDF para gerar meus PDFs. Aqui está um blog sobre isso: http://nyveldt.com/blog/post/Introducing-RazorPDF

Rosdi Kasim
fonte
4

Você deve olhar para o método File do Controller. É exatamente para isso que serve. Ele retorna um FilePathResult em vez de um ActionResult.

Martin Peck
fonte
3

mgnoonan,

Você pode fazer isso para retornar um FileStream:

/// <summary>
/// Creates a new Excel spreadsheet based on a template using the NPOI library.
/// The template is changed in memory and a copy of it is sent to
/// the user computer through a file stream.
/// </summary>
/// <returns>Excel report</returns>
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult NPOICreate()
{
    try
    {
        // Opening the Excel template...
        FileStream fs =
            new FileStream(Server.MapPath(@"\Content\NPOITemplate.xls"), FileMode.Open, FileAccess.Read);

        // Getting the complete workbook...
        HSSFWorkbook templateWorkbook = new HSSFWorkbook(fs, true);

        // Getting the worksheet by its name...
        HSSFSheet sheet = templateWorkbook.GetSheet("Sheet1");

        // Getting the row... 0 is the first row.
        HSSFRow dataRow = sheet.GetRow(4);

        // Setting the value 77 at row 5 column 1
        dataRow.GetCell(0).SetCellValue(77);

        // Forcing formula recalculation...
        sheet.ForceFormulaRecalculation = true;

        MemoryStream ms = new MemoryStream();

        // Writing the workbook content to the FileStream...
        templateWorkbook.Write(ms);

        TempData["Message"] = "Excel report created successfully!";

        // Sending the server processed data back to the user computer...
        return File(ms.ToArray(), "application/vnd.ms-excel", "NPOINewFile.xls");
    }
    catch(Exception ex)
    {
        TempData["Message"] = "Oops! Something went wrong.";

        return RedirectToAction("NPOI");
    }
}
Leniel Maccaferri
fonte
1

Embora os resultados de ação padrão FileContentResult ou FileStreamResult possam ser usados ​​para fazer download de arquivos, para reutilização, criar um resultado de ação personalizada pode ser a melhor solução.

Como exemplo, vamos criar um resultado de ação personalizado para exportar dados para arquivos do Excel dinamicamente para download.

A classe ExcelResult herda a classe ActionResult abstrata e substitui o método ExecuteResult.

Estamos usando o pacote FastMember para criar DataTable a partir do objeto IEnumerable e o pacote ClosedXML para criar um arquivo Excel a partir de DataTable.

public class ExcelResult<T> : ActionResult
{
    private DataTable dataTable;
    private string fileName;

    public ExcelResult(IEnumerable<T> data, string filename, string[] columns)
    {
        this.dataTable = new DataTable();
        using (var reader = ObjectReader.Create(data, columns))
        {
            dataTable.Load(reader);
        }
        this.fileName = filename;
    }

    public override void ExecuteResult(ControllerContext context)
    {
        if (context != null)
        {
            var response = context.HttpContext.Response;
            response.Clear();
            response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
            response.AddHeader("content-disposition", string.Format(@"attachment;filename=""{0}""", fileName));
            using (XLWorkbook wb = new XLWorkbook())
            {
                wb.Worksheets.Add(dataTable, "Sheet1");
                using (MemoryStream stream = new MemoryStream())
                {
                    wb.SaveAs(stream);
                    response.BinaryWrite(stream.ToArray());
                }
            }
        }
    }
}

No Controlador, use o resultado da ação ExcelResult personalizado da seguinte forma

[HttpGet]
public async Task<ExcelResult<MyViewModel>> ExportToExcel()
{
    var model = new Models.MyDataModel();
    var items = await model.GetItems();
    string[] columns = new string[] { "Column1", "Column2", "Column3" };
    string filename = "mydata.xlsx";
    return new ExcelResult<MyViewModel>(items, filename, columns);
}

Uma vez que estamos baixando o arquivo usando HttpGet, crie uma Visualização vazia sem modelo e layout vazio.

Postagem de blog sobre o resultado da ação personalizada para o download de arquivos criados dinamicamente:

https://acanozturk.blogspot.com/2019/03/custom-actionresult-for-files-in-aspnet.html

Ahmetcan Ozturk
fonte
-4

Use o tipo de arquivo .ashx e use o mesmo código

0100110010101
fonte