Model Binding
This tutorial explains what model binding is, how it works, and how to customize its behavior
Prerequisites
- Windows 10/11
- Visual studio 2019/2022
- Phoesion Glow Blaze , with Reactor running (see getting started guide)
Sample Code
You can download the Sample Code archive from the downloads section.
This tutorial follows the 1_REST sample in the archive. (View code on GitHub)
What is Model binding
Actions work with data that comes from incoming requests. Writing code to retrieve/deserialize and map the incoming data to .NET types would be tedious and error-prone. Model binding automates this process.
The model binding system:
- Retrieves data from various sources such as request body, form fields, and query strings.
- Provides the data to your Actions in method parameters and public properties.
- Converts incoming/outgoing data to .NET types.
- Updates properties of complex types.
Example
Suppose you have the following action method:
[Action(Methods.GET)]
public Pet GetById(int id, bool dogsOnly)
And your service receives a request with this URL:
http://foompany.com/myService/Pets/GetPet?id=2&DogsOnly=true
Model binding goes through the following steps after the routing system selects the action method:
- Finds the first parameter of
GetByID
, an integer namedid
. - Looks through the available sources in the HTTP request and finds
id
= "2" in query string. - Converts the string "2" into integer
2
. - Finds the next parameter of
GetByID
, a boolean nameddogsOnly
. - Looks through the sources and finds "DogsOnly=true" in the query string. Name matching is not case-sensitive.
- Converts the string "true" into boolean
true
The framework then calls the GetById
method, passing in 2
for the id
parameter, and true
for the dogsOnly
parameter.
In the preceding example, the model binding targets are method parameters that are simple types. Targets may also be the properties of a complex type. After each property is successfully bound, model validation occurs for that property. The record of what data is bound to the model, and any binding or validation errors, is stored in FireflyModule.ModelState. To find out if this process was successful, the app checks the ModelState.IsValid flag.
Binding Sources
Model binding can get request's data from the following sources :
- Form fields
- The request body
- Query string parameters
- Headers
- Cookies
If the default source is not correct, you can use one of the following attributes to specify the source :
- [FromQuery] - Gets values from the query string.
- [FromCookies] - Gets values from the query string.
- [FromForm] - Gets values from posted form fields.
- [FromBody] - Gets values from the request body.
- [FromHeader] - Gets values from HTTP headers.
- [FromParams] - Gets values from the Url path, using the [ParamMap(template)] mapping.
These attributes are added to action parameters, as in the following example :
[Action(Methods.POST)]
public void Add([FromBody] Pet pet, [FromHeader(Name="Accept-Language")] string language)
When the first parameter is a complex type, the [FromBody] attribute can be omitted.
- For example this action
Is equivalent to[Action(Methods.POST)] public void Add([FromBody] Pet pet, int ownerId)
[Action(Methods.POST)] public void Add(Pet pet, int ownerId)
Type conversion errors
If a source is found but can't be converted into the target type, model state is flagged as invalid. Unless specified otherwise (using [IgnoreModelValidationErrors]), an invalid model state results in an automatic HTTP 400 (BadRequest) response.
When using the [IgnoreModelValidationErrors] on a Module or Action, an invalid model state will not result in an automatic HTTP 400, but instead the execution of the Action will continue and the errors will be reported in the FireflyModule.ModelState property.
You can then examine the like so :
[Action(Methods.POST), IgnoreModelValidationErrors]
public string Add(Pet pet)
{
//manually examine model state
if (!ModelState.IsValid)
throw PhotonException.BadRequest.WithMessage("model validation failed!");
//TODO: add to db
return "ok";
}
Binding complex objects from Query string and Form
You can bind a complex object from a query string or form like so :
- Assuming a model definition :
public class MyModel { public string FullName { get; set; } public int Age { get; set; } }
- You can bind it from query string of a url :
http://foompany.com/myService/Onwers/Get?owner.Fullname=John&owner.Age=20 - To your action :
[Action(Methods.GET)] public string Get([FromQuery] MyModel owner)
Note
if [FromQuery] is missing, the default binding source for a complex object would be the request Body
Binding from Uri Path params
You can specify a map to automatically bind dynamic parameters from the Url path.
This type of binding can be accomplished with the combination of [ParamMap(template)] and [FromParams] attributes like so :
[Action(Methods.GET)]
[ParamMap("customer/{customerId}/booking/{bookingId}")]
public void GetBooking([FromParams] int customerId, [FromParams] string bookingId)
For the example above, we can use this api with this url : http://foompany.com/myService/myModule/GetBooking/customer/2/booking/123ABC
Note
You can find an example in sample 1_REST/SampleService1/SampleModule1/Action4
Collections method #1
You can bind collections from QueryString, Form, Headers, Cookies like so :
- Assuming a url
http://foompany.com/myService/Onwers/GetPets?owners=John&owners=Mike&owners=George - You can bind it to your action as a collection :
[Action(Methods.GET)] public IList<Pet> GetPets([FromQuery] List<string> owners)
Collections method #2
Another method that can be used to bind collections from QueryString is :
- Assuming a url
http://foompany.com/myService/Onwers/GetPets?owners[]=John,Mike,George - You can bind it to your action as a collection :
[Action(Methods.GET)] public IList<Pet> GetPets([FromQuery] List<string> owners)
Note
Make sure you properly escape (Url.DataEscape) each item of the collection before creating the comma-separated string.
JSON Binding from QueryString
You can bind a complex type from the QueryString using JSON like so :
- Using a model :
public class myModel { public int Value1 { get; set; } public string Value2 { get; set; } }
- And an action :
[Action(Methods.GET)] public string myAction([FromQuery] myModel input)
- You can use the Url :
http://foompany.com/myService/myModule/myAction?input={"Value1":5,"Value2":"test"}
Note
Make sure you properly escape (Url.DataEscape) the json string.