1

I've been struggeling with this one for quite a while. Everything goes as planned until I try to return the values filled in from the View back to the Controller (See below):

View after populating

Using Fiddler, as soon as I submit the data it shows valid html (Please see below:)

4 fields - valid

To my understanding, this should bind to the model. I am posting back to the Finance ActionResult, but this is what I am getting:

null is returned

NULL is returned in ActionResult Finance

I just don't understand this. I am passing a List into the View:

@model List<FinanceMVC.Models.FinanceViewModel>

And my ActionResult is expecting the following:

List<FinanceViewModel> finance

I have a feeling I have a problem in my model somewhere, and I've tried my best to find it, but I just can't see it. Has anyone experienced this before? Please can someone lend a hand.

Please see my code below:

I have the following Model:

namespace FinanceMVC.Models
{
    //public class Finance
    //{

    //}

    public class FinanceViewModel
    {
        /*ID*/
        //[Required]
        //[Display(Name = "ID")]
        //public int ID { get; set; }

        /*BINL_BIND_ID*/
        [Required]
        [Display(Name = "Invoice Type")]
        public IEnumerable<SelectListItem> InvoiceType { get; set; }

        /*BINL_Inv_Num_Pointer*/
        [RegularExpression(@"^[0-9]*$",
                            ErrorMessage = "Email is not valid")]
        [Required]
        [Display(Name = "Invoice Number")]
        public string InvoiceNumber { get; set; }

        /*BINL_Inv_Num_Period*/
        [Required]
        [Display(Name = "Invoice Number Period")]
        public IEnumerable<SelectListItem> InvoiceNumberPeriod { get; set; }
        //public List<C_Period> InvoiceNumberPeriod { get; set; }

        /*BINL_Created*/
        [Display(Name = "Invoice Created Date")]
        [Required]
        [DataType(DataType.DateTime)]
        public DateTime InvoiceDateCreated { get; set; }

        ///*BINL_SystemInserted*/
        //[Required]
        //[Display(Name = "Invoice System Inserted")]
        //public bool InvoiceSystemInserted { get; set; }
    }

    public class C_InvoiceType
    {
        public int ID { get; set; }
        public string Description { get; set; }
    }

    public class C_Period
    {
        public int Period { get; set; }
    }

    public class ReturnInfo
    {
        string InvoiceType { get; set; }
        string InvoiceNumber { get; set; }
        string InvoiceNumberPeriod { get; set; }
        string InvoiceDateCreated { get; set; }
    }
}

View:

@model List<FinanceMVC.Models.FinanceViewModel>
@{
    ViewBag.Title = "Index";
}
@using (Html.BeginForm("Finance", "Finance", FormMethod.Post, new { @class = "form-horizontal" }))
{
    <div class="col-md-1 fade in" style="margin-left: 5%; width: 100%; font-size: 11px; height: auto;">
        <div id="div_Finance_Container" style="width: 100%; height: 80%; overflow: auto; text-align: left;" runat="server" class="scroll-pane">
            <div style="height: 500px; overflow: auto;">
                <table id="dataTable" border="0">
                    @for(int i = 0; i <Model.Count; i++)
                    {
                    <tr>
                        @Html.Label("The next recommended Invoice Number is: ", "The next recommended Invoice Number is: " + Model[i].InvoiceNumber, new { style="font-weight:bold;" })
                    </tr>

                    <br />
                    <tr class="tr_clone">
                        <td>
                            <div class="col-md-1" style="width: 20%;">
                                @Html.DropDownList("InvoiceType[" + @i + "].InvoiceType", Model[i].InvoiceType)
                            </div>
                        </td>
                        <td>
                            @Html.TextBox("InvoiceNumber[" + @i + "].InvoiceNumber", Model[i].InvoiceNumber)
                        </td>

                        <td>
                            @Html.DropDownList("InvoiceNumberPeriod[" + @i + "].InvoiceNumberPeriod", Model[i].InvoiceNumberPeriod, new { @class = "InvoiceNumberPeriod", string.Empty })
                        </td>
                        <td>
                            @Html.TextBox("InvoiceDateCreated[" + @i + "].InvoiceDateCreated", Model[i].InvoiceDateCreated, new { @class = "InvoiceDateCreated", @type = "date" })
                        </td>
                        <td>
                            <input type="button" name="add" value="Add another record" class="tr_clone_add">
                        </td>
                    </tr>
                    }

                </table>
                <input type="submit" value="Save Bulk Data" />
            </div>
        </div>
    </div>
}

and Controller:

