Introduction to Model Binding
This is part 41 of an ASP.NET Core tutorial. In this video, we'll discuss model binding in ASP.NET Core with examples. Model binding maps data in an HTTP request to controller action method parameters. The action parameters may be simple types, such as integers, strings, etc., or complex types like Customer, Employee, Order, etc. Model binding is great because without it, we have to write a lot of custom code to map request data to action method parameters, which is not only tedious but also error-prone.
When a request arrives at our MVC application, it is the controller action method that handles that incoming HTTP request. For example, let's say we want to view specific employee details whose ID is two. For that, we issue a GET request to this URL: /home/details/2. In the URL is the ID of the employee whose details we want to view. This value is mapped to the id parameter of the Details action method of our HomeController. So mapping this incoming HTTP request data to this action method parameter is done by model binding.
This is done automatically by MVC for us. We do not have to write any custom code for this. Imagine the amount of time MVC is saving us. MVC binds request data to action parameters by name. If we take a look at the Startup.cs file, notice the default route template that we have right here. The parameter in this last URL path segment is named id, and the parameter of our Details action method is also named id. So this value, 2, in the route data of our request is mapped to this id parameter by name.
Binding Multiple Simple Types
Now let's look at another quick example. I'm going to introduce another parameter, let's call it name. Now let's keep this code commented for a minute, and what we want to do is return the values of these two parameters, ID and name, as a string. So let's change the return type of this method to string.
And return ID= we want to append the ID parameter value. ID is a nullable parameter, so we use the Value property and then convert it to a string. And then to that we also want to append the name parameter value. With this change, when we reload our webpage, notice this value 2 that is passed as part of this request route data, it is mapped to the id parameter of our Details action method. And that's the reason we see ID=2. At the moment, as part of this request, we are not passing any value for the name parameter. So at the moment, the name parameter of our Details action method is null, and that is the reason we don't see anything displayed on the webpage for name.
Now let's include a query string in the URL. I'm going to name it name and set its value to Pragim. Notice now we see ID=2 and name=Pragim. Now let's change the name of the query string parameter here to name1 and issue the request again. Notice name is null again. Now let's change the name of this query string parameter from name1 to name. We see the value again. So the point that I'm trying to make is model binding in ASP.NET Core MVC maps the incoming HTTP request data to the respective controller action method parameters by name.
Model Binding Source Order
Now we are already passing a value for the ID parameter as part of route data in this request. Now let's also include a query string parameter ID in the URL and set its value to five and see what happens. With this HTTP request, the value for the id parameter is present in two places: as part of route data right here, and as part of this query string parameter id.
So the important point to keep in mind is, to bind request data to controller action method parameters, model binding in ASP.NET Core MVC looks for data in the incoming HTTP request in the following three locations, in the order that is specified right here: first, it looks at the posted form values, then route values, and finally query strings. For this reason, though we have a value for the ID parameter in two places in this request, that is, as part of route data and as part of a query string, the model binder used the value that is present in route data.
Now if we do not pass any value as part of route data, let's remove the value 2 from here and only include the query string parameter id. Notice now it uses the value present as part of the query string.
Setting Up for Complex Type Binding
Now let's see how model binding works with complex types such as orders, employees, customers, etc. I'm going to undo the changes that we have done on this Details action method.
Now let's navigate to /home/index. Here is what we want to do. When we fill this create employee form and click the Create button, we want to create a new employee on our server. At the moment, within our application in the HomeController, we only have one Create action method, and it is this action method that is returning us this create employee form when a GET request is issued to this URL: /home/create. After we fill the employee details and click the Create button, we issue a POST request. To handle this POST request, we need another Create action method. So let's make a copy of this and then change the bits that are required. This method receives the Employee object that we want to create as a parameter. We are going to make use of this EmployeeRepository to create a new employee. The employee repository is already injected into a home controller. At the moment if we take a look at the IEmployeeRepository interface, we don't have a method to add a new employee, so let's add that method right now. The Add method is going to return us the employee that we add. And obviously, to add an employee, we have to pass that Employee object to the Add method. Next, we need to provide implementation for this Add method in MockEmployeeRepository.
Implementing the Create Functionality
Notice we see a red squiggly under IEmployeeRepository interface. When I hover the mouse over, we see a message, "MockEmployeeRepository does not implement interface member IEmployeeRepository.Add." So basically we need to provide implementation for the Add method that we just added to the IEmployeeRepository interface. We can do that simply by clicking here and selecting this option, Implement interface. This automatically generates the method signature for us. We want this method to add this employee object to this private _employeeList list. So inside the method, on the employee list, let's call the Add method and pass the employee object and then finally return that employee object.
Now here's the important bit to understand. When we submit this form, we are only supplying values for Name, Email, and Department properties. But if we take a look at the Employee object, it also has this ID property. So when we are creating a new employee, we also have to compute a value for the ID property. And that's very easy to do by using the LINQ Max method. So on the Employee List, let's use the Max method. Given an employee object, we want the max ID property value and then add one to it. We want to set this as the value for the ID property of this employee object.
Next, within our HomeController, let's use this injected IEmployeeRepository and add this employee object to the repository. We use the Add method, and to it, we pass the employee object. Notice from the IntelliSense, this Add method of IEmployeeRepository returns us the employee object that we just added. The ID property of this returned object is also populated. So let's store this object in a variable of type Employee. Let's call the variable newEmployee.
We will then redirect the user to this Details action method to view the details of the new employee that we just added. For that, we use the RedirectToAction method. From the IntelliSense, as you can see, we need to specify the name of the action method. In our case, the name is Details. We also need to pass a value for the id parameter of this Details action method. For that, we're going to use the second parameter, routeValues. So let's create a new anonymous object, include a property with name ID, and the value for this is going to come from the ID property of this newEmployee object. We have a red squiggly, and when I have the mouse over this RedirectToAction method, notice this method returns RedirectToActionResult, but here we have specified the return type as ViewResult. That's the reason we have this red squiggly. So let's change the return type to RedirectToActionResult.
Resolving the Ambiguous Action Exception
At this point, let's save all our changes and take a look at the browser. Notice when we reload the page, we have an ambiguous action exception. This is because at the moment, within our HomeController, we have two Create action methods, and MVC does not know which action method to use for HTTP GET and which method to use for HTTP POST. We want this method to respond to HTTP GET, so let's decorate this with the [HttpGet] attribute. And we want this method to respond to HTTP POST, so let's decorate this with [HttpPost]. Notice now when we reload the page, we see the create employee form. Now let's fill in new employee details and then click the Create button. There we go. Our new employee is created, his employee ID is four. We can also see that in the URL right here. And when we navigate to the list view, we can see our new employee here as well.
Now let's understand what's going on behind the scenes when we submit this create employee form. Let's view the page source. Notice the name of this name input element, it is name. So the value that we have in this name input element is mapped to the Name property of this Employee object because both of them match. Similarly, the email input element has the name email. So the value in this input element is mapped to the Email property of the Employee object. The same is true even for the department select list. So the point that I'm trying to make is the name attribute on an HTML control is significant because it is this name attribute value that is used by MVC to map the value that is present in the control to a corresponding property of the complex object that is passed as a parameter to our controller action method.
At the moment our create employee form does not have any validation in place. So if we submit this form without filling any details, notice we just created a new employee without name and email. We'll discuss how to prevent this using form validation in our next video. Thank you for listening.