WebApi FromUri Binding url parameters with braces - asp.net-web-api2

I had an MVC controller with url which had a parameter binding with square braces in it like
public Product GetProduct([Bind(Prefix = "product[productid]") int id)
which used to work with the request url like
http://localhost:8080/Product/GetProduct?product[productid] = 1001.
I'm able to get the value for id as 1001 in MVC controller. Unfortunately I'm supposed to change this to an web api controller and I have the api controller method defined like this
public Task<Product> GetProduct([FromUri(Name=product[productid])] int id)
for which i get a 404 response with the same request url . The [FromUrl] works with the parameter name without braces when
defined like
public Task<Product> GetProduct([FromUri(Name=productid)] int id)
and the url like
http://localhost:8080/Product/GetProduct?productid = 1001
Is there any workaround for this apart from parsing the request url with braces and get the value.

Your URL is right. You should fix the controller the following way:
public Task<Product> GetProduct([FromUri(Name=product.productid)] int id)
I don't really know why this works.

Related

Net 6 API Controller Routing - Case Sensitive behavior

I have the following API Controller
[ApiController]
[Route("api/[controller]")]
public class SubContractsController: ControllerBase
{
private readonly ISubContractsRepository subContractsRepository;
public SubContractsController(ISubContractsRepository subContractsRepository)
{
this.subContractsRepository = subContractsRepository;
}
[HttpGet]
public async Task <ActionResult<IEnumerable<SubContract>>> GetSubContracts()
{
try....
I don't get result with https://localhost:7059/api/subcontracts ( full lower case) where as all the three below Urls give me the desired results. Note that S and C are capatalised differently in the below Urls.
https://localhost:7059/api/Subcontracts
https://localhost:7059/api/subContracts
https://localhost:7059/api/SubContracts
Are the routes case sensitive if so why is it working for all combinations except for one?
How do I disable the case sensitive nature?
Text matching is case-insensitive and based on the decoded
representation of the URL's path.
Read this , to know more about route.
I don't get result with https://localhost:7059/api/subcontracts ( full
lower case)
Try again, maybe something wrong. I can use lower case and all case.
Result:
I want to confirm that I've seen and wrestled this problem myself. It is real. To validate, I open with a InPrivate browser, and the API works as expected. It's not the code or .NET, it's the browser. I wanted to confirm this hear so that people can know the solution.

Exclude controller from route with string param .NET core

I want to have an endpoint that looks like: localhost:5000/abc123
This is basically to replicate the functionality of tinyurl.
Controller
[HttpGet("/{myString}")]
public async Task<IActionResult> Get(string myString)
{}
This works but all files now come through this contoller eg: localhost:5000/runtime.js etc
Is this possible to do only for certain strings?
Use Route constraint to filter values for myString
For example, if a file name is a string containing a dot . is a valid suggestion in your case, you can use the following regex to accept alphanumeric strings
[HttpGet("/{myString::regex(^\\w+$)}")]

How to bind optional querystring string parameter on POST

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!

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.

Howto select a ASP.NET Core action from a JSON property in the body?

I have a REST interface endpoint like
POST /items/12345/actions
I utilize a generic actions sub collection to be apply to apply changes to 12345 which are not easily mapped to the content or direct other sub collections of it.
My question is the following: Since there could be multiple different action types I identify the action by a JSON property of the content of an uploaded document.
How do I select a action by a part of the JSON body of the request. Is there something possible like...
[Route("api/v1/items")
public class ItemsController : Controller
{
[HttpPost("{id}/actions")]
[CheckJsonBody("type", "ActionA")]
public ActionResult DoActionA(int id, ActionA a)
{
// do something
}
[HttpPost("{id}/actions")]
[CheckJsonBody("type", "ActionB")]
public ActionResult DoActionB(int id, ActionB b)
{
// do something
}
}
The request would look like ...
{
"type": "ActionA",
"abc": "xyz"
}
I have digged myself up into the code till Microsoft.AspNetCore.Mvc.ActionConstraints.ActionMethodSelectorAttribute (GitHub).
However starting from there, I am a bit lost to reach a high-performance solution. Do I need to decode the body or is that something which is already done at that time the constraint is evaluated?
ps: And yes, I know I could handle them in one action and do a switch on the "type" property.
An ASP.NET team member was so friendly to direct me to an answer: In the ActionMethodSelectorAttribute you can read the body into a memory stream, read till the property for the selection filter. Then you seek the memory stream to zero and replace it in the request (for later model binding). You can cache the criteria value in HttpContext.Items to speed it up if you use the same property for multiple actions.