Over a million developers have joined DZone.

Adding Export Capabilities to the Razor WebGrid

·

When MVC first came out I really missed having a native grid control. Now a few years later, we have grid support again! In case, you didn’t know the Razor WebGrid was included in the System.Web.Helpers assembly which shipped with ASP.NET MVC 3. Over the years, I have sampled a variety of grid controls such as jqGrid, the Infragistics grid control and the MvcContrib grid. Each grid control has its benefits and disadvantages but I think the new WebGrid offers a clean API which makes development tasks simple. Here is a screenshot of what the grid looks like:

Default webGrid

To display a grid on a razor view page, you basically create a new grid object, assign some properties and then call the GetHtml helper method. Here is an example:

@{
    var grid = new WebGrid(canPage: true, canSort: true, rowsPerPage: Model.PagingInfo.PageSize, ajaxUpdateContainerId: "grid");
    grid.Bind(Model.Data, rowCount: Model.PagingInfo.TotalItems, autoSortAndPage: false);       
}

@grid.GetHtml(
    columns: new List<WebGridColumn>() {            
        grid.Column(columnName: "Id", header: "#" ),
        grid.Column(columnName: "Date", header: "Date"),
        grid.Column(columnName: "UniqueId", header: "Unique Id" ),
        grid.Column(columnName: "Text", header: "Random Text" )
    }) 

Of course, displaying a simple table on a web page is never enough for my demanding clients. I needed to add some additional functionality to my grid, In particular I needed to add some exporting options to my grid. To get started I built out an export to CSV feature. By Utilizing the adapter pattern (a fancy way of saying I wrapped the method call) I was easily able to add in my custom html above the grid which has an export link. Here is my GetHtmlEx method: 

public static IHtmlString GetHtmlEx( 
    this WebGrid grid, 
    < a long list of parameters >
    string recordsText = RecordsText, 
    string pageText = PageText,
    string exportText = ExportText)
{
    var sb = new StringBuilder();
    sb.Append(grid.GetExportHtml());
    sb.Append(grid.GetHtml(tableStyle, headerStyle, footerStyle, rowStyle, alternatingRowStyle, selectedRowStyle,
                        caption, displayHeader, fillEmptyRows, emptyRowCellValue, columns, exclusions, mode, 
                        firstText, previousText, nextText, lastText, numericLinksCount, htmlAttributes));
    sb.Append(grid.GetTotalsHtml());
    return MvcHtmlString.Create(sb.ToString());
}        

Nothing too complicated here, just concatenating some HTML together and returning it as one big MvcHtmlString. The GetExportHtml method prints out the links for exporting to CSV. Since my method wraps the native GetHtml method, I just need to modify the view page and add an ‘Ex’ to the end of the @grid.GetHtml method.

@grid.GetHtmlEx(..) 

With the help of my wrapper above, my grid now has some new schizzle:

image

The code that actually exports the data to a CSV file is tucked away in my view model. However, In order to make my design modular and reusable, I created a base class called WebGridViewModel:

public abstract class WebGridViewModel<T>
{
    private IQueryable<T> _superset;        

    protected WebGridViewModel(WebGridSettings settings)
    {
        Settings = settings;                        
    }            
    
    public WebGridSettings Settings { get; set; }        

    public IPagination PagingInfo { 
        get { return Data as IPagination; } 
    }        

    protected abstract IQueryable<T> GetSuperSet();

    protected virtual IEnumerable<object> GetExportData()
    {
        return GetSuperSet() as IEnumerable<object>;
    }

    public ActionResult View( Controller controller, string viewName = "" )
    {
        var shouldExport = !String.IsNullOrWhiteSpace(Settings.ExportTo);            
        if( shouldExport ) return GetExportViewResult();

        controller.ViewData.Model = this;
        var viewResult = new ViewResult() {ViewData = controller.ViewData };

        if (!String.IsNullOrWhiteSpace(viewName))
            viewResult.ViewName = viewName;

        return viewResult;
    }

    private ActionResult GetExportViewResult()
    {
        var name = typeof(T).Name;
        return new CsvActionResult(name + ".csv", GetExportData().ToList());        
    }

    public IEnumerable<T> Data
    {
        get
        {
            if (_superset == null)
                _superset = GetSuperSet();
            
            var sortColumn = String.IsNullOrWhiteSpace(Settings.Sort) ? Settings.DefaultSort : Settings.Sort;
            var sort = String.Format("{0}{1}", sortColumn, String.IsNullOrWhiteSpace(Settings.SortDir) ? "" : " " + Settings.SortDir);
            var ordered = _superset.OrderBy(sort);                
            return ordered.AsPagination(Settings.Page ?? 1, Settings.PageSize);
        }
    }
}

So In order to build the model for my view, the only thing I have to do is inherit from WebGridViewModel and override the GetSuperSet method. I can optionally return a custom result set for the export by overriding the GetExportData method:

public class MyGridViewModel : WebGridViewModel<FakeData>
{
    public MyGridViewModel( WebGridSettings gridSettings )
        : base( gridSettings )
    {
        gridSettings.DefaultSort = "Id";            
    }

    protected override IQueryable<FakeData> GetSuperSet()
    {
        return FakeData.Records;
    }
}

In real life, the GetSuperSet method would hit a database and return some records. However, in order to keep things simple I am using some static data. The Index action of my Home controller takes care of creating the view model and passes it along to the View. Before I added the export feature, my controller action looked like this:

public ActionResult Index( WebGridSettings gridSettings )
{
    var model = new MyGridViewModel(gridSettings);
    return View(model);
}

However, now that my grid supports exporting, I need to support two different action results. A standard ViewResult for displaying the grid on the view page and a custom ActionResult which takes care of building the CSV file and returning it to the client via the response stream. If you look at the implementation details of the WebGridViewModel you may have noticed a method called “View” which basically detects whether or not an export was requested and conditionally returns a regular ViewResult or a CsvActionResult. So the only thing I need to change in the Index method is the return statement:

public ActionResult Index( WebGridSettings gridSettings )
{
    var model = new MyGridViewModel(gridSettings);
    return model.View(this);
}

So now when the Home/Index action is called a standard view is returned. However, when you click on the export hyperlink link an additional query string parameters is added to the url named ExportTo with a value of CSV (Home/Index?ExportTo=CSV). The WebGridViewModel inspects the ExportTo parameter and will automatically determine if a CSV file should be returned. 

Download The Sample Code (468.61 kb)

Topics:

Published at DZone with permission of Michael Ceranski, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}