Arguments model and action methods in ASP.NET MVC Part 1

Sending values from an MVC view to a controller works very well when you are binding back to the same model/viewmodel you populated the view with. But when you start trying to do something a little different, it’s not so obvious or straightforward.

The quick answer

The name attribute of the html element must match the argument sent to the action method.

Here are a few examples.

If the type sent to the action method is an Employee (which has strings for Fristname and Lastname) the html input elements should look like

	<input type="text" name="Firstname" value="" />
	<input type="text" name="Lastname" value="" />

If the type sent to the action method is an EmployeeViewModel (this has an Employee type inside, which has strings for Fristname and Lastname) the html input elements should look like

	<input type="text" name="Employee.Firstname" value="" />
	<input type="text" name="Employee.Lastname" value="" />

Note the name attributes are different from the previous example, but that they match the EmployeeViewModel.

That might be enough to solve your problem, if so you can get back to work, if not, read on.

The long answer

The following example is a bit contrived, and don’t use it as an example of how to code MVC models, or viewmodels, but it allows me to show a few ways of achieving some unusual model binding. Full source code is attached. I’ve followed the standard MVC Index, Create, Edit and Delete approach that comes out of the box with Visual Studio 2012.

I have an Employee -

1    public class Employee
2    {
3        public int EmployeeID { get; set; }
4        public string Firstname { get; set; }
5        public string Lastname { get; set; }
6        public int RoleID { get; set; }
7    }

and a Role -

1    public class Role
2    {
3        public int RoleID { get; set; }
4        public string Description { get; set; }
5    }

For the purpose of this demo I’m using a list to store the employees and roles, you can see them in the FakeData class in the attached source.

The EmployeeViewModel is used to populate the Create and Edit views, it contains an Employee and the SelectList of Roles for the dropdownlist -

1    public class EmployeeViewModel
2    {
3    ...snip...
4        public Employee Employee { get; set; }
5        public IEnumerable Roles { get; set; }
6    ...snip...
7    }

The Create View

The Create view follows the standard MVC approach, pass in the EmployeeViewModel, use LabelFor, EditorFor and DropDownListFor.

On the controller side, take the EmployeeViewModel as the argument to the POST Create action method. All the model binding “just works”. This is because the @Html.EditorFor has created html like the following -

1<input class="text-box single-line" id="Employee_Firstname" type="text" name="Employee.Firstname" value="" />

Note the name="Employee.Firstname", this matches the EmployeeViewModel.Employee.Firstname, same behaviour applies to Lastname and RoldID.

We stuck with what MVC gave us and everything worked.

The Edit View

With the Edit view we’ll do something different, something you won’t normally do in this scenario, but I want to keep it simple from a code perspective.

The Edit view will take a the EmployeeViewModel to populate the controls, but the POST Edit action method will an EmployeeArgsModel -

1    public class EmployeeArgsModel
2    {
3        public int EmployeeID { get; set; }
4        public string Firstname { get; set; }
5        public string Lastname { get; set; }
6        public int RoleID { get; set; }
7
8        public string ReasonForEdit { get; set; }
9    }

You’ll note that it has all the properties that the Employee has, and yes, I could have used an Employee instead, but I need to do it this way for demonstration purposes. I’ve added a string property called ReasonForEdit, this is going to be textbox on the view.

In order for the POST method to get the right parameters and perform model binding the way we are expecting we need to change the way we write the Edit View from how we wrote the Create View.

Why use an arguments model

You could pass in the EmployeeID, Firstname, Lastname, RoleID and ReasonForEdit directly to the action method

1    public ActionResult Edit(int employeeID, string firstname, string lastname, int roleID, string reasonForEdit)

But this will quickly become unwieldy. Many parameters would be needed for a search with filtering, sorting, and paging! Creating a model that captures what you need in a much neater way.

You can combine the usage of an arguments model and simple arguments, there is an example of this in the source code.

The importance of the “name” attribute

For simplicity I’m going to say that you need to match the “name” attribute of your html to the public property on the model the controller takes as an argument. If the controller takes an EmployeeViewModel with an Employee inside it, then the “name"s of your html attributes should be prefixed with “Employee.” and the name of the property.

If you are having trouble working out what names your should be using in the view change your action method to something like -

1public ActionResult Edit(FormCollection collectionOfValues)

Use the debugger to check the names of the values passed to the method in the collectionOfValues.

Comparison of Create and Edit views and action methods

The rest of this post deals with the attached source code, please have a look at it before reading on.

The Create view follows the normal MVC approach, the view takes a model and the POST action method takes the same model.

1        [HttpPost]
2        public ActionResult Create(EmployeeViewModel employeeViewModel)

The Edit view takes a different approach, the view takes the a EmployeeViewModel but the POST action method takes an EmployeeArgeModel.

1        [HttpPost]
2        public ActionResult Edit(EmployeeArgsModel employeeArgsModel)

Below compares the Razor code and html produced for the two views on a element by element basis, note how the “name” attributes differ.

Firstname

Create View

Razor : @Html.EditorFor(model => model.Employee.Firstname) binds to EmployeeViewModel.Employee.Firstname property. HTML : <input id="Employee_Firstname" type="text" value="" name="Employee.Firstname">

Edit View

Razor : @Html.Editor("Employee.Firstname","", "Firstname") shows the value in the Employee.Firstname of the Model sent to the view, and binds to Firstname (set in the third argument to Html.Editor) property in the argument to the action method. HTML : <input id="Firstname" type="text" value="Dave" name="Firstname">

Lastname

Create View

Razor : @Html.EditorFor(model => model.Employee.Lastname) binds to EmployeeViewModel.Employee.Lasttname property. HTML : <input type="text" value="" name="Employee.Lastname" id="Employee_Lastname">

Edit View

Razor : @Html.EditorFor(model => model.Employee.Lastname,"", "Lastname") shows the value in the Employee.Lastname of the Model sent to the view and binds to Lastname (set in the third argument to Html.Editor) property in the argument to the action method. HTML : <input type="text" value="Jones" name="Lastname" id="Lastname">

ReasonForEdit

Edit view

Razor : @Html.TextBox("ReasonForEdit", null) binds to the ReasonForEdit property in the argument to the action method HTML : <input type="text" value="" name="ReasonForEdit" id="ReasonForEdit">

Role ID

Create View

Razor : @Html.DropDownListFor(model => model.Employee.RoleID, Model.Roles, "--Select a role --" ) binds to EmployeeViewModel.Employee.RoleID property in the argument to the action method. HTML :

1<select name="Employee.RoleID" id="Employee_RoleID" data-val-required="The RoleID field is required." data-val-number="The field RoleID must be a number." data-val="true">
2   <option value="">--Select a role --</option>
3   <option value="1">Junior Engineer</option>
4   <option value="2">Senior Engineer</option>
5   <option value="3">Lead Engineer</option>
6</select>

Edit View

Razor : @Html.DropDownListFor(model => model.Employee.RoleID, Model.Roles, "--Select a role --", new { Name = "RoleID"} ) binds to the RoleID property in the argument to the action method

1<select id="Employee_RoleID" data-val-required="The RoleID field is required." data-val-number="The field RoleID must be a number." data-val="true" name="RoleID">
2    <option value="">--Select a role --</option>
3    <option value="1" selected="selected">Junior Engineer</option>
4    <option value="2">Senior Engineer</option>
5    <option value="3">Lead Engineer</option>
6</select>

Summary

The html elements must be named to match the arguments your action method takes. If this explanation doesn’t make it clear, I hope the source code will.

comments powered by Disqus

Related