Different action method depending on parameter value? - asp.net-mvc-4

I have two urls like this,
/products/home?subcat=abc
/products/home?subcat=xyz
I would like to have two action methods in products controller - homeabc, and homexyz.
How can I construct the route so it goes to the correct action method, depending on the value of the 'subcat' parameter?
PS: I don't have the freedom to change the url format, it has to stay the same.
Thanks.

Since you cannot change the URL formats and you only have one or two, the easiest way would be to do some type of switch in the Home/Index method
public ActionResult Index(string subcat){
switch(subcat.ToLower())[
case "abc":
return RedirectToAction("abc");
case "xyz":
return RedirectToAction("xyz");
default:
return RedirectToAction("UnknownCategory", "Errors");
}
}
Unfortunately, the routing engine does not allow you to route based on query string parameters (?). The only other possibility would be a url rewrite.

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.

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!

Pass multiple optional parameters to Controller Action

I have a controller Action that returns a list of activities to a view. (eventID,Venue,Room,EventDescription,EventType,StartDateTime,EndDateTime). The users wanted to be able to filter by Venue so I added Venue as id to the action method
ActionResult ListEvents(id string)
{
... Get the relevant details and return the view with the model
}
Now they want to also be able to filter by any/all of Event Type, Start, End, whether Post-event data has been completed.
Am I better to add these as GET query parameters or to define a custom route that will accept all 5 arguments or is there another solution
I will also need to add sorting and pagination at some point in case this changes the suggestion.
Typically, these would be handled via a query string, but it doesn't matter how you do it really. Regardless, of how the parameters are sent, your action simply needs to accept them all. The only thing you have to be aware of is the standard C# method rule (since actions are just methods) that optional parameters must be the last ones on the method. If they're all optional, then even that isn't really a concern.
Basically, you just have something like:
public ActionResult ListEvents(string id = null, int? eventID = null, ...)
{
Then inside, you'd just do something like:
var events = db.Events;
if (eventID.HasValue)
{
events = events.Where(m => m.EventID == eventId);
}
// rinse and repeat for each filter

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.

Web API 2 Routing based on Parameter's Value

Is it possible to setup Web Api 2 route based on a parameter's value in the query string.
I have the following requirement:
/api/controller?data=param.data1
should go to controller's action 1
/api/controller?data=param.data2
should go to controller's action 2
any other value of data must go to action 3.
I know there's an option to set a constraint with a regex, but the examples I've found are for generic scenarios and not as specific as mine.
This is what I've tried
config.Routes.MapHttpRoute(
name: "test",
routeTemplate: "api/Hub/{data2}",
defaults: new { action = "Test" },
constraints: new { data2 = #"^(param\.data2)$" }
);
Is there a way to do it? Maybe there's a better way?
Important to note, I cannot change the URI of the service. It must have ?data=[value]
This is a fallback for a legacy system :(
You can use Attribute Routing, new in Web API 2.
Let's say you have the following actions, where the data param is, let's say, a string:
public Stuff GetStuffForData1(string data) { ... }
public Stuff GetStuffForData2(string data) { ... }
public Stuff GetStuffForData(string data) { ... }
Since you mentioned regex, you can specify route constraints for each of the above actions using a regex like the one you mentioned in your question1, for example:
[Route("controller/{data:regex(#"^(param\.data1)$")]
public Stuff GetStuffForData1(string data) { ... }
[Route("controller/{data:regex(#"^(param\.data2)$")]
public Stuff GetStuffForData2(string data) { ... }
// No need for a route constraint for other data params.
public Stuff GetStuffForData(string data) { ... }
The general syntax is {parameterName:constraint(params)} (params is optional and is not used for all constraints). In the above example, the first route will only be selected if the data segment of the URI matches the data1 regex. Similarly, the second route will be selected if the data segment of the URI matches the data2 regex. Otherwise, the last route will be chosen.
In general, the total ordering is determined as follows:
Compare the RouteOrder property of the route attribute. Lower values are evaluated first. The default order value is zero.
Look at each URI segment in the route template. For each segment, order as follows:
Literal segments.
Route parameters with constraints.
Route parameters without constraints.
Wildcard parameter segments with constraints.
Wildcard parameter segments without constraints.
In the case of a tie, routes are ordered by a case-insensitive ordinal string comparison (OrdinalIgnoreCase) of the route template.
You can even create your own custom route constraints by implementing the IHttpRouteConstraint interface and registering it in the Register method of your WebApiConfig class, assuming you're hosting on IIS, or in the Configuration method of your Startup class if self-hosting using OWIN.
Note I haven't personally tried any of the above, but it should all work; at the very least it should give you some ideas. For more details, including very nice examples, you should start by taking a look at the following article (which I shamelessly used extensively in my answer):
http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2#constraints
1 I'm really not an expert on writing regexes, so unfortunately I can't advise you on the specific ones you'll need.