Request and Response Models
Model binding turns an HTTP request into C# parameters; formatters turn your return value back into JSON. Knowing the rules saves hours of “why is my parameter null?”.
On this page · 6 min · Beginner
On this page
- Learning objectives
- Explanation
- Syntax
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 6 min
- Difficulty
- Beginner
- Updated
- Oct 28, 2026
Related tutorials
Learning objectives#
- Know where Web API 2 binds each parameter from
- Use [FromUri] for query objects and [FromBody] for request bodies
- Read request headers
- Configure JSON output: camelCase, no XML, ignore nulls
Explanation#
Simple types (int, string, bool, decimal, DateTime, Guid) are read from the URL — route values and the query string — by default.
Complex types (classes) are read from the request body by default, using the Content-Type header to choose a formatter (JSON or XML).
Override with attributes: [FromUri] makes a class bind from the query string (great for search filters); [FromBody] makes a simple type bind from the body. Only ONE parameter may come from the body.
Web API 2 serialises with Json.NET. By default property names stay PascalCase and the XML formatter is enabled. Most teams remove XML and switch to camelCase in WebApiConfig.
Read headers through Request.Headers (an HttpRequestHeaders). Use TryGetValues, because a missing header is normal.
Syntax#
public IHttpActionResult Get(int id) // id from the route
public IHttpActionResult Search([FromUri] ProductQuery query) // class from the query string
public IHttpActionResult Post(CreateProductRequest request) // class from the JSON body
public IHttpActionResult Rename(int id, [FromBody] string name)
Code example: Binding inputs and shaping JSON#
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
// App_Start/WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Formatters.Remove(config.Formatters.XmlFormatter); // JSON only
var json = config.Formatters.JsonFormatter.SerializerSettings;
json.ContractResolver = new CamelCasePropertyNamesContractResolver();
json.NullValueHandling = NullValueHandling.Ignore;
json.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
}
}
public class ProductQuery // bound from ?category=Books&maxPrice=500
{
public string Category { get; set; }
public decimal? MaxPrice { get; set; }
}
public class CreateProductRequest // bound from the JSON body
{
public string Name { get; set; }
public decimal Price { get; set; }
}
public class ProductResponse
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Badge { get; set; } // null → left out of the JSON
}
[RoutePrefix("api/products")]
public class ProductsController : ApiController
{
[HttpGet, Route("")]
public IHttpActionResult Search([FromUri] ProductQuery query)
{
return Ok(new { query.Category, query.MaxPrice });
}
[HttpPost, Route("")]
public IHttpActionResult Create(CreateProductRequest request)
{
var response = new ProductResponse { Id = 7, Name = request.Name, Price = request.Price };
return Created("api/products/7", response);
}
[HttpPut, Route("{id:int}/name")]
public IHttpActionResult Rename(int id, [FromBody] string newName)
{
return Ok(new { id, newName });
}
[HttpGet, Route("language")]
public IHttpActionResult Language()
{
IEnumerable<string> values;
var language = Request.Headers.TryGetValues("Accept-Language", out values) ? values.First() : "en";
return Ok(new { language });
}
}
GET /api/products?category=Books&maxPrice=500
→ 200 {"category":"Books","maxPrice":500.0}
POST /api/products Content-Type: application/json {"name":"Pen","price":20}
→ 201 {"id":7,"name":"Pen","price":20.0} (badge is null, so it is omitted)
PUT /api/products/7/name Content-Type: application/json "Gel Pen"
→ 200 {"id":7,"newName":"Gel Pen"}
GET /api/products/language Accept-Language: hi-IN
→ 200 {"language":"hi-IN"}This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.
Step-by-step explanation#
- WebApiConfig removes the XML formatter and sets camelCase names, so C# Name becomes JSON name.
- [FromUri] ProductQuery collects several query-string values into one object.
- CreateProductRequest is a class, so it is read from the JSON body automatically.
- [FromBody] string reads a raw JSON string — the body must be "Gel Pen" in quotes.
- NullValueHandling.Ignore drops null properties such as badge from the response.
- Request.Headers.TryGetValues reads an optional header safely.
Real-world use case#
A React front end expects camelCase JSON. One change in WebApiConfig makes every endpoint match, instead of decorating hundreds of properties with [JsonProperty("name")].
Common mistakes#
Usually the Content-Type header is missing or wrong (send application/json), or the JSON does not match the class property names/types.
Web API 2 can read the body only once. Combine them into one request class.
The JSON formatter expects "text" with quotes. Unquoted text binds to null.
Best practices#
- Use one request class per POST/PUT action
- Use [FromUri] classes for filters and paging
- Configure JSON globally once
- Return response classes, not anonymous objects, for documented endpoints
Remember this#
Simple types → URL. Classes → body.
[FromUri] class = query-string object. Only one [FromBody].
camelCase: CamelCasePropertyNamesContractResolver.
Practice exercise#
Your task: Write an action GET api/reports/sales that binds a [FromUri] SalesQuery with From and To (DateTime) and Region (string, optional), and returns them back. What query string calls it?
Need a hint?
Show solution
using System;
using System.Web.Http;
public class SalesQuery
{
public DateTime From { get; set; }
public DateTime To { get; set; }
public string Region { get; set; }
}
public class ReportsController : ApiController
{
// GET api/reports/sales?from=2026-01-01&to=2026-03-31®ion=South
[HttpGet, Route("api/reports/sales")]
public IHttpActionResult Sales([FromUri] SalesQuery query)
{
return Ok(query);
}
}
Mini quiz#
By default, where does Web API 2 bind a complex type parameter from?
Want more? Practise all ASP.NET Web API MCQs · ASP.NET Web API interview questions
My notes#
Saved in this browser.