public class FinanceController : Controller
{
    public ActionResult Finance()
    {
        List<C_Period> c_Per = new List<C_Period>();

        // This is only for show by default one row for insert data to the database
        List<FinanceViewModel> FinanceViewList = new List<FinanceViewModel> 
        { 
            new FinanceViewModel 
            { 
                /*ID = 0 ,*/ InvoiceType = ListInvoiceTypesForFinances() /*ReturnInvoiceType()*/, InvoiceNumber = ReturnInvoiceNumber(), InvoiceNumberPeriod = ListInvoiceNumPeriodForFinances() /*c_Per*/, InvoiceDateCreated = DateTime.Now, /*InvoiceSystemInserted = false*/
            }, 
            new FinanceViewModel 
            { 
                /*ID = 0 ,*/ InvoiceType = ListInvoiceTypesForFinances() /*ReturnInvoiceType()*/, InvoiceNumber = ReturnInvoiceNumber(), InvoiceNumberPeriod = ListInvoiceNumPeriodForFinances() /*c_Per*/, InvoiceDateCreated = DateTime.Now, /*InvoiceSystemInserted = false*/
            },
            new FinanceViewModel 
            { 
                /*ID = 0 ,*/ InvoiceType = ListInvoiceTypesForFinances() /*ReturnInvoiceType()*/, InvoiceNumber = ReturnInvoiceNumber(), InvoiceNumberPeriod = ListInvoiceNumPeriodForFinances() /*c_Per*/, InvoiceDateCreated = DateTime.Now, /*InvoiceSystemInserted = false*/
            } 
        };

        return View(FinanceViewList);
    }

    // 
    // GET: /Finance/ 

    /*Matches file name of Finance.cshtml*/
    //[HttpPost]
    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Finance(List<FinanceViewModel> finance)
    {
        if (ModelState.IsValid)
        {

        }

        return null;
    }

    // 
    // GET: /Finance/Welcome/ 

    //public string Index()
    //{
    //    return "This is the Welcome action method...";
    //}

    public static string ReturnInvoiceNumber()
    {
        string sQ = ""
                   + " Select Max(BINL_Inv_Num_Pointer) AS [Last Invoice Number]  \n"
                   + "   From Biller_InvoiceNum_List with(nolock) \n"
                   ;

        using (SqlConnection sCon = new SqlConnection(Application_Info.sMOB_Master_Conn()))
        {
            if (sCon.State != ConnectionState.Open)
            {
                sCon.Open();
            }

            using (SqlCommand sCmd = new SqlCommand(sQ, sCon))
            {
                using (SqlDataReader sRdr = sCmd.ExecuteReader())
                {
                    string LastInvoiceRecordNumber = "";
                    while (sRdr.Read())
                    {
                        LastInvoiceRecordNumber = (string)sRdr["Last Invoice Number"];
                    }

                    int LastInvoiceLength = LastInvoiceRecordNumber.Length;
                    int number = int.Parse(LastInvoiceRecordNumber);

                    /*Increment to get recommended next Invoice Number*/
                    number = (number + 1);

                    string IntStr = number.ToString();
                    string NextInvoiceRecordNumber = IntStr.PadLeft(LastInvoiceLength, '0');

                    return NextInvoiceRecordNumber;
                }
            }

        }
    }

    public static IEnumerable<C_InvoiceType> ReturnIType()
    {
        var srtQry = "\n"
                    + " Select ID, BIND_Description \n"
                    + " From Biller_InvoiceNum_DefSet with(nolock) \n"
                    ;

        using (var conn = new SqlConnection(Application_Info.sMOB_Master_Conn()))
        using (var objCommand = new SqlCommand(srtQry, conn) { CommandType = CommandType.Text })
        using (var dt = new DataTable())
        using (var adp = new SqlDataAdapter(objCommand))
        {
            conn.Open();
            adp.Fill(dt);
            return dt.AsEnumerable().Select(o => new C_InvoiceType
            {
                ID = o.Field<int>("ID"),
                Description = o.Field<string>("BIND_Description"),
            }).ToList();
        }
    }

    public static IEnumerable<SelectListItem> ListInvoiceTypesForFinances()
    {
        var listIVTypes = ReturnIType();

        return listIVTypes
                .Select(o => new SelectListItem
                {
                    Text = o.Description,
                    Value = o.ID.ToString()
                })
                .ToList();
    }

