ChatGPT解决这个技术问题 Extra ChatGPT

Optional query string parameters in ASP.NET Web API

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?

put [httpget] on action.
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...
Yes. I know how it works. I just can't get it to work under THIS particular circumstance.
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.

f
frapontillo

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.


Can I use null here as a default? For instance: string author=null ?
Yes, null is considered a constant expression, and therefore a valid default value.
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.
@RBT So that the route can be matched
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.
A
Andrew C

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.


Yes, but the [FromUri] decorator alone does not appear to support optional parameters.
@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.
@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 case
@Clark - 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.
M
Muhammad Amin

Use initial default values for all parameters like below

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

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.
you can pass date as a string and parse it inside your controller function using DateTime.Parse() function.
@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.
@IvayloSlavov DateTime.Now is not a compile time constant so it cant be assigned as the default parameter.
@GiriB, you're right indeed. Datetime.Now cannot be used in default parameter initialization, I stand corrected.
v
vijay

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.


This is only true for the POST parameters in the request body - params in the url can still be reference individually as arguments.
R
Rizwan Mumtaz

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} _
      )

Actually, they can. I'm using C#, not VB.NET.