views:

162

answers:

2

Hi all,

I'm currently generating and returning a fairly large file from a controller on a button click from a view. What i'd like to be able to do is display an overlay stating "Generating File" while the file is being generated and once it's done have the overlay disappear. How would i go about doing something like this?

Here's a sample of what my controller looks like.

  public ActionResult Generate(FormViewModel fvm)
  {
        var isValid = AreInputsValid(fvm);
        if (!isValid)
        {
            TryUpdateModel(fvm);
            return View("Index", );
        }
        RenderReport(new Report(fvm));
        return View();
    }

    private void RenderReport(Models.Report report)
    {
        var localReport = new LocalReport { ReportPath = report.ReportPath };

        var reportDataSource = new ReportDataSource(report.DataSourceName, report.Model);
        localReport.DataSources.Add(reportDataSource);

        var reportType = "PDF";
        string mimeType;
        string encoding;
        string fileNameExtension;

        //The DeviceInfo settings should be changed based on the reportType
        //http://msdn2.microsoft.com/en-us/library/ms155397.aspx
        var deviceInfo =
            string.Format("<DeviceInfo><OutputFormat>{0}</OutputFormat><PageWidth>11in</PageWidth><PageHeight>8.5in</PageHeight><MarginTop>0.5in</MarginTop><MarginLeft>0.25in</MarginLeft><MarginRight>0.25in</MarginRight><MarginBottom>0.5in</MarginBottom></DeviceInfo>", reportType);

        Warning[] warnings;
        string[] streams;

        //Render the report
        var renderedBytes = localReport.Render(
            reportType,
            deviceInfo,
            out mimeType,
            out encoding,
            out fileNameExtension,
            out streams,
            out warnings);

        //Clear the response stream and write the bytes to the outputstream
        //Set content-disposition to "attachment" so that user is prompted to take an action
        //on the file (open or save)
        Response.Clear();
        Response.ContentType = mimeType;
        Response.AddHeader("content-disposition", "attachment; filename=" + report.ReportName + "." + fileNameExtension);
        Response.BinaryWrite(renderedBytes);
        Response.End();
    }

Thanks in advance

+1  A: 

I would use the jquery blockUI to display the overlay and in a div have your "Generating File" message. You can pop this onClientClick and it will remain until you come back from the server

I always put this block of code on pages that I use blockUI on It is helpful when for some reason you have to come back from the server and display a modal, otherwise it will hide the modal that was previously visible.

function pageLoad(event, args)
{
    var hdf = $('[id$=hdf_DisplayModal]').val();

    if(hdf != "")
        showPopup(hdf);
    else
        $.unblockUI();
}

I then have an external jasvascript file that is attached which contains the following:

function showPopup(modal)
{
    showPopup(modal, null);
}

function showPopup(modal, fadeInTime)
{
    if(fadeInTime == null)
        fadeInTime = 500;

    modal = $('[id$=' + modal + ']');
    $.blockUI({ message: modal,
        fadeIn : fadeInTime,
        css: {
            top:  ($(window).height() - modal.height())/2  + 'px', 
            left: ($(window).width() - modal.width()) /2 + 'px', 
            width: modal.width() + 'px'
        }
    });
}

function closePopup(modal)
{
    closePopup(modal, null);
}

function closePopup(modal, fadeOutTime)
{
    $('[id$=hdf_DisplayModal]').attr("value", "");
    modal = $('[id$=' + modal + ']')
    $.unblockUI({ message: modal,
        fadeOut: fadeOutTime
    });
}

Now I have never done any of this in MVC, but from what I hear from a co-worker all this should be possible after a little tweaking. I hope it helps.

jmein
I've tried this but once the file is returned the page still displays the div. I'll update my post with the code from the controller. Maybe that'll help
zSysop
+1  A: 

The UI is the least of your problems here.

If you have an action which takes multiple seconds to execute, then you are tying up ASP.NET worker threads in crushing the scalability of your site. This is why MVC 2 has AsyncControllers for delegating long-running tasks to a background thread. There's also a workaround for MVC 1.

Also, your action should not write to Response. Doing so makes the action unnecessarily difficult to test, and deviates from the standard MVC pipeline, where a result object like ViewResult does the actual writing to Response.

Finally, when you're ready to update the UI, you would typically do this with a callback.

E.g., start with the "loading message in your original View:

<div id="report">Loading...</div>

Then load in the ready event, and replace the "Loading..." with the content of the response:

$(document).ready(function() {
    $("#report").load("/path/to/action");
});
Craig Stuntz
I'm sorry i still don't understand something. If writing to the response stream is a no no, then how would you recommend i go about returning the generated file? I'm new to mvc so any help would be appreciated.
zSysop
One way would be to write a custom Result type. There's a sample here: http://haacked.com/archive/2008/05/10/writing-a-custom-file-download-action-result-for-asp.net-mvc.aspx The general idea is that the Action passes data to the Result, and the Result updates the Response.
Craig Stuntz