    public static IEnumerable<C_Period> ReturnINumPeriod()
    {
        var srtQry = "\n"
                    + " Select BINL_Inv_Num_Period \n"
                    + " From Biller_InvoiceNum_List with(nolock) \n"
                    + " Where ID = 99999 \n"
                    ;

        using (var conn = new SqlConnection(Application_Info.sMOB_Master_Conn()))
        using (var objCommand = new SqlCommand(srtQry, conn) { CommandType = CommandType.Text })
        using (var dt = new DataTable())
        using (var adp = new SqlDataAdapter(objCommand))
        {
            conn.Open();
            adp.Fill(dt);
            return dt.AsEnumerable().Select(o => new C_Period
            {
                Period = o.Field<int>("BINL_Inv_Num_Period"), 
            }).ToList();
        }
    }

    public static IEnumerable<SelectListItem> ListInvoiceNumPeriodForFinances()
    {
        var listIVTypes = ReturnINumPeriod();

        return listIVTypes
                .Select(o => new SelectListItem
                {
                    Text = o.Period.ToString(),
                    Value = o.Period.ToString()
                })
                .ToList();
    }
}

UPDATE:

So, changed View @Html helpers like below:

@for(int i = 0; i <Model.Count; i++)
{
<tr>
    @Html.Label("The next recommended Invoice Number is: ", "The next recommended Invoice Number is: " + Model[i].InvoiceNumber, new { style="font-weight:bold;" })
</tr>

<br />
<tr class="tr_clone">
    <td>
        <div class="col-md-1" style="width: 20%;">
            @*@Html.DropDownList("InvoiceType[" + @i + "].InvoiceType", Model[i].InvoiceType)*@
            @Html.DropDownListFor(x => Model[i].InvoiceType, Model[i].InvoiceType)
        </div>
    </td>
    <td>
        @*@Html.TextBox("InvoiceNumber[" + @i + "].InvoiceNumber", Model[i].InvoiceNumber)*@
        @Html.TextAreaFor(x => Model[i].InvoiceNumber)
    </td>

    <td>
        @*@Html.DropDownList("InvoiceNumberPeriod[" + @i + "].InvoiceNumberPeriod", Model[i].InvoiceNumberPeriod, new { @class = "InvoiceNumberPeriod", string.Empty })*@
        @Html.DropDownListFor(x => Model[i].InvoiceNumberPeriod, Model[i].InvoiceNumberPeriod, new { @class = "InvoiceNumberPeriod", string.Empty })
    </td>
    <td>
        @*@Html.TextBox("InvoiceDateCreated[" + @i + "].InvoiceDateCreated", Model[i].InvoiceDateCreated, new { @class = "InvoiceDateCreated", @type = "date" })*@
        @Html.TextBoxFor(x => Model[i].InvoiceDateCreated, new { @class = "InvoiceDateCreated", @type = "date" })
    </td>
    <td>
        <input type="button" name="add" value="Add another record" class="tr_clone_add">
    </td>
</tr>
}

Still having problems with drop down lists displaying:

Post from View to Controller

ekad
  • 14,436
  • 26
  • 44
  • 46
fransHbrink
  • 364
  • 5
  • 13
  • In your view, is '@i' necessary? I'd think that just 'i' itself is enough as you already have @ on the start of the line. – Tom Nov 28 '14 at 10:03

3 Answers3

2

If you use them, your prefixes need to match your action parameter's name:

@Html.TextBox("finance[" + i + "].InvoiceNumber", Model[i].InvoiceNumber)
@Html.DropDownList("finance[" + i + "].InvoiceNumberPeriod", Model[i].InvoiceNumberPeriod, new { @class = "InvoiceNumberPeriod", string.Empty })
@Html.TextBox("finance[" + i + "].InvoiceDateCreated", Model[i].InvoiceDateCreated, new { @class = "InvoiceDateCreated", @type = "date" })

They are not required though when you have only one parameter, in which case you can use:

@Html.TextBox("[" + i + "].Property", ...)

You also can, and actually should, use the For methods:

@Html.TextBoxFor(m => m[i].InvoiceNumber, ...)
@Html.DropDownListFor(m => m[i].InvoiceNumberPeriod, ...)
@Html.TextBoxFor(m => m[i].InvoiceDateCreated, ...)
CodeCaster
  • 147,647
  • 23
  • 218
  • 272
  • The first one will not bind – Ehsan Sajjad Nov 28 '14 at 10:13
  • @Ehsan it will. The prefix is arbitrary and can be omitted. If you do use it, it must be the same for all. – CodeCaster Nov 28 '14 at 10:24
  • in collection case index is enough to make it work i am sure this will not work – Ehsan Sajjad Nov 28 '14 at 10:33
  • I just tested it, and it does work. See also [MVC Form not able to post List of objects](http://stackoverflow.com/questions/19964553/mvc-form-not-able-to-post-list-of-objects). – CodeCaster Nov 28 '14 at 10:34
  • @CodeCaster, one step closer, please see my EDIT at the top. Everything is passed through, except the two drop down lists? – fransHbrink Nov 28 '14 at 10:42
  • On my system this way it posts null model can you show me in dotnetfiddle – Ehsan Sajjad Nov 28 '14 at 10:43
  • @CodeCaster, The first set using the 'arbitary` prefix `@Html.TextBox("invoiceItem[" + i + "].InvoiceNumber"` does **not** work. –  Nov 28 '14 at 10:44
  • @Stephen it did for me using MVC 5. – CodeCaster Nov 28 '14 at 10:45
  • @CodeCaster i tested it using mvc4 this not works , i tested mine one it works as it should – Ehsan Sajjad Nov 28 '14 at 10:46
  • @RandbyB your dropdowns not posting is because dropdowns don't work like that. You need a field to hold the posted value too, not just the `List`. – CodeCaster Nov 28 '14 at 10:51
  • @CodeCaster, As far as I know, the `DefaultModelBinder` has not changed since MVC4. The only way that ` –  Nov 28 '14 at 10:52
  • @Stephen no, I tested it with an `IEnumerable` where `Foo` had a string property `Bar`. Perhaps the prefix needs to match the parameter name of the action method, in OP's case `finance`. – CodeCaster Nov 28 '14 at 10:59
  • @CodeCaster, thanks lead me on the right way. Moving from asp.net to mvc takes a little getting used to. – fransHbrink Nov 28 '14 at 11:45
1

You have to write it like this:

@Html.DropDownList("[" + @i + "].InvoiceType", Model[i].InvoiceType)
@Html.TextBox("[" + @i + "].InvoiceNumber", Model[i].InvoiceNumber)
.................................
.................................

Because you don't have InvoiceType collection property in your Model which has another property InvoiceType when you write InvoiceType[0].InvoiceType you are saying in my Model i have collection with name InvoiceType which has a Property name InvoiceType which is not the case

Better approach is to use For Helpers which will automatically bind control value with Model:

@Html.DropDownListFor(x=> Model[i].InvoiceType)
@Html.TextBoxFor (x=> Model[i].InvoiceNumber)
.................................
.................................

You can also refere this related SO post

Community
  • 1
  • 1
Ehsan Sajjad
  • 61,834
  • 16
  • 105
  • 160
  • only thing is, when I use Html.DropdownListFor as indicated above it gives me the following error: "CS1501: No overload for method 'DropDownListFor' takes 1 arguments". So, changing it to Html.DropDownListFor(x => Model[i].InvoiceType, Model[i].InvoiceType) fixes it, however the values are not passed through. – fransHbrink Nov 28 '14 at 10:52
  • Is your DropDownList populating with data? – Ehsan Sajjad Nov 28 '14 at 10:59
  • 1
    Dropdown post value attribute of option back as integer and you have binded dropdown with select list add an integer property in you view model and bind it to that – Ehsan Sajjad Nov 28 '14 at 11:01
  • Added int property and it works now. Posts back successfully. Thanks Ehsan. – fransHbrink Nov 28 '14 at 11:44
  • welcome @RandbyB remember that dropdown post selected value back to controller so it should be binded to integer – Ehsan Sajjad Nov 28 '14 at 11:46
1

OMG. Use the strongly typed helpers. None of your control names match your property names

@Html.TextBoxFor(m => m[i].InvoiceNumber)
  • this works perfectly. Will remember in future. Thanks again for the help. Please just see my Update at the top, having trouble displaying the two dropdownlists. Playing around and trying to find the answer. Wow, I have tons to learn. – fransHbrink Nov 28 '14 at 10:46
  • `DropDownListFor` requires a property to bind to (typically `int` or `string`) and a collection that renders te options (`IEnumerable` or `SelectList`) –  Nov 28 '14 at 10:55
  • 1
    @RandbyB, I also suggest you look at [this answer](http://stackoverflow.com/questions/26742155/dropdownlistfor-will-not-show-the-correct-selection/26752086#26752086). `DropDownList()` and `DropDownListFor()` behave a little differently in a loop than all other controls and you really should be using an `EditorTemplate` –  Nov 28 '14 at 10:59
  • Can't get this working now, and will need to explore this subject more, but will remember this post. Thank you for the help once again. – fransHbrink Nov 28 '14 at 11:43
  • [Here is another answer](http://stackoverflow.com/questions/26950614/how-to-create-a-dropdown-list-in-mvc4/26952781#26952781) about using `DropDownListFor()` which may help you understanding of how it works –  Nov 28 '14 at 11:50