Model Binding using FromQuery in ASP.NET Core MVC

Model Binding using FromQuery in ASP.NET Core MVC

In this article, I will discuss How to Use FromQuery to Perform Model Binding in an ASP.NET Core MVC Application with Examples. Please read our previous article discussing Model Binding using FromForm in ASP.NET Core MVC. The FromQuery attribute is commonly used in API development and situations where data is passed via URL parameters rather than in the body of the request.

Model Binding using FromQuery in ASP.NET Core MVC

In ASP.NET Core MVC, model binding allows you to map data from various sources (such as query strings, form fields, and JSON bodies) to the parameters of your action methods. The FromQuery attribute specifically tells the ASP.NET Core MVC framework to bind data from the query string of the request to parameters in your action method. The query string is part of a URL after the “?” Character and is composed of key-value pairs separated by & characters. 

When you decorate an action method parameter with the [FromQuery] attribute, ASP.NET Core MVC automatically binds data from the query string to that parameter based on its name. If you go to the definition of FromQueryAttribute, you will see the following signature.

Model Binding using FromQuery in ASP.NET Core MVC

In ASP.NET Core MVC, the BindingSource and Name property of the FromQueryAttribute provide detailed control over how data from the request’s query string is mapped to the parameters of an action method.

  • BindingSource: BindingSource specifies where the value for a parameter is found. For example, in the context of the FromQueryAttribute, ASP.NET Core uses the BindingSource.Query automatically. This means the system is configured to look for values specifically in the query part of the incoming HTTP request URL.
  • Name: The Name property of FromQueryAttribute is important when the parameter name in your action method does not match the key in the query string. It explicitly maps a query string key to a method parameter, offering flexibility in how parameters are named in your method versus what is exposed in URLs.
Example to Understand FromQueryAttribute in ASP.NET Core MVC:

Suppose we have a scenario where we search for users based on Name and Age. So, let us first create a model to hold the search string. Create a class file named UserSearchCriteria.cs within the Models folder and copy and paste the following code.

namespace ModelBindingDemo.Models
{
    public class UserSearchCriteria
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
}

Next, create another class file named User.cs within the Models folder and copy and paste the following code. This is a simple model that represents a user.

namespace ModelBindingDemo.Models
{
    public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
        public string? Mobile { get; set; }
    }
}

Next, create an Empty MVC Controller named UsersController within the Controllers and copy and paste the following code. In the following example, we are binding query parameters to a model, but you can also bind them directly to action method primitive parameters. Please ensure the null values are handled, or default values are provided if some query parameters are optional. In the example below, int? Age means age can be null. 

using Microsoft.AspNetCore.Mvc;
using ModelBindingDemo.Models;

namespace ModelBindingDemo.Controllers
{
    public class UsersController : Controller
    {
        private List<User> _users;
        public UsersController()
        {
            _users = new List<User>()
            {
                new User(){Id =1, Name ="Pranaya", Age = 35},
                new User(){Id =2, Name ="Priyanka", Age = 30},
                new User(){Id =3, Name ="Anurag", Age = 35},
                new User(){Id =4, Name ="Prateek", Age=30},
                new User(){Id =5, Name ="Hina", Age=35}
            };
        }

        [HttpGet("users/search")]
        public IActionResult Search([FromQuery] UserSearchCriteria criteria)
        {
            List<User> FilteredUsers = new List<User>();
            
            if (criteria != null)
            {
                if (!string.IsNullOrEmpty(criteria.Name) && criteria.Age > 0)
                {
                    FilteredUsers = _users.Where(x => x.Name.ToLower().StartsWith(criteria.Name.ToLower()) || x.Age > criteria.Age).ToList();
                }
                else if(!string.IsNullOrEmpty(criteria.Name))
                {
                    FilteredUsers = _users.Where(x => x.Name.ToLower().StartsWith(criteria.Name.ToLower())).ToList();
                }
                else if (criteria.Age > 0)
                {
                    FilteredUsers = _users.Where(x => x.Age > criteria.Age).ToList();
                }
            }

            return Ok(FilteredUsers);
        }
    }
}
Understanding the Search Action Method:

The method is decorated with the [HttpGet] attribute, indicating that it will respond to HTTP GET requests only. The route template “users/search” specifies the endpoint URL for accessing this method. The method takes a parameter criteria of type UserSearchCriteria, decorated with [FromQuery] attribute. This means that the method expects the values for criteria to be provided in the query string of the request URL.

