In this article, we’ll discuss the different ways to pass parameters to a GET
request in ASP.NET Core.
Before we dive into this topic, we recommend going through the basics of handling the GET request and best practices for ASP.NET Core Web API.
When working with ASP.NET Core, we use GET
methods to retrieve data from a web API. We often must pass parameters to a GET
method to specify which data to retrieve.
There are several ways we can pass parameters to a method:
- Query parameters
- Route parameters
- Combination of query and route parameters
- Request body
- Header Parameters
Let’s take a closer look at each section to gain a better understanding.
VIDEO: Pass Parameters With a GET Request in ASP.NET Core.
Setting Up the Application
To start with, we’ll create two model classes, Product
:
public class Product { public int Id { get; set; } public string? Category { get; set; } public string? Brand { get; set; } public string? Name { get; set; } public int WarrantyYears { get; set; } public bool IsAvailable { get; set; } }
And ProductDto
:
public class ProductDto { public int Id { get; set; } public string? Category { get; set; } public string? Brand { get; set; } public string? Name { get; set; } public int WarrantyYears { get; set; } public bool IsAvailable { get; set; } }
Then, we will create a Map
class to map the data from the Product
class to the ProductDto
class:
public class Map : Profile { public Map() { CreateMap<Product, ProductDto>(); } }
To better understand how AutoMapper works, we suggest reading our guide on using AutoMapper in ASP.NET Core.
Now, let’s create a ProductController
class and define a list of products:
[ApiController] [Route("api/[controller]")] public class ProductController : ControllerBase { private readonly List<Product> _products = new() { new Product{ Id = 1, Category = "Electronic", Brand = "Sony", Name = "Play Station", WarrantyYears = 2, IsAvailable = true }, //removed other entires for a better readability new Product{ Id = 7, Category = "Electronic", Brand = "Apple", Name = "Mobile", WarrantyYears = 2, IsAvailable = true } }; }
Finally, let’s declare a field named mapper
of type IMapper
, and inject an instance of IMapper
in the constructor:
private readonly IMapper _mapper; public ProductController(IMapper mapper) { _mapper = mapper; }
Now that we have finished the initial steps, let’s explore each section in detail.
Query Parameters
In a GET
method, we commonly use query strings to pass parameters. We include them in the URL as key-value pairs separated by an equal sign and separate multiple parameters by an ampersand symbol.
Query parameters begin with a question mark (?), and we put them after the URL. If any route parameters exist in the URL, query parameters should be placed after them.
In this URL, category
and brand
are the query parameters with the values “Electronic” and “Sony”:
https://localhost:7097/api/Product?category=Electronic&brand=Sony
When we work with query parameters, we have the option to use the [FromQuery]
attribute to bind parameters from the query string explicitly. However, the parameter binding process automatically converts request data into strongly typed parameters. This process eliminates the need for explicit [FromQuery]
attribute usage.
First, let’s create a GET method without using the [FromQuery]
attribute:
[HttpGet] public IActionResult GetProductsByCategoryAndBrand(string category, string brand) { List<Product> result = _products .Where(x => x.Category == category && x.Brand == brand) .ToList(); return Ok(_mapper.Map<List<ProductDto>>(result)); }
We have a GET endpoint that accepts two parameters category
and brand
. Then, we use the LINQ Where
method to filter the _products
based on the category
and brand
and map the result with the ProductDto
class.
Let’s proceed by sending a request to the endpoint:
We can observe that parameter names in our method signature align with the query parameter names in the request URL, so the framework automatically binds the values from the query string to the method parameters based on their names and filters the _products
.
Sometimes, we may encounter situations where the parameter names in the method signature do not match the query parameter names. In such scenarios, we can explicitly use the [FromQuery]
attribute to bind the query parameters to the respective method parameters.
Now, let’s create a GET
method that takes two parameters from the query string, and we use the FromQuery
attribute:
[HttpGet("type-manufacturer")] public IActionResult GetProductsByCategoryAndBrandUsingFromQuery( [FromQuery(Name = "type")] string category, [FromQuery(Name = "manufacturer")] string brand) { List<Product> result = _products .Where(x => x.Category == category && x.Brand == brand) .ToList(); return Ok(_mapper.Map<List<ProductDto>>(result)); }
We use [FromQuery(Name = "type")]
and [FromQuery(Name = "manufacturer")]
attribute to bind the values of query parameters type
and manufacturer
to the corresponding method parameters category
and brand
.
To test this, let’s send a request with two query parameters:
The API filters the data and returns the result based on the query parameters type
and manufacturer
.
Since there is a limit to the length of a URL, query parameters have a limited capacity for passing data. Check out this helpful resource for information on the maximum character limit for a query string in GET
requests for different browsers.
Additionally, query parameters are not secure because the values are visible in the URL, making them vulnerable to interception by malicious users.
Route Parameters
We can also use route parameters to pass data but in a different way. Route parameters are placeholders in the URL. We use route parameters to identify a specific resource or resources, while the query parameters to sort/filter those resources.
We recognize route parameters by curly braces ({}) containing a parameter name as part of the route template.
Let’s take a look at an URL that contains the route parameter:
https://localhost:7097/api/Product/2
We use this URL to access a product resource by passing the product Id as a route parameter to the API endpoint.
By default, the framework binds route parameters based on their names when we use route parameters, allowing us to omit the [FromRoute]
attribute. However, there are scenarios where the parameter names in the method signature differ from the route parameter names. In such situations, we can use the [FromRoute]
attribute.
Let’s create another GET
method without using FromRoute
attribute:
[HttpGet("{id}")] public IActionResult GetProductById(int id) { return Ok(_mapper.Map<ProductDto>(_products .Where(x => x.Id == id) .FirstOrDefault())); }
Firstly, we define a GET
method with a route parameter “id” using the [HttpGet("{id}")]
attribute. Then, using the LINQ query, we filter the _products
and map the retrieved product to a ProductDto
.
Let’s send a request with a route parameter:
The API utilizes the route parameter to identify the specific resource.
Let’s create an additional GET
method that uses [FromRoute]
attribute:
[HttpGet("productId/{productId}")] public IActionResult GetProductByIdUsingFromRoute([FromRoute(Name = "productId")] int id) { return Ok(_mapper.Map<ProductDto>(_products .Where(x => x.Id == id) .FirstOrDefault())); }
In this GET
method, we define a [FromRoute(Name = "productId")]
attribute to bind the productId
parameter to the corresponding action method parameter.
Let’s send a request to the endpoint:
As we can see, API identifies the resource based on the route parameter we passed.
Combination of Query and Route Parameters
We can combine query and route params to pass params in a GET
method. It is often more appropriate to pass a set of required parameters as route parameters and optional parameters as query parameters.
With this approach, we can design an API endpoint to include some of the parameters in the URL as route parameters while passing others in the URL as query parameters. The route parameters help identify the requested primary resource, while the query parameters help filter the result based on additional criteria.
Now, we’ll create a GET
method that uses both query and route params::
[HttpGet("brand/{brand}")] public IActionResult GetProductsByBrandAndWarranty(string brand, int warranty) { return Ok(_mapper.Map<List<ProductDto>>(_products .Where(x => x.Brand == brand && x.WarrantyYears == warranty) .ToList())); }
The brand
parameter is included in the URL as a route parameter while we pass the warranty
as a query parameter. This allows retrieving _products
by the brand
while further filtering the result by the warranty
.
We’ll send a request to the endpoint:
The API filters the _products
based on the route and query parameters.
Now, let’s create a new GET
action method that uses both the [FromQuery]
and [FromRoute]
attributes:
[HttpGet("manufacturer/{manufacturer}")] public IActionResult GetProductsByBrandAndWarrantyUsingAttributes( [FromRoute(Name = "manufacturer")] string brand, [FromQuery(Name = "coverage")] int warranty) { return Ok(_mapper.Map<List<ProductDto>>(_products .Where(x => x.Brand == brand && x.WarrantyYears == warranty) .ToList())); }
We have a GET
endpoint that retrieves products based on the brand and warranty attributes. We use the [FromRoute(Name = "manufacturer")]
attribute to ensure that the value for the brand
parameter is retrieved from the route segment. Meanwhile, the [FromQuery(Name = "coverage")]
attribute indicates that the warranty
value will be obtained from the query string.
Let’s send a request:
By utilizing both the brand
route parameter and the coverage
query parameter, the API filters the _products
.
Request Body
We typically use the request body for more complex or sensitive data that we cannot easily pass in the URL. It is commonly used in HTTP POST
, PUT
, and PATCH
methods to send data to the server. According to the best practices of REST API, we don’t recommend passing parameters through the request body in a GET
method, although it is possible.
We encapsulate the parameters in a model or class to use the request body to pass multiple parameters to a GET
method. The properties of the model should correspond to the parameters we pass.
By default, the HTTP methods GET
, HEAD
, OPTIONS
, and DELETE
do not bind data implicitly from the request body. To bind data explicitly from the request body, marked as JSON, for these methods, we can use the [FromBody]
attribute. Also, we can use the [FromQuery]
attribute as we did in our Pagination article.
For this example, we are going to create another GET
method that accepts the request body using [FromBody]
attribute:
[HttpGet("category")] public IActionResult GetProductsByCategory([FromBody] ProductDto model) { return Ok(_mapper.Map<List<ProductDto>>(_products .Where(x => x.Category == model.Category) .ToList())); }
We use the route name “category” to indicate that we want to retrieve a list of _products
belonging to a specific category. We utilize the ProductDto
class to pass the model into the request body. The [FromBody]
attribute binds the request body values to the specified model.
Furthermore, we use LINQ to filter the _products
based on their category and map the resulting _products
to the ProductDto
class.
Passing data in a request body is more secure than exposing the values in the URL.
Let’s send a request to the endpoint:
The API filters the result based on the category
parameter passed in the request body in JSON format.
Header Parameters
Header parameters are another way to pass data to a GET
method using [FromHeader]
attribute. Unlike query and route parameters, header parameters are not part of the URL. Instead, we can send them as part of the request headers.
Header parameters are an essential aspect of HTTP requests that provide additional information about the request. We can use header parameters to convey information such as authentication tokens, content type, and content-encoding.
Now, we’ll create a GET
method that uses the header parameters:
[HttpGet("category-brand")] public IActionResult GetProductsByCategoryAndBrandViaHeaders([FromHeader] string category, [FromHeader] string brand) { return Ok(_mapper.Map<List<ProductDto>>(_products .Where(x => x.Category == category && x.Brand == brand) .ToList())); }
We use the [FromHeader]
attribute to bind the category
and brand
header parameter values to the corresponding method parameters. We use “category-brand” as the route name because it filters the _products
based on the provided category
and brand
. Then using the header parameters, we filter the _products
using the LINQ Where()
method, map them to ProductDto
and return them.
Using header parameters in GET requests is an excellent option to pass sensitive parameter values to avoid exposing them in the URL.
Let’s go ahead and send a request using the header parameters:
When we send the API request, we pass the category
and brand
parameters in the headers, which the API uses to filter the results and return the _products
.
Conclusion
In this article, we discussed various ways of passing parameters to a GET
method.
We commonly use query and route parameters to pass parameters in GET
requests. However, if the parameter values are sensitive, header parameters or the request body may be more appropriate. We recommend avoiding passing data in the request body for GET
requests and only considering it if other options are not feasible.