So there’s plenty of blog posts/code examples of how to export to excel using ASP.NET MVC. The most common way, is to inherit ActionResult with your own result class that takes in a model. Some example code below:

public class ExcelResult<Model> : ActionResult
    {
        string _fileName;
        string _viewPath;
        Model _model;
        ControllerContext _context;

        public ExcelResult(ControllerContext context, string viewPath, string fileName, Model model)
        {
            this._context = context;
            this._fileName = fileName;
            this._viewPath = viewPath;
            this._model = model;
        }
         protected string RenderViewToString()
        {
            using (var writer = new StringWriter())
            {
                var view = new WebFormView(_context, _viewPath);
                var vdd = new ViewDataDictionary<Model>(_model);
                var viewCxt = new ViewContext(_context, view, vdd, new TempDataDictionary(), writer);
                viewCxt.View.Render(viewCxt, writer);
                return writer.ToString();
            }
        }
        void WriteFile(string content)
        {
            HttpContext context = HttpContext.Current;
            context.Response.Clear();
            context.Response.AddHeader("content-disposition", "attachment;filename=\"" + _fileName + "\"");
            context.Response.Charset = "";
            context.Response.ContentType = "application/ms-excel";
            context.Response.Write(content);
            context.Response.End();
        }

        public override void ExecuteResult(ControllerContext context)
        {
            string content = this.RenderViewToString();
            this.WriteFile(content);
        }
    }

And you’d return this like you would any normal ActionResult like so:

return new ExcelResult<ModelBase>
                (
                    ControllerContext,
                    "Export.aspx",
                    "ExcelExport",
                    myModel
                );

So this works all fine and dandy when you want to take an existing MVC View you have, and have it exported to excel. Obviously there are certain things you are going to keep in mind when you do this. If you want to export an image, you might have to code something extra, or just remove the image tag from the view before the export by manipulating the HTML on the back-end, or just creating a unique view for each one that exists to hold it’s export style. That’s completely up to you.

This same code can be used to export specific HTML elements. You don’t need to have a strong typed view, you can just pass in the view as any basic HTML, and it will export fine – just remember to either create an overload that doesn’t take a model, or create a dummy model to pass it to meet the parameter requirements.

However, what if you have a view, that when loaded, generates it’s HTML dynamically, either through AJAX or something similar. If you were to pass something like that through this export, you’d get a blank export, because no HTML exists yet. It would work, but you wouldn’t get any data. I’ve come up with a simple way of getting something like this to work. Say you have a view that looks like this:

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<ModelBase>"

<div id="results"></div>

<script>
    $(document).ready(function () {
        fillResults();
    });
</script>

…and fillResults() is actually making an AJAX call back to the server and getting data, and then creating the HTML and appending that into the element on the page. If you were to render this view through the Excel Export we talked about earlier, you’d get nothing. When views get rendered this way, posts are not run.

Here’s a simple solution (I know there are probably better ones, but this one seems to work for us, and is pretty clean and cheap).

Create a property on your model to hold the HTML after it is populated on the front end.

public string Results { get; set; }

Create a method on the server called SaveResults(). This would look something like this:

[ValidateInput(false)]
        public ActionResult SaveResults(string html)
        {
            ModelBase model = this.GetCurrentModel();
            model.Results = html;
            return Json("");
        }

The [ValidateInput(false)] is very important. This tells your server to disregard a string that contains markup, and enables you to set a property with the passed in html. Obviously you could perform some extra validation checks on the front end or on the back-end before this happens to make sure you aren’t getting bad data. Now in your view, you also want to place something that tells the framework not to validate posts from this view, so the first line in your view becomes something like this:

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<ModelBase>" ValidateRequest="false" %>

Now, all you have to do is, after the AJAX post/return was successful, make a call to the SaveResults() method on the server using another AJAX call, and pass the contents of the html element on your page. Something like this would do the trick:

function saveResults()
    {
        var results = $("#results").html();
        var params = 
            {
                html : results
            }
        $.ajax({
            type: "POST",
            url: "<%= Url.Action("SaveResults", "MyController") %>",
            data: params,
            success:function(data)
            {
                alert('Results Saved!');
            }
        });
    }

So now you have the html you need to export saved on your model. So what you can do is, when you want to export that particular view, you add a check in your Export method on the controller, like this:

if (viewName.EndsWith("DynamicView.aspx"))
                {
                    viewName = model.Results;
                }

And then in your export you just check whether the view passed in is an html string, or an ASPX view in the beginning of RenderViewToString() above, and if it’s just html, you return the contents immediately:

if (!_viewPath.EndsWith(".aspx"))
            {
                return _viewPath;
            }

…and then the rest will take care of itself :) Cool little trick. You can expand a lot of this stuff to look cleaner. I wrote it out here as more of an example for one view, but as this became something more and more standard in your app, you’d probably want to architect the code a little better.

Hope it helps you out. Thanks guys!

About these ads