The Logic of the Search Method:
  • The method initializes an empty list called FilteredUsers, which will store the users that match the search criteria. Then, it checks if the criteria object is not null. If the criteria is not null, it checks whether the Name property or the Age property (or both) of the criteria object has been provided.
  • Depending on the provided criteria, it filters the _users list accordingly. If both Name and Age are provided, it filters users whose names start with the provided name (case-insensitive) or whose age is greater than the provided age. If only the Name is provided, it filters users whose names start with the provided name (case-insensitive). If only Age is provided, it filters users whose age is greater than the provided age.
  • Finally, it returns an HTTP response with status code 200 (OK) and the filtered list of users.
Return Value:
  • The method returns an IActionResult, which is a common base type for all action results in ASP.NET Core MVC. If the filtering logic executes successfully, it returns an HTTP 200 OK response and the filtered list of users (FilteredUsers).
Testing the Action Method:

When calling the above endpoint, you can provide search criteria through the query string as follows:

  • /users/search?Name=pr
  • /users/search?Age=30
  • /users/search?Name=Pr&Age=30

In the above cases, the values from the query string (Name and Age) will be bound to the properties of the UserSearchCriteria object. This is possible because of the FromQuery attribute.

Using Primitive Type with FromQuery Attribute

Instead of using the Complex type, we can also use the primitive data type along with the FromQuery attribute. For a better understanding, please modify the UsersController as follows:

using Microsoft.AspNetCore.Mvc;
using ModelBindingDemo.Models;

namespace ModelBindingDemo.Controllers
{
    public class UsersController : Controller
    {
        private List<User> _users;
        public UsersController()
        {
            _users = new List<User>()
            {
                new User(){Id =1, Name ="Pranaya", Age = 35},
                new User(){Id =2, Name ="Priyanka", Age = 30},
                new User(){Id =3, Name ="Anurag", Age = 35},
                new User(){Id =4, Name ="Prateek", Age=30},
                new User(){Id =5, Name ="Hina", Age=35}
            };
        }

        [HttpGet("users/search")]
        public IActionResult Search([FromQuery] string Name, [FromQuery] int? Age)
        {
            List<User> FilteredUsers = new List<User>();

            if (!string.IsNullOrEmpty(Name) && Age != null && Age > 0)
            {
                FilteredUsers = _users.Where(x => x.Name.ToLower().StartsWith(Name.ToLower()) || x.Age > Age).ToList();
            }
            else if (!string.IsNullOrEmpty(Name))
            {
                FilteredUsers = _users.Where(x => x.Name.ToLower().StartsWith(Name.ToLower())).ToList();
            }
            else if (Age != null & Age > 0)
            {
                FilteredUsers = _users.Where(x => x.Age > Age).ToList();
            }

            return Ok(FilteredUsers);
        }
    }
}

If the parameter names and query string keys match, you don’t strictly need the FromQuery attribute for binding to work. For a better understanding, please modify the UsersController as follows:

using Microsoft.AspNetCore.Mvc;
using ModelBindingDemo.Models;

namespace ModelBindingDemo.Controllers
{
    public class UsersController : Controller
    {
        private List<User> _users;
        public UsersController()
        {
            _users = new List<User>()
            {
                new User(){Id =1, Name ="Pranaya", Age = 35},
                new User(){Id =2, Name ="Priyanka", Age = 30},
                new User(){Id =3, Name ="Anurag", Age = 35},
                new User(){Id =4, Name ="Prateek", Age=30},
                new User(){Id =5, Name ="Hina", Age=35}
            };
        }

        [HttpGet("users/search")]
        public IActionResult Search(string Name, int? Age)
        {
            List<User> FilteredUsers = new List<User>();

            if (!string.IsNullOrEmpty(Name) && Age != null && Age > 0)
            {
                FilteredUsers = _users.Where(x => x.Name.ToLower().StartsWith(Name.ToLower()) || x.Age > Age).ToList();
            }
            else if (!string.IsNullOrEmpty(Name))
            {
                FilteredUsers = _users.Where(x => x.Name.ToLower().StartsWith(Name.ToLower())).ToList();
            }
            else if (Age != null & Age > 0)
            {
                FilteredUsers = _users.Where(x => x.Age > Age).ToList();
            }

            return Ok(FilteredUsers);
        }
    }
}

Note: Using the attribute explicitly clarifies your intention and makes the code more maintainable and understandable. 

How Does Model Binding Work with FromQuery Attribute in ASP.NET Core MVC?

