Table of Contents

Model Binding

This tutorial explains what model binding is, how it works, and how to customize its behavior

Prerequisites

  1. Windows 10/11
  2. Visual studio 2019/2022
  3. 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 named id.
  • 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 named dogsOnly.
  • 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 :

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
      [Action(Methods.POST)]
      public void Add([FromBody] Pet pet, int ownerId)
    
    Is equivalent to
      [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 :

Collections method #2

Another method that can be used to bind collections from QueryString is :

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 :

Note

Make sure you properly escape (Url.DataEscape) the json string.