0

We are trying to create Excel-import feature to our website where we provide some templates that the users can fill out and then we filter out the information and post it in our database eventually.

First they download the template, fill it out and then upload it to the page. Then we go to the controller and fill out the information in our model (this works) and we print it on the page as a formatted table. Then when they confirm that the table we showed matched their Excel-document they click the "ConfirmCustomOrder"-button.

The problem: How do we get the information from the model we posted back in our controller? We would want to use the Model.CustomOrderList just as it is in the model to later validate the information and post it to our database and complete the import. But when we press the "ConfirmCustomOrder"-button we get that our ExcelModel (parameter in the controller) is null.

Model

public class ExcelModel
{
    // Custom class with all Custom Order fields.
    public IEnumerable<CustomOrderRow> CustomOrderList { get; set; }
    // Custom class with all Purchase Order fields.
    public IEnumerable<PurchaseOrderRow> PurchaseOrderList { get; set; }
    public bool TriggerOnLoad { get; set; }
    public string TriggerOnLoadMessage { get; set; }
}

Controller

public class ExcelController : Controller
{
    // GET: Excel
    public ActionResult Excel()
    {
        if (Session["myID"] == null)
        {
            return ExpireSession();
        }

        var model = new ExcelModel
        {
            CustomOrderList = null,
            PurchaseOrderList = null,
            TriggerOnLoad = false,
            TriggerOnLoadMessage = string.Empty
        };

        return View(model);
    }

    [HttpPost]
    public ActionResult Excel(FormCollection formCollection, ExcelModel model)
    {
        if (Session["myID"] == null)
        {
            return ExpireSession();
        }

        if (!ModelState.IsValid)
            return View(model);

        // If the user confirmed the information displayed.
        if (Request.Form.AllKeys.Contains("ConfirmCustomOrder"))
        {
            return ConfirmExcelDocument(model);
        }

        // Otherwise assume the user is trying to upload a new file.
        model = new ExcelModel
        {
            CustomOrderList = null,
            PurchaseOrderList = null,
            TriggerOnLoad = false,
            TriggerOnLoadMessage = string.Empty
        };

        // Read excel file and create the list in the ExcelModel object.
        var file = Request?.Files["UploadedFile"];

        if (file == null || (file.ContentLength <= 0) || string.IsNullOrEmpty(file.FileName))
            return View(model);

        using (var package = new ExcelPackage(file.InputStream))
        {
            var currentSheet = package.Workbook.Worksheets;
            var workSheet = currentSheet.First();

            switch (workSheet.Cells[1, 1].Value.ToString().Trim())
            {
                case "Custom Order":
                    // Custom order
                    IterateCustomOrder(model, workSheet);
                    break;
                case "Purchase Order":
                    // Purchase order
                    IteratePurchaseOrder(model, workSheet);
                    break;
                default:
                    model.TriggerOnLoadMessage = "Incorrect file format, please use our template.";
                    model.TriggerOnLoad = true;
                    model.CustomOrderList = null;
                    model.PurchaseOrderList = null;
                    return View(model);
            }
        }

        return View(model);
    }

    [HttpPost]
    public ActionResult ConfirmExcelDocument(ExcelModel model)
    {
        // Later we want to further validate the information here. Currently we only print the TriggerOnLoadMessage on the screen after the "Confirm"-button is pressed.
        model = new ExcelModel
        {
            TriggerOnLoad = true,
            TriggerOnLoadMessage = "YOU PRESSED THE CONFIRM BUTTON!",
            PurchaseOrderList = null
        };
        return View(model);
    }
}

View