When a request is made to an MVC action that has parameters decorated with the FromQuery attribute, the model binding system kicks in with the following steps:

  • Identify the Source: The model binder identifies the query string as the source of the binding data due to the FromQuery attribute.
  • Match Parameters: The model binder matches keys in the query string with the names of the action method parameters. If a Name property is specified in the FromQuery attribute, it uses this custom name to match the keys in the query string.
  • Conversion and Binding: Once matching keys are found, the model binder converts the query string values from strings to the types of the respective action method parameters. ASP.NET Core has built-in type converters that handle most primitive types and can bind to complex types by matching the query string keys to property names of the complex types.
  • Handling Errors: If there are binding errors, such as a mismatch in type conversion, ASP.NET Core handles these, typically assigning default values or nulls depending on the scenario and configuration. Errors can also be handled or logged by checking the model state (ModelState.IsValid).
Using Name Property of FromQuery Attribute:

The Name property of the FromQueryAttribute is used when the name of the parameter in the action method does not match the key in the query string. It provides a way to map the query string key directly to a method parameter that might have a different name.

Suppose you have a query string named EmpAge, but in your method, you want to bind it to a parameter named Age. Again, you want to map the EmpName query string with the Name parameter of our action method. In this case, we need to use the Name property to map these correctly. For a better understanding, please modify the UsersController as follows:

using Microsoft.AspNetCore.Mvc;
using ModelBindingDemo.Models;

namespace ModelBindingDemo.Controllers
{
    public class UsersController : Controller
    {
        private List<User> _users;
        public UsersController()
        {
            _users = new List<User>()
            {
                new User(){Id =1, Name ="Pranaya", Age = 35},
                new User(){Id =2, Name ="Priyanka", Age = 30},
                new User(){Id =3, Name ="Anurag", Age = 35},
                new User(){Id =4, Name ="Prateek", Age=30},
                new User(){Id =5, Name ="Hina", Age=35}
            };
        }

        [HttpGet("users/search")]
        public IActionResult Search([FromQuery(Name ="EmpName")] string Name, [FromQuery(Name ="EmpAge")] int? Age)
        {
            List<User> FilteredUsers = new List<User>();

            if (!string.IsNullOrEmpty(Name) && Age != null && Age > 0)
            {
                FilteredUsers = _users.Where(x => x.Name.ToLower().StartsWith(Name.ToLower()) || x.Age > Age).ToList();
            }
            else if (!string.IsNullOrEmpty(Name))
            {
                FilteredUsers = _users.Where(x => x.Name.ToLower().StartsWith(Name.ToLower())).ToList();
            }
            else if (Age != null & Age > 0)
            {
                FilteredUsers = _users.Where(x => x.Age > Age).ToList();
            }

            return Ok(FilteredUsers);
        }
    }
}

Now, while calling the above action method, you can provide search criteria through the query string as follows:

  • /users/search?EmpName=pr
  • /users/search?EmpAge=30
  • /users/search?EmpName=Pr&EmpAge=30
When Should We Use FromQuery Attribute in ASP.NET Core?

The FromQuery Attribute in ASP.NET Core can be useful for handling HTTP GET requests where you want to pass information through the URL. The following are a few scenarios where you might use the FromQuery attribute:

  • Filtering Data: It’s common to use query parameters to filter data in APIs. For instance, if you have an endpoint that retrieves a list of products, you can use FromQuery to allow clients to filter products based on criteria like category, price range, etc.
  • Pagination: For endpoints that return a collection of items, query parameters can be used to implement pagination. Using FromQuery, you can specify parameters such as page and pageSize to control how many items to return and which data page to retrieve.
  • Search Parameters: If you have a search endpoint, you can use FromQuery to capture search criteria sent by the client, such as search keywords, date ranges, or other filters.
  • Sorting: When returning a list of items, you might want to allow the user to specify sorting criteria through the query string, such as sorting by date, name, or price.
When Not to Use:
  • Sensitive Data: Never pass sensitive data in the query string, like passwords or security tokens. They can be logged in server logs, browser history, or referrer headers, posing a security risk.
  • Very Large Data Sets: Browsers and servers have URL length limits. If the query string becomes too long, it’s better to use a different method, like sending a JSON payload in the POST request body.

In the next article, I will discuss How to Use FromRoute to Perform Model Binding in ASP.NET Core MVC with Examples. In this article, I explain How to use FromQuery to Perform Model Binding in ASP.NET Core MVC with Examples. I hope you enjoy this FromQuery in ASP.NET Core MVC article.

Leave a Reply

Your email address will not be published. Required fields are marked *