Optional query string parameters in ASP.NET Web API
Asked Answered
E

6

245

I need to implement the following WebAPI method:

/api/books?author=XXX&title=XXX&isbn=XXX&somethingelse=XXX&date=XXX

All of the query string parameters can be null. That is, the caller can specify from 0 to all of the 5 parameters.

In MVC4 beta I used to do the following:

public class BooksController : ApiController
{
    // GET /api/books?author=tolk&title=lord&isbn=91&somethingelse=ABC&date=1970-01-01
    public string GetFindBooks(string author, string title, string isbn, string somethingelse, DateTime? date) 
    {
        // ...
    }
}

MVC4 RC doesn't behave like this anymore. If I specify fewer than 5 parameters, it replies with a 404 saying:

No action was found on the controller 'Books' that matches the request.

What is the correct method signature to make it behave like it used to, without having to specify the optional parameter in the URL routing?

Eam answered 8/8, 2012 at 9:58 Comment(6)
put [httpget] on action.Intemperate
If I set all of the parameters the method gets called; furthermore it starts with Get so it is automatically bound with the HTTP GET method...Eam
This is how web api routing works, asp.net/web-api/overview/web-api-routing-and-actions/…Intemperate
Yes. I know how it works. I just can't get it to work under THIS particular circumstance.Eam
How did this even compile? string? is not a valid type. You can't declare string as a nullable type since it is a reference type.Dwayne
@Dwayne you are right, it probably was a quick gist of code I wrote just for the purpose of understanding why I wasn't able to default to null. As I recall, the real issue was with DateTime which couldn't be defaulted until the beta release. Also, this question is from 1 year and a half ago.Eam
E
337

This issue has been fixed in the regular release of MVC4. Now you can do:

public string GetFindBooks(string author="", string title="", string isbn="", string  somethingelse="", DateTime? date= null) 
{
    // ...
}

and everything will work out of the box.

Eam answered 12/9, 2012 at 11:39 Comment(7)
Can I use null here as a default? For instance: string author=null ?Eroticism
Yes, null is considered a constant expression, and therefore a valid default value.Aloise
I wonder why we have to mention default values even for optional parameters as said here . Any type in C# always have a default value so routing run-time could have taken the type's default value if it didn't receive it from the URI. What is the technical reason behind this?. I'm sure this has something to do with model binder.Partridgeberry
@Partridgeberry So that the route can be matchedGorman
I was using date parameters and if i just set them to nullable was not working. So i have to set it nullable and the set null as default value, and use server side validation accordingly and return error messages back. It worked.Spade
@AttaH. That's strange because at least on Core 3.1 objects of nullable types default to null for me without having to set them explicitly.Ruthie
@Eam I want to do something like. I have two optional params. I want the user to pass at least any one of the param but not both combinedSimoneaux
F
97

It's possible to pass multiple parameters as a single model as vijay suggested. This works for GET when you use the FromUri parameter attribute. This tells WebAPI to fill the model from the query parameters.

The result is a cleaner controller action with just a single parameter. For more information see: http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api

public class BooksController : ApiController
  {
    // GET /api/books?author=tolk&title=lord&isbn=91&somethingelse=ABC&date=1970-01-01
    public string GetFindBooks([FromUri]BookQuery query)
    {
      // ...
    }
  }

  public class BookQuery
  {
    public string Author { get; set; }
    public string Title { get; set; }
    public string ISBN { get; set; }
    public string SomethingElse { get; set; }
    public DateTime? Date { get; set; }
  }

It even supports multiple parameters, as long as the properties don't conflict.

// GET /api/books?author=tolk&title=lord&isbn=91&somethingelse=ABC&date=1970-01-01
public string GetFindBooks([FromUri]BookQuery query, [FromUri]Paging paging)
{
  // ...
}

public class Paging
{
  public string Sort { get; set; }
  public int Skip { get; set; }
  public int Take { get; set; }
}

Update:
In order to ensure the values are optional make sure to use reference types or nullables (ex. int?) for the models properties.

Filibertofilibuster answered 23/5, 2014 at 14:16 Comment(4)
Yes, but the [FromUri] decorator alone does not appear to support optional parameters.Abbott
@JohnMeyer You are correct using [FromUri] doesn't directly answer the original question. It basically says populate these models with the values from the Uri. The models properties would need to be nullable or a reference type in order for them to support being optional. Added additional information.Filibertofilibuster
@AndrewC - Could you elaborate on when/why you need to use nullables to ensure values are optional? If you don't make the values nullable (for ex, property int Skip) and there is no query param for that property specified, the API Controller method will still successfully match the request and the value for Skip will just be the default value for that type, or 0 in this caseUndershoot
@Undershoot - Without using a nullable type you won't know if the user did not provide a value and got the uninitialized type value (0 for int) or if the user specified 0. By using nullable you are sure the user left it undefined therefore you can safely apply your default in the controller action. If you look at Take from the example above, what should the action do if it received a 0 for Take? Did the user mean to request 0 records or did they not specify it and therefore you should take all records. Generally if you want a value type (int, bool, etc.) to be optional then it should be nullable.Filibertofilibuster
C
81

Use initial default values for all parameters like below

public string GetFindBooks(string author="", string title="", string isbn="", string  somethingelse="", DateTime? date= null) 
{
    // ...
}
Cedell answered 8/8, 2012 at 11:59 Comment(5)
This is the correct procedure but for one thing: DateTime is not nullable. I have already tried to use DateTime? instead, but then MVC does not map the request to the given method if I set only some of the parameters in my HTTP request.Eam
you can pass date as a string and parse it inside your controller function using DateTime.Parse() function.Cedell
@MuhammadAmin, DateTime is not a nullable data type. Your code should not compile, as you would not be able to assign a null value to a parameter of type DateTime. Perhaps, you should change it to DateTime?, or use different value for a default like DateTime.Now.Crural
@IvayloSlavov DateTime.Now is not a compile time constant so it cant be assigned as the default parameter.Unexpected
@GiriB, you're right indeed. Datetime.Now cannot be used in default parameter initialization, I stand corrected.Crural
B
2

For newer versions, setting the parameter as nullable works fine:

public IActionResult GetData(string? parameterFromQuery)
{
...
}
Beautify answered 26/9, 2023 at 17:30 Comment(1)
Avoid answering old questions that already have solutions available, especially if they are more than a decade old. It would be more beneficial to focus on providing support for new questions.Neigh
H
1

if you want to pass multiple parameters then you can create model instead of passing multiple parameters.

in case you dont want to pass any parameter then you can skip as well in it, and your code will look neat and clean.

Holtz answered 12/9, 2012 at 11:51 Comment(1)
This is only true for the POST parameters in the request body - params in the url can still be reference individually as arguments.Premonitory
Y
1

Default values cannot be supplied for parameters that are not declared 'optional'

 Function GetFindBooks(id As Integer, ByVal pid As Integer, Optional sort As String = "DESC", Optional limit As Integer = 99)

In your WebApiConfig

 config.Routes.MapHttpRoute( _
          name:="books", _
          routeTemplate:="api/{controller}/{action}/{id}/{pid}/{sort}/{limit}", _
          defaults:=New With {.id = RouteParameter.Optional, .pid = RouteParameter.Optional, .sort = UrlParameter.Optional, .limit = UrlParameter.Optional} _
      )
Yon answered 21/2, 2013 at 10:54 Comment(1)
Actually, they can. I'm using C#, not VB.NET.Eam

© 2022 - 2024 — McMap. All rights reserved.