@{
    if (Model.TriggerOnLoad && Model.CustomOrderList != null)
    {
        using (Html.BeginForm("Excel", "Excel", FormMethod.Post))
        {
            <input id="ConfirmCustomOrder" type="submit" class="standardbutton" name="ConfirmCustomOrder" value="Confirm Custom Order" formaction="Excel" />
        }

            <table id="logtable">
                <thead>
                    <tr>
                        <th>Order</th>
                        <th>Customer</th>
                        <th>Line</th>
                        <th>Product</th>
                        <th>Quantity</th>
                        <th></th>
                    </tr>
                </thead>
             <tbody>
             @foreach (var item in Model.CustomOrderList)
             {
                 <tr>
                     <td>@Html.DisplayFor(modelItem => item.OrderNr)</td>
                     <td>@Html.DisplayFor(modelItem => item.Name)</td>
                     <td>@Html.DisplayFor(modelItem => item.Line)</td>
                     <td>@Html.DisplayFor(modelItem => item.ItemId)</td>
                     <td>@Html.DisplayFor(modelItem => item.Quantity)</td>
                     <td><input type="button" class="standardbutton extra-info-button" name="answer" value="Info" onclick="showDiv(@item.RowNum.ToString())" /></td>
                 </tr>
                 <tr>
                     <td id="@item.RowNum.ToString()" style="display: none;" class="answer_list" colspan="5">
                     <pre>
                         <strong>OrderNr:</strong> @item.OrderNr
                         <strong>Customer:</strong> @item.Customer 
                         <strong>OrderLines:</strong> @item.OrderLines 
                         // ...and a lot more fields in the model. 
                     </pre> <br />
                 </td>
              </tr>
           }
        </tbody>
     </table>
  }

Edit 1.

Picture of the problem.

Danieboy
  • 4,393
  • 6
  • 32
  • 57
  • 1
    You didn't put any form fields on the page. What exactly would you expect to be posted? – Sam Axe Dec 12 '16 at 12:20
  • @SamAxe The model that I used to print the table below. Just literally the model is what I need together with the button name that I pressed. Maybe I don't need the "beginForm". But I still want the model information posted back to my controller. – Danieboy Dec 12 '16 at 12:52
  • Why not try using the session object to hold the list of data. Once the user confirms the data table, use logic within your httpPost to create a model and populate it with the data in your session object. Foreach through your list and save each record to your database. This will help you work around your null reference exception. – AbdulG Dec 12 '16 at 13:10
  • @AbdulG I don't want to save the information again and then put it back together in my list in the model. I just want to send the model.CustomOrderList back to the controller intact. – Danieboy Dec 12 '16 at 13:36
  • @Danieboy from my experience, your _two_ easiest options here are either to use a global variable to hold your data (using Session object) or create your model within your HttpPost using the ID's of the records displayed in the view. Using Session being the easier of the two – AbdulG Dec 12 '16 at 14:05

2 Answers2

1

you cant send the excel model information , because your BeginForm is only wrapping the button.

anyway, i think even if you do, you will have a problem because the model binder needs an index to bind a collection. look at this stackoverflows entries.

MVC post a list of complex objects

MVC Form not able to post List of objects

How can I post a list of items in MVC

Community
  • 1
  • 1
manu_rit
  • 54
  • 5
  • None of those threads were actually about posting back the model object and using the already loaded objects information in the controller. – Danieboy Dec 12 '16 at 13:39
  • but is your View Strongly Typed? because if it is, that is automatically done by the framework. – manu_rit Dec 12 '16 at 14:09
1

So I realized that there is actually no good way of doing what I was planning. The alternatives around the web were using hidden fields or binding to index and then re-building the list etc... Both scale really poorly from what I understand. And since it's an Excel sheet with 50+ columns and the possibility of endless rows it didn't seem optimal.

I ended up using Session (Thanks to @AbdulG) to store my Model.CustomOrderList to get pretty much the result that I was looking for (not having to re-build my list after it's posted in the view from the model).

Controller

[HttpPost]
public ActionResult Excel(FormCollection formCollection)
{
    if (Session["myID"] == null)
    {
        return ExpireSession();
    }
    if (Request.Form.AllKeys.Contains("ConfirmCustomOrder"))
    {
        return ConfirmExcelDocument();
    }

    var model = new ExcelModel
    {
        CustomOrderList = null,
        PurchaseOrderList = null,
        TriggerOnLoad = false,
        TriggerOnLoadMessage = string.Empty
    };

    if (!ModelState.IsValid)
        return View(model);

    var file = Request?.Files["UploadedFile"];

    if (file == null || (file.ContentLength <= 0) || string.IsNullOrEmpty(file.FileName))
        return View(model);

    using (var package = new ExcelPackage(file.InputStream))
    {
        var currentSheet = package.Workbook.Worksheets;
        var workSheet = currentSheet.First();

        switch (workSheet.Cells[1, 1].Value.ToString().Trim())
        {
            case "Custom Order":
                // Custom order
                IterateCustomOrder(model, workSheet);
                Session["CustomOrderList"] = model.CustomOrderList;
                break;
            case "Purchase Order":
                // Purchase order
                IteratePurchaseOrder(model, workSheet);
                Session["PurchaseOrderList"] = model.PurchaseOrderList;
                break;
            default:
                model.TriggerOnLoadMessage = "Incorrect file format, please use our template.";
                model.TriggerOnLoad = true;
                model.CustomOrderList = null;
                model.PurchaseOrderList = null;
                return View(model);
        }
    }

    return View(model);     
}

[HttpPost]
public ActionResult ConfirmExcelDocument()
{
    var model = new ExcelModel
    {
        TriggerOnLoad = true,
        TriggerOnLoadMessage = string.Empty,
        PurchaseOrderList = null,
        CustomOrderList = null
    };

    if (Session["CustomOrderList"] == null)
        return View(model);

    var list = (IEnumerable<CustomOrderRow>)Session["CustomOrderList"];

    Session["CustomOrderList"] = null;

    // ... Do some validating etc. 

    return View(model);
}
Danieboy
  • 4,393
  • 6
  • 32
  • 57