How to bind optional querystring string parameter on POST - asp.net-web-api2

I have the following controller in ASP.NET WebApi 2:
[RoutePrefix("Validations")]
public partial class ValidationsController
{
[HttpPost, Route("Bsb")]
public IHttpActionResult ValidateBsb(string value)
{
var validator = new BankStateBranchValidator(DbContext.BankStateBranches);
var data = new ValidationsResult
{
IsValid = validator.IsValid(value ?? string.Empty)
};
data.Error = data.IsValid
? null
: "The BSB you have entered does not appear to be valid. Please check the value and try again.";
return Ok(data);
}
}
For historical reasons, the value parameter needs to be in the querystring, rather than the form body, which should be empty. So the expected API call would be POST /Validate/Bsb?value=012345.
That all works fine, and I get the expected result; however, sometimes we are getting clients calling the API with POST /Validate/Bsb or POST /Validate/Bsb?value=, and that is resulting in a 400 Bad Request response from WebAPI itself, because, as far as I can tell, the model binder is failing to bind the missing value to the parameter. If I put a breakpoint inside the method, it never gets hit.
So, given that I can't change the API contract, how can I handle this scenario? I've tried adding a [ValueProvider(typeof(RouteDataValueProviderFactory))] attribute to the parameter, and my test case for the missing value works, but then the valid value test cases break since the value isn't in the route but in the querystring.
Update
Based on Craig H's suggestion, I've added a default value to the value parameter. So the various scenarios are:
POST /Validate/Bsb?value=012345 - pass (valid value)
POST /Validate/Bsb?value=000000 - pass (invalid value)
POST /Validate/Bsb?value= - fail (empty value)
POST /Validate/Bsb - pass (missing value)

You should be able to make the parameter optional by specifying a default value in the method signature.
e.g.
[HttpPost, Route("Bsb")]
public IHttpActionResult ValidateBsb(string value = null)
Your question says that a query with ?value= was throwing a bad request.
When I tried this locally my breakpoint was hit and value was null.
If I omitted the QS parameter completely, then I received a method not allowed response.
This page makes mention of optional route parameters with attribute routing, although you are not specifying the parameter like that here.
I cannot find the document which describes the other options with regards to routing and optional parameters. I have seen one which indicates the differences between defining it as optional in the route definition, and optional in the method signature. If I find it, I will update this answer!

Related

Api Platform pagination custom page_parameter_name

I have very specific question on which I cannot find any answer and/or solution provided for Api Platform.
By default, the documentation states, that if you want to pass a page parameter for paging action, you must do the following:
pagination:
page_parameter_name: _page
However, due to the nature of our frontend we're not able to pass this variable to the request. It is hardcoded to the frontend request and is something like page[number]=1.
Is it possible to configure page_parameter_name to receive this variable or we need to transform it somehow in the Api itself?
Thank you!
ApiPlatform\Core\EventListener\ReadListener::onKernelRequest gets $context['filters'] from the request through ApiPlatform\Core\Util\RequestParser::parseRequestParams which ultimately uses PHP's parse_str function so the value of 'page[number]' will be in $context$context['filters']['page']['number'].
ApiPlatform\Core\DataProvider\Pagination::getPage retrieves the page number from $context['filters'][$parameterName] so whatever the value of [$parameterName] it will at best retrieve the array ['number'=> 1].
Then ::getPage casts that to int, which happens to be 1. But will (at least with PHP7) be 1 for any value under 'number'.
Conclusion: You need to transform it somehow in the Api itself. For example by decoration of the ApiPlatform\Core\DataProvider\Pagination service (api_platform.pagination).
API_URL?page[number]=2
print_r($request->attributes->get('_api_pagination'));
Array(
[number] => 2
)
The value of the "page_parameter_name" parameter should be "number" .
api_platform.yaml
collection:
pagination:
page_parameter_name: number
This may not work in version 3
vendor/api-platform/core/src/JsonApi/EventListener/TransformPaginationParametersListener.php
public function onKernelRequest(RequestEvent $event): void
{
$request = $event->getRequest();
$pageParameter = $request->query->all()['page'] ?? null;
...
/* #TODO remove the `_api_pagination` attribute in 3.0 */
$request->attributes->set('_api_pagination', $pageParameter);
}

Put request to /api/controller/:id instead of /api/controller?id=:id

I'm currently doing this:
[HttpPut]
public void Edit(int id, Model model)
{
...
}
Which gives me the endpoint /api/controller?id=66 instead of what I want: /api/controller/66
To get what you want -- api/controller/66 on your PUT request, your HTTP verb attribute should be modified to [HttpPut("{id}")]
And the further reason why your id is obtained from the query string by default is that the parameter binding in the case of PUT request works in such a way that the primitive type is bound from request query string and the complex type from request body.
A brief of the parameter binding rules is listed out in this answer.

ASP.NET Core OData Action With Two Parameters

