ardalis / ApiEndpoints

A project for supporting API Endpoints in ASP.NET Core web applications.
MIT License
3.13k stars 227 forks source link

Add Custom Response for File Download Endpoints #81

Closed ardalis closed 3 years ago

ardalis commented 3 years ago

Hi Ardalis, please consider adding a custom return type for File types public abstract class WithResponseCustoms : BaseEndpointAsync { public abstract Task HandleAsync( TRequest request, CancellationToken cancellationToken = default ); }

from https://github.com/ardalis/ApiEndpoints/issues/77#issuecomment-829787486

ardalis commented 3 years ago

The only difference between that and what we have:

                public abstract Task<ActionResult<TResponse>> HandleAsync(
                    TRequest request,
                    CancellationToken cancellationToken = default
                );

is the lack of ActionResult<> wrapping the TResponse. That's what you need? The ActionResult is causing problems when you want to return a File?

Looking at this SO thread (https://stackoverflow.com/questions/42460198/return-file-in-asp-net-core-web-api) it looks like there are plenty of ways to use a custom ActionResult to return a file. And the issue seems to be that ApiEndpoints doesn't let you swap out the ActionResult type with FileContentResult or FileStreamResult, correct?

ojrojas commented 3 years ago

If that's correct, sorry not for not making me understand well but I don't speak English. The error occurs when the returns a FileResult or FileStreamResult, the type of parameter returned by the ActionResult does not match, leaving an error of the type ActionResult<T> not defined"

dotnetgeek commented 3 years ago

Hi,

I ran into a similar problem. I would like to return a "FileContentResult" which is an ActionResult at the end. So it will return a ActionResult. This results in the following error:

System.ArgumentException: Invalid type parameter 'Microsoft.AspNetCore.Mvc.FileContentResult' specified for 'ActionResult'.

That's how my code looks like:

    [ApiController]
    [Route("fxrates")]
    public class GetForDate : BaseAsyncEndpoint
        .WithRequest<DateTime>
        .WithResponse<FileContentResult>
    {
        private readonly IService _service;
        private readonly ILogger<GetForDate> _logger;

        public GetForDate(
            IService service,
            ILogger<GetForDate> logger)
        {
            _service = service;
            _logger = logger;
        }

        [HttpGet]
        [Produces("text/plain")]
        public override async Task<ActionResult<FileContentResult>> HandleAsync(
            DateTime request, 
            CancellationToken cancellationToken = default)
        {
            var (filename, content) = await _service.GetFileForDay(DateTime.Now, cancellationToken);
            if (content == null)
                return NotFound();

            return File(content, "text/plain", Path.GetFileName(filename));

        }
    }

What seems to be working is this modification of the code

    [ApiController]
    [Route("fxrates")]
    public class GetForDate : BaseAsyncEndpoint
        .WithRequest<DateTime>
        .WithoutResponse
    {
        private readonly IService _service;
        private readonly ILogger<GetForDate> _logger;

        public GetForDate(
            IService service,
            ILogger<GetForDate> logger)
        {
            _service = service;
            _logger = logger;
        }

        [HttpGet]
        [Produces("text/plain")]
        public override async Task<ActionResult> HandleAsync(
            DateTime request, 
            CancellationToken cancellationToken = default)
        {
            var (filename, content) = await _service.GetFileForDay(DateTime.Now, cancellationToken);
            if (content == null)
                return NotFound();

            return File(content, "text/plain", Path.GetFileName(filename));

        }
    }

Not sure if I like this approach, but it works.

Cheers Daniel

ardalis commented 3 years ago

I've added a sample that shows how to download a file using ApiEndpoints and the fluent generic base classes. It's here:

https://github.com/ardalis/ApiEndpoints/blob/main/sample/SampleEndpointApp/AuthorEndpoints/ListJsonFile.cs

Basically you just need to use WithoutResponse and return File(...)