web-dev-qa-db-fra.com

Retour de CSV à partir du contrôleur .NET Core

J'ai du mal à résoudre un point de terminaison du contrôleur API .NET Core en un téléchargement CSV. J'utilise le code suivant que j'ai extrait d'un contrôleur .NET 4.5:

[HttpGet]
[Route("{id:int}")]
public async Task<HttpResponseMessage> Get(int id)
{
    string csv = await reportManager.GetReport(CustomerId, id);
    var response = new HttpResponseMessage(HttpStatusCode.OK);
    response.Content = new StringContent(csv);
    response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv");
    response.Content.Headers.ContentDisposition = 
        new ContentDispositionHeaderValue("attachment") { FileName = "report.csv" };
    return response;
}

Lorsque j'atteins ce point de terminaison depuis mon application Angular 4, je reçois la réponse suivante écrite dans le navigateur:

{
    "version": {
        "major": 1,
        "minor": 1,
        "build": -1,
        "revision": -1,
        "majorRevision": -1,
        "minorRevision": -1
    },
    "content": {
        "headers": [
            {
                "key": "Content-Type",
                "value": [
                    "text/csv"
                ]
            },
            {
                "key": "Content-Disposition",
                "value": [
                    "attachment; filename=11.csv"
                ]
            }
        ]
    },
    "statusCode": 200,
    "reasonPhrase": "OK",
    "headers": [ ],
    "requestMessage": null,
    "isSuccessStatusCode": true
}

Je m'attends à ce que lorsque j'atteins le point final, l'utilisateur soit invité à télécharger le CSV.

J'ai trouvé this post sur la façon d'exporter un CSV dans .NET Core. Le problème est que je récupère le CSV en mémoire à partir de sa source (un compartiment AWS S3) alors que ce code semble fonctionner uniquement lorsque vous avez un IEnumerable<object>.

Je me demande si mon problème réside dans les en-têtes de demande ou de réponse, où il y a quelque chose qui empêche le navigateur de récupérer un CSV de mon API. Voici ce que je vois dans la console du navigateur:

enter image description here

14
im1dermike

La bonne façon de forcer le téléchargement d'un fichier au lieu de l'afficher en ligne consiste à utiliser Content-Disposition en-tête de réponse. Bien que la solution ci-dessous fonctionne ( voir documentation ), il a été souligné que cela peut avoir des effets secondaires involontaires.


Ancienne réponse

Réglage du Content-Type en-tête de réponse à application/octet-stream forcera la plupart des principaux navigateurs à inviter l'utilisateur à enregistrer le fichier au lieu de l'afficher dans la fenêtre.

Essayez de faire quelque chose comme ça:

var result = new FileContentResult(myCsvByteArray, "application/octet-stream");
result.FileDownloadName = "my-csv-file.csv";
return result;

Voir ma réponse à cette question similaire pour plus d'informations

4
Neil

Solution: utilisez FileResult

Cela doit être utilisé si vous souhaitez que le client obtienne la boîte de dialogue " Enregistrer le fichier ".

Il existe une variété de choix ici, tels que FileContentResult, FileStreamResult, VirtualFileResult, PhysicalFileResult; mais ils dérivent tous de FileResult - nous allons donc aller avec celui-ci pour cet exemple.

public async Task<FileResult> Download()
{
    string fileName = "foo.csv";
    byte[] fileBytes = ... ;

    return File(fileBytes, "text/csv", fileName); // this is the key!
}

Ce qui précède fonctionnera également si vous utilisez public async Task<IActionResult> si vous préférez utiliser cela à la place.

La clé est que vous renvoyez un type File.

Extra: Contenu-Disposition

FileResult fournira automatiquement le Content-Disposition en-tête à attachment.

Si vous souhaitez ouvrir le fichier dans le navigateur ("en ligne"), au lieu d'inviter la boîte de dialogue "Enregistrer le fichier" ("pièce jointe"). Ensuite, vous pouvez le faire en modifiant le Content-Disposition valeur d'en-tête.

Prenons par exemple, nous voulons afficher le fichier PDF dans le navigateur.

public IActionResult Index()
{
    byte[] contents = FetchPdfBytes();
    Response.AddHeader("Content-Disposition", "inline; filename=test.pdf");
    return File(contents, "application/pdf");
}

Créditez cela Réponse SO


Formateurs personnalisés

Les formateurs personnalisés sont un excellent choix en général, car ils permettent au client de demander le type de données souhaité, comme le JSON le plus populaire ou le XML moins populaire.

Cela fonctionne principalement en servant le contenu spécifié dans l'en-tête Accept que le client transmet au serveur, tel que CSV, XLS, XML, JSON, etc.

Vous souhaitez utiliser un type de format de "text/csv" mais il n'y a pas de formateur prédéfini pour cela, vous devrez donc l'ajouter manuellement aux collections de formateurs d'entrée et de sortie:

services.AddMvc(options =>
{
    options.InputFormatters.Insert(0, new MyCustomInputFormatter());
    options.OutputFormatters.Insert(0, new MyCustomOutputFormatter());
});

Formateur personnalisé très simple

Voici une version très simple d'un formateur personnalisé, qui est une version allégée fournie avec exemple Microsoft Docs .

public class CsvOutputFormatter : TextOutputFormatter
{
    public CsvOutputFormatter()
    {
        SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/csv"));
        SupportedEncodings.Add(Encoding.UTF8);
        SupportedEncodings.Add(Encoding.Unicode);
    }

    protected override bool CanWriteType(Type type)
    {
        return true; // you could be fancy here but this gets the job done.
    }

    public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
    {
        var response = context.HttpContext.Response;

        // your magic goes here
        string foo = "Hello World!";

        return response.WriteAsync(foo);
    }
}

Forcer un format particulier

// force all actions in the controller
[Produces("text/csv")]
public class FooController
{
    // or apply on to a single action
    [Produces("text/csv")]
    public async Task<IActionResult> Index()
    {
    }
}  

Pour plus d'informations, je vous recommande de lire:

14
Svek