I have this action method available to OData:
[HttpPost]
[ODataRoute("({id})/Replace")]
public Blog Replace([FromODataUri] int id, Blog blog)
This responds for POST requests on /odata/Blogs(1)/Replace.
It is working except for the part that the blog parameter is never bound from the POST, that is, it is not null, but has default values for all properties. If I add [FromBody] to the parameter, it becomes null. I also tried with a parameter of type ODataActionParameters, but it is always null.
This is the relevant part of my model:
var replace = builder.EntitySet<Blog>("Blogs").EntityType.Action("Replace");
//replace.Parameter<int>("id").Required(); //this can be added or not, it doesn't matter
replace.EntityParameter<Blog>("blog").Required();
replace.Returns<int>();
I read somewhere that an OData action cannot have two parameters, but I am not sure of that. I need the id parameter to be present, so I need to find a solution for this.
I am using ASP.NET Core 3.1 and the latest versions of all packages.
What am I doing wrong?
The solution turned out to be simple:
The parameter declaration was wrong: the name of the entityset is “blogs”, not “blog”
I was posting the JSON for the “Blog” entity, but I had to modify it so as to be included in a parameter “blog”, as this:
{
“blog”: {
“BlogId”: 1,
<< rest of the Blog properties >>
}
}
This solved my problem.

Where is my null entry for parameter error coming from?

The parameters dictionary contains a null entry for parameter
'restaurantId' of non-nullable type 'System.Int32' for method
'System.Web.Mvc.ActionResult Index(Int32)' in
'OdeToFood.Controllers.ResturantReviewsController'. An optional
parameter must be a reference type, a nullable type, or be declared as
an optional parameter.
The error only occurs when I click the back to list button
I am getting the same error as the patron posting here. I have tried out all of the suggestions on that post but I am able to add some additional points of reference not listed on that question. I noticed that when I hover on the 'back to list' link the url on the bottom of the screen does not contain the restaurantId which got me questioning as to why/how it works on the Pluralsight tutorial anyway.
I have even gone so far as replacing all of my files with those from the provided exercise files and I get the same error.
Here is the code that I believe is causing the error:
[HttpPost]
public ActionResult Edit(RestaurantReview review)
{
if (ModelState.IsValid)
{
_db.Entry(review).State = EntityState.Modified;
_db.SaveChanges();
return RedirectToAction("Index", new { id = review.RestaurantId });
}
return View(review);
}
For the code that you posted (it helps but not much, should have posted the view's code and the create/edit/reviews actions code) my assumption is below:
What is happening is that you have an action that expected to receive an restaurantId (of type int, that means, not nullable) but it is not receiving any value for restaurantId, causing that exception. Probably, for what you say and looking at the code, it is the Reviews action that expects a restaurantId that it is not being provided. If the Reviews action has an restaurantId as parameter you should create the "Back Link" with that id.
Hope I´ve helped.
Regards,

Optional query string parameters in URITemplate in WCF?

I'm developing some RESTful services in WCF 4.0. I've got a method as below:
[OperationContract]
[WebGet(UriTemplate = "Test?format=XML&records={records}", ResponseFormat=WebMessageFormat.Xml)]
public string TestXml(string records)
{
return "Hello XML";
}
So if i navigate my browser to http://localhost:8000/Service/Test?format=XML&records=10, then everything works as exepcted.
HOWEVER, i want to be able to navigate to http://localhost:8000/Service/Test?format=XML and leave off the "&records=10" portion of the URL. But now, I get a service error since the URI doesn't match the expected URI template.
So how do I implement defaults for some of my query string parameters? I want to default the "records" to 10 for instance if that part is left off the query string.
Note: This question is out of date, please see the other answers.
This does not appear to be supported.
However, Microsoft has been made aware of this issue and there is a work-around:
You can get the desired effect by
omitting the Query string from the
UriTemplate on your WebGet or
WebInvoke attribute, and using
WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters
from within your handlers to inspect,
set defaults, etc. on the query
parameters.
https://connect.microsoft.com/VisualStudio/feedback/details/451296/
According to this answer this is fixed in .NET 4.0. Failing to supply the query string parameter seems to result in its being given the default value for the type.
Check this blog post out. Makes sense to me, and comes with a class to parse out the query string parameters.
http://blogs.msdn.com/b/rjacobs/archive/2009/02/10/ambiguous-uritemplates-query-parameters-and-integration-testing.aspx
Basically don't define the query string parameters in the UriTemplate so it matches with/without the parameters, and use the sample class to retrieve them if they're there in the method implementation.
This seems to work in WCF 4.0.
Just make sure to set your default value in your "Service1.svc.cs"
public string TestXml(string records)
{
if (records == null)
records = "10";
//... rest of the code
}
While this is an old question, we still come to this scenario from time to time in recent projects.
To send optional query parameters, I created WCF Web Extensions nuget package.
After installation, you can use the package like this:
using (var factory = new WebChannelFactory<IQueryParametersTestService>(new WebHttpBinding()))
{
factory.Endpoint.Address = new EndpointAddress(ServiceUri);
factory.Endpoint.EndpointBehaviors.Add(new QueryParametersServiceBehavior());
using (var client = factory.CreateWebChannel())
{
client.AddQueryParameter("format", "xml");
client.AddQueryParameter("version", "2");
var result = client.Channel.GetReport();
}
}
Server side you can retrieve the parameters using WebOperationContext:
WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters;