wootscootinboogie
wootscootinboogie

Reputation: 8705

Extension Method in ASP.NET MVC 4 HttpPost ActionResult

I've got a simple HomeController with one overloaded (GET/POST) Index method which displays data from a list of Person objects in an HTML table and gives the user a form in which they can type a sheet name and a file name and download an Excel file of the results (EPPlus Excel library) and download them. To encapsulate the information needed to save the Excel File I created the following view model:

public class ExcelDownloadView
    {
        public List<Person> Persons { get; set; }
        //ConvertToDataTable is a List extension method return converts the list and its properties
        //into a table where columns are the properties
        public DataTable DataTable { get { return Persons.ConvertToDataTable(); } }
        public string SheetName { get; set; }
        public string FileName { get; set; }
    }

Here's what my view looks like, and on the initial GET request, everything comes out fine:

@model ExportToExcel.Models.ExcelDownloadView
@using ExportToExcel.Helpers
@{
    ViewBag.Title = "Index";

}
<div id="grid">
@Html.ListToTable(Model.Persons)

</div>
<div>
This is the number of rows: @Model.DataTable.Rows.Count (correct)
</div>


    @using (Html.BeginForm())
    {
        <input name="sheetName" type="text" /><br />
        <input name="fileName" type="text" /><br />
        <input type="submit" value="Click to download Excel file..." />
    }

When I try to submit the form I get an error in my ConvertToDataTable method:

 public static DataTable ConvertToDataTable<T>(this List<T> data)
        {
            DataTable dt = new DataTable();
            PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(T));
            for (int i = 0; i < props.Count; i++)
            {
                PropertyDescriptor prop = props[i];
                dt.Columns.Add(prop.Name, prop.PropertyType);
            }
            object[] values = new object[props.Count];
            //*****error is at the start of the foreach loop
            foreach (T item in data)
            {
                for (int i = 0; i < values.Length; i++)
                {
                    values[i] = props[i].GetValue(item);
                }
                dt.Rows.Add(values);
            }
            return dt;
        }

Controller:

[HttpGet]
        public ActionResult Index()
        {
            ExcelDownloadView model = new ExcelDownloadView();
            model.Persons = Person.GetPersons().ToList();
            var dataTable = model.Persons.ConvertToDataTable();
            return View(model);
        }
        [HttpPost]
        public ActionResult Index(ExcelDownloadView viewModel)
        {
            //DataTable property takes the List<Person> of the view model and turns it into 
            //a datable for use in the following Excel-file-creating function

            /*--> if nonWorkingVersion is commented out, the program programs with a NullReferenceException*/
            //DataTable nonWorkingVersion = viewModel.DataTable;
            //if a re-seed the DataTable, everything works fine
            DataTable dt = Person.GetPersons().ToList().ConvertToDataTable();

            using (ExcelPackage pck = new ExcelPackage())
            {
                //Create the worksheet
                ExcelWorksheet ws = pck.Workbook.Worksheets.Add(viewModel.SheetName);

                //Load the datatable into the sheet, starting from cell A1. Print the column names on row 1

                ws.Cells["A1"].LoadFromDataTable(dt, true);
                //Format the header for column 1-3
                using (ExcelRange rng = ws.Cells["A1:C1"])
                {
                    rng.Style.Font.Bold = true;
                    rng.Style.Fill.PatternType = ExcelFillStyle.Solid;                      //Set Pattern for the background to Solid
                    rng.Style.Fill.BackgroundColor.SetColor(Color.FromArgb(79, 129, 189));  //Set color to dark blue
                    rng.Style.Font.Color.SetColor(Color.White);
                }

                //Example how to Format Column 1 as numeric 
                using (ExcelRange col = ws.Cells[2, 1, 2 + dt.Rows.Count, 1])
                {
                    col.Style.Numberformat.Format = "#,##0.00";
                    col.Style.HorizontalAlignment = ExcelHorizontalAlignment.Right;
                }

                //Write it back to the client

                Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
                Response.AddHeader("content-disposition", "attachment;  filename=" + viewModel.FileName);
                Response.BinaryWrite(pck.GetAsByteArray());
            }
            return View(viewModel);
        }

In the GET version of Index method the data is there and correct. Why is the data not showing up correctly in my POST version? If I modify the view and remove references to the Persons property, a diaglog box will pop up and I can download the file I want. Why isn't the POST version receiving the data for Persons from the GET version?

Upvotes: 0

Views: 2178

Answers (1)

Andrei
Andrei

Reputation: 56716

First thing is to realize that these actions are called for two separate requests. First is called when user navigated to the page, and second is called when user saw the page, filled out the form and submitted it.

Next is the fact that in ASP.NET MVC you do not have state. The only info you have is the one posted form client. So even though there was some object created on the previous request, this is object has nothing to do with current request. In your form has two inputs - these are the pieces you get in request.

The correct way to handle this is what you have already implemented. That is if you need some data base querying during the request - generally you need to do it on every request.

Upvotes: 1

Related Questions