3

I have a controller action which should return a FileStreamResult.
This action is triggered by a button click on a "Downloads" page.

 public class SoftwareUpdatesDownloadController : BaseController
    {
        public virtual async Task<ActionResult> Entity(EntityModel entity, int containerSize = 0)
        {
           //some action preparation & validation code, omitted for clarity
           Stream stream = await GetFileStream(model.Updates[GetUpdateNumberFromQueryString()].UpdateLink).ConfigureAwait(false);   

           return File(stream, "application/octet-stream",GetFileNameFromLink(model.Updates[GetUpdateNumberFromQueryString()].UpdateLink)); 
           //return new FileStreamWithLengthResult(stream, "application/octet-stream", GetFileNameFromLink(model.Updates[GetUpdateNumberFromQueryString()].UpdateLink));<--same behaviour
        }
    }

When this code is executed, an error is thrown: enter image description here

And the browser downloads a text file which contains an html of the current page (with the above error message text in the middle).

I tried using a custom action result, which results in the same behaviour, the only difference being that I see more exactly where the error is thrown:

public class FileStreamWithLengthResult : ActionResult
    {
        private Stream stream;
        private string mimeType;
        private string fileName;
        private long contentLength;

        public FileStreamWithLengthResult(Stream stream,string mimeType,string fileName)
        {
            this.stream = stream;
            this.mimeType = mimeType;
            this.fileName = fileName;
            this.contentLength = stream.Length;
        }

        public override void ExecuteResult(ControllerContext context)
        {
            var response = context.HttpContext.Response;
            response.BufferOutput = false;
            response.Headers.Add("Content-Type", mimeType);
            response.Headers.Add("Content-Length", contentLength.ToString());
            response.Headers.Add("Content-Disposition", "attachment; filename=" + fileName);
            using (stream)
            {
                stream.CopyTo(response.OutputStream); //the error is thrown here (output stream is not available when a custom TextWriter is used
            }
            response.End();
        }
    }

As far as I understand DXA (which is little), the regions of the page are rendered using the @Html.DxaRegion() and @Html.DxaEntities()helpers - and these are the custom TextWriters - and when these are in use, it is not possible to access the output stream of the HTTP response.

Therefore, the question is - how should the files for download be served from webapps which use DXA?

Also, just to be sure that there is no problem with the stream I am returning, I have used the very same code in a blank MVC app, and there it works just fine.

3 Answers 3

2

But why do you inherit from DXA's BaseController? Why not have a "regular MVC controller" which would return the relevant data?

I think you should approach this the following way:

  1. Render the download page using DXA
  2. The page has a download button which would target your custom controller which would return the data as a stream
1

Did you have a look at the Example Site / Core Module? It also supports Downloadable content, but not using anything near to what you are trying to do.

See, for example:

No custom Controller used here at all; this just renders HTML like any other View. The downloadable (binary) content is handled through DXA’s Static Content mechanism: https://github.com/sdl/dxa-web-application-dotnet/blob/master/Sdl.Web.Mvc/Statics/StaticContentModule.cs

1

I think Atila's answer is your best bet.

I think the problem you're having is that DXA is involved quite early on in the pipeline.

It's getting the page content from Tridion, building up the page then handing over to the Entity method in that controller - so the HTML response is already partly built in a way.

You cant then change that response to a different type.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.