45

I am trying to use model binding from query parameters to an object for searching.

My search object is

[DataContract]
public class Criteria 
{
  [DataMember(Name = "first_name")]
  public string FirstName { get; set; }
}

My controller has the following action

[Route("users")]
public class UserController : Controller 
{
  [HttpGet("search")]
  public IActionResult Search([FromQuery] Criteria criteria)
  {
    ...
  }
}

When I call the endpoint as follows .../users/search?first_name=dave the criteria property on the controller action is null. However, I can call the endpoint not as snake case .../users/search?firstName=dave and the criteria property contains the property value. In this case Model Binding has worked but not when I use snake_case.

How can I use snake_case with Model Binding?

Tseng
  • 61,549
  • 15
  • 193
  • 205
Carl Thomas
  • 3,605
  • 6
  • 38
  • 50
  • Possible duplicate of [Model binding in ASP.NET Core to map underscores to title case property names](http://stackoverflow.com/questions/38305295/model-binding-in-asp-net-core-to-map-underscores-to-title-case-property-names) – Win Mar 21 '17 at 15:43

7 Answers7

69

You need to add [FromQuery] attribute to the model properties individually

public class Criteria
{
  [FromQuery(Name = "first_name")]
  public string FirstName { get; set; }
}
Carl Thomas
  • 3,605
  • 6
  • 38
  • 50
  • 2
    What if I want to use data contracts with members with the custom names for GET and POST endpoints, so in the first case it will be FromQuery, and FromBody for POST request? – Dmitry Pavlov Nov 29 '17 at 13:42
  • 13
    I can't get this to work but it is marked as an answer. – sander Mar 08 '18 at 15:10
  • 6
    I just want to add that I had an issue which other people could encounter too: I named my parameter 'model'. My object had a property which was also named 'model'. The databinding wouldn't work untill I renamed my parameter. – sander Mar 08 '18 at 15:41
  • 11
    `FromQuery` sits in `Microsoft.AspNetCore.Mvc` package, it is a bit sad to add this dependency to the project where you only store your POCO. – Ramūnas Feb 22 '19 at 13:34
  • 2
    @sander 's solution worked for me - thank you I spend well over an hour trying to figure this one out. Lesson learned to check all parameter names. – elembie Aug 18 '20 at 05:06
  • @elembie yeah, this one was a real gotcha! – sander Aug 18 '20 at 06:38
42

Solution for .net core 2.1, 2.2, 3.0 and 3.1

Or without attributes you can do something like this which is cleaner I think (of course if the model properties are same as query parameters).

Meanwhile I use it in .net core 2.1, 2.2 and 3.0 preview & 3.1.

public async Task<IActionResult> Get([FromQuery]ReportQueryModel queryModel) 
{ 

}
peyman gilmour
  • 1,168
  • 2
  • 16
  • 35
12

For anyone that got here from search engine like me:

To make it work on asp.net core 3.1+

public async Task<IActionResult> Get([FromQuery] RequestDto request);

public class RequestDto
{
  [FromQuery(Name = "otherName")]
  public string Name { get; set; }
}

Will read json property otherName into RequestDto.Name so basically you have to use FromQuery in 2 places. Above answers are IMHO too complicated for such a simple thing already provided in asp.net framework.

n.podbielski
  • 595
  • 6
  • 11
  • It will also works with ```[JsonPropertyName("otherName")]``` on your properties. Because of that I´m assuming it will also work with Newtonsoft.JSON annotations (but didn´t try). So you can just specify ```[FromQuery]``` in your method and could still use the same object in a Body when needed (without additional annotations). No need to be this specific about it. – Max Nov 08 '20 at 14:32
  • 2
    @Max - hi Max. No it doesn't work for Newtonsoft [JsonPropery(Name = "otherName")] – code5 May 04 '21 at 02:43
2

In my case, I had an issue where my parameter name was option and in my class I also had the property called option so it was collapsing.

public class Content
{
    
    public string Option { get; set; }
    
    public int Page { get; set; }
}

public async Task<IActionResult> SendContent([FromQuery] Content option)

changed the parameter to something else:

public async Task<IActionResult> SendContent([FromQuery] Content contentOptions)
Giga Meta
  • 299
  • 2
  • 16
2

In .NET 6, I needed to add [FromQuery] to the endpoint's parameter, as well as add [FromQuery(Name="")] to each of the model's properties.

Rather than add the MVC dependency to my model project, I turned the service model into an interface and made a new model in the WebAPI project, which can use the annotations and implement the interface, so I don't need to create/map 2 models.

Emery Noel
  • 45
  • 4
1

According to @Carl Thomas answer, here is the easier and the strongly typed way to have snake case FromQuery name:

CustomFromQuery

public class CustomFromQueryAttribute : FromQueryAttribute
{
    public CustomFromQuery(string name)
    {
        Name = name.ToSnakeCase();
    }
}

StringExtensions

public static class ObjectExtensions
{
  public static string ToSnakeCase(this string o) => Regex.Replace(o, @"(\w)([A-Z])", "$1_$2").ToLower();
}

Usage

public class Criteria
{
   [CustomFromQuery(nameof(FirstName))]
   public string FirstName { get; set; }
}
th3morg
  • 4,321
  • 1
  • 32
  • 45
Soheil Alizadeh
  • 2,936
  • 11
  • 29
  • 56
-1

If the public async Task<IActionResult> Get([FromQuery] RequestDto request);

not work for anyone, you can try [FromRoute]

public async Task<IActionResult> Get([FromRoute] RequestDto request);.

In your dto you must keep the [FromQuery]

public class RequestDto
{
  [FromQuery(Name = "otherName")]
  public string Name { get; set; }
}