ASP.NET MVC Routing? // UrlParamter.Optional Vs Empty String - c#

What is the actual difference between these two routes ?
&
These two routes are the same?
//Route 1.
routes.MapRoute("SampleRounteOne" , "{controller}/{action}/{id}" , new {id = UrlParamter.Optional})
//Route 2.
routes.MapRoute("SampleRounteTwo" , "{controller}/{action}/{id}" , new {id = ""})

Theoretically speaking:
In Route1 you have a parameter which is declared as UrlParameter.Optional, which, for instance, is optional.
In Route2 your parameter is default set to "", which is an empty string.
Practically speaking:
On Route1, your parameter will be of Nullable so the default value is null when you're passing it to the controller.
So you will have this kind of implementation:
public void MyController(string? id)
{
//dowork
}
On Route2, your parameter will be Non-Nullable type, which, for default, will be valued to "" on your controller, an empty string.
Your controller will look like this in this situation:
public void MyController(string id = "")
{
//dowork
}

No! These two routes are not the same.
//Route1
route.MapRoute("Route1" , "{controller}/{action}/{id}" , new { id = UrlParameter.Optional});
The same thing can also be acomplished by setting the id to be an empty string: { id : ""}
for example:
//Route 2.
routes.MapRoute("Route2" , "{controller}/{action}/{id}" , new {id = ""})
This seems a lot short/breif, so why not we use this ?
What is the difference?
First thing:
you asking that : These two routes are the same?
No! these two routes are not same.
Now Come to the point!
What is the actual difference between these two routes?
When we type URL parameters values are parsed out of the URL and put into a dictionary?
Now When we use UrlParameter.Optional as a default value and no value is supplied in the URL, Routing does'nt even add an entry to the dictionary. If the default value is set to an empty string, the route value dictionary will contain a value with the key "id" and the value as an empty string. In some cases, this distincition is important it lets you know the difference between the id not being specified, and it being specified but left empty.

Related

Multiple optional parameters web api attribute routing

I am new to attribute routing, and I am not sure if this is even possible.
I have an attribute route, which works fine like this:
[HttpGet]
[Route("GetIssuesByFlag/{flag:int=3}")]
public IEnumerable<IssueDto> GetIssuesByFlag(int flag)
Now I want to add some extra optional parameters to narrow down my search, so I want to add 2 extra optional parameters.
What I have tried:
[HttpGet]
[Route("GetIssuesByFlag/{flag:int=3?}/{categoryId:int?}/{tagIds?}")]
public IEnumerable<IssueDto> GetIssuesByFlag(int flag , int? categoryId = null, int?[] tagIds = null)
This works fine if my call is /api/controller/1/2, but fails with 404 when it comes to /api/controller/1.
How can I achieve this?
Edit 1: Nkosi's answer below worked, however an extra modification was needed.
[HttpGet]
[Route("GetIssuesByFlag/{flag:int=3}/{tagIds?}/{categoryId:int?}")]
public IEnumerable<IssueDto> GetIssuesByFlag(int flag , List<int> tagIds, int? categoryId = null )
The list or array must be second as it is automatically null if no value is provided and cant be marked as optional with = null.
{flag:int=3?} is the problem. it is either optional {flag:int?} with the default value in the action or {flag:int=3}.
[HttpGet]
Route("GetIssuesByFlag/{flag:int=3}/{categoryId:int?}/{tagIds?}")]
public IEnumerable<IssueDto> GetIssuesByFlag(int flag , int? categoryId = null, int?[] tagIds = null)
You currently have 3 optional parameters. when you have just the 1 value routing table wont know which optional parameter you are referring to, hence the 404
Use a query string.
[HttpGet]
[Route("GetIssuesByFlag/{flag:int=3?}")]
public IEnumerable<IssueDto> GetIssuesByFlag(int flag, List<int> tagIds, int? categoryId = null)
Url: /getissuesbyflag/1?tagIds=2,5,6&categoryId=56
You really should use query strings for optional parameters and path parameters if they are required.

Passing multiple multivalued parameters to a MVC controller from different search form

I have a website with two search forms, both calling the same controller and action. First form is for cars, second for motorcycles. Each form has multiple search filters. These filters are multivalued.
My route file:
routes.MapRoute("Cars",
"search/cars",
new { controller = "Search", action = "Index", SearchType = "Cars", Param1 = "", Param2="" }, null);
routes.MapRoute("Motorcycles",
"search/moto",
new { controller = "Search", action = "Index", SearchType = "Moto", Param3 = "", Param4="" }, null);
So calling "mywebsite.com/search/cars?Param1=BMW&Param1=VW" should get me these values into my controller:
SearchType = "Cars"
Param1[] = {"BMW", "VW"}
Is there any way to avoid having action in Search controller declared as:
public ActionResult Index(string SearchType, string Param1, string Param2, string Param3, string Param4){}
But instead have one params[] variable which would then contain key value pairs? Parameters in both cases have different names, so I can't always use the same name. Also each search page has different number of parameters.
You may want to consider using JSON and doing a POST with a JSON object. If you're method is going to take a variety of parameters to the point where it could have 3-4+ params, then you should reconsider how the data is transferred. Perhaps create a model for the search that has the filters as fields? You could then just accept the model as an object and handle it that way.
You should also consider using two different actions on the Search controller. Even though you're "searching", you've stated that each form handles different data, which means you will probably need to handle the search differently. Maybe use a Cars action and a Moto action?

MVC Routing Null parameters error

On my Index page I have the following link to the Details view:
#Html.ActionLink("Details", "Details", new { id = item.ClubId })|
My controller is expecting an int:
public ActionResult Details(int ClubId)
{
var club = _service.GetClub(ClubId);
var model = AutoMapper.Mapper.Map<ClubViewModel>(club);
return View(model);
}
Im getting this every time though:
The parameters dictionary contains a null entry for parameter 'ClubId'
of non-nullable type 'System.Int32' for method
'System.Web.Mvc.ActionResult Details(Int32)' in
'MyProject.Web.Controllers.ClubsController'. An optional
parameter must be a reference type, a nullable type, or be declared as
an optional parameter. Parameter name: parameters
I know this is something to do with routing however I have tried swapping UrlParameter.Optional to "" and making the ViewModel's ClubId nullable but the error remains.
If I rewire my controller to accept a Club object and pass in item from the Index view then everything is fine and the ClubId is populated in debug but I'm left with a stupidly large parameter list in the URL.
I don't really get what the problem is here?
Your controller is expecting a parameter named ClubId but you're passing a parameter called id. They need to match.
have you tried using ClubId instead of just id?

Multiple optional parameters routing

I have the following route definition in my webapi project. I have problem one of the parameter is not passed. eg;
when i call /Controller/Action/param2/startdate/enddate the value i passed for param2 is taken for param1 and vice versa.The problem is, the RoutingModule can not detect that the provided route value is for param2 not param1
It works if i use querystring in the url but doesn't want to use querystring. Appreciate your help.
Is there any way to achieve what i expect?
config.Routes.MapHttpRoute(
name: "RetrieveHistory",
routeTemplate: "{controller}/{action}/{param1}/{param2}/{startDate}/{endDate}",
defaults: new
{
controller = "Vend",
action = "RetrieveUtrnHistory",
param1 = RouteParameter.Optional,
param2 = RouteParameter.Optional,
starDate = RouteParameter.Optional,
endDate = RouteParameter.Optional
});
Thanks
To solve your problem you must take into account this things:
you can register more than one route. The first registered route that can handle an URL, will handle it.
you can use something apart from slash / as separator, to make parts of a route distinguishable
you can use parameter constraints, usually regular expressions, to make it easier to discover if a parameter is of one or other kind
you can specify default values for your parameters, and, if you do so, the action method must have default values for them (unless MVC, that only requires them to be nullable or of reference type)
As you didn't tell how your URL looks like I'll show you my own examples.
Let's suppose that you have a TestController Web API Controller class with an action like this:
// GET api/Test/TestAction/ ...
[HttpGet]
public object TestAction(int param1, DateTime startDate, DateTime endDate,
int? param2 = null)
{
return new
{
param1 = param1,
param2 = param2,
startDate = startDate,
endDate = endDate
}.ToString();
}
NOTE: with the default routes a Web API controller's method named GetXxx is available to HTTP GET, a method named PostXxx is available to HTTP POST and so on. However, once you include Controller and Action in the URL template, you must use the [HttpXxx] attributes to make your method available to the required HTTP method.
Optional parameter(s) in the middle
In this first example, I suppose that both param1, and param2 are integer numbers, and stardDate and endDate are dates:
http://myhost/api/Mycontroller/Myaction/12/22/2014-12-01/2014-12-31
http://myhost/api/Mycontroller/Myaction/22/2014-12-01/2014-12-31
If you want the first URL to match parameters like these:
param1 = 12; param2 = 22; startDate = 2014-12-01; endData = 2014-12-31
and the second like these:
param1 = 12; param2 = null; startDate = 2014-12-01; endData = 2014-12-31
You need to register two routes, one that will match each possible URL structure, i.e.
// for the 1st
routeTemplate: "api/{controller}/{action}/{param1}/{param2}/{startDate}/{endDate}"
// for the 2nd
routeTemplate: "api/{controller}/{action}/{param1}/{startDate}/{endDate}"
Note that, in this case, both routes are mutually exclusive, i.e. a single URL can match only one of the routes, so you can register them in any other.
However, you must notice that the second URL doesn't define a value for param2, and the TestAction method requires it. This wouldn't work: you must include a default value for this parameter, both in the controler's method and in the route registration:
action parameter int? param2 = null (C# requires optional parameter to be the last ones).
the route must include the default: defaults: new { param2 = RouteParameter.Optional }
This is the way to solve the optional parameter in the middle problem. In general, you'll need to define several routes, depending on how many optional parameters there are, and declare this parameters, with default values, in the Web API action method.
NOTE: as I wrote above, in MVC you don't need to specify a default value in the method parameter for this to work
Parameter constraints
Specifying constrains for a route parameter has two consequences:
There's a warranty that the parameter value has the expected format
Most importantly, the route will only handle the URL if the format is the expected one. So this helps you make your URL more selective, thus making it more flexible.
You simply need to add a constraint parameter on the route registration, like this:
config.Routes.MapHttpRoute(
name: "Multiparam2",
routeTemplate: "api/{controller}/{action}/{param1}/{param2}/{startDate}/{endDate}",
constraints: new
{
startDate = #"20\d\d-[0-1]?\d-[0-3]?\d", // regex
endDate = #"20\d\d-[0-1]?\d-[0-3]?\d" // regex
},
defaults: new object { }
);
Note that it's necessary to specify a defaults parameter, even if it's empty.
NOTE: the constraints in this case are a regex that only matches dates in the year 20XX, the month expressed as a single digit, or as 0x or 1x, and the date as a single digit or 0x, 1x, 2x or 3x, separated by dashes. So this regex will match 2012-1-1 or 2015-12-30, but not 1920-12-30. You should adapt the regex to your needs.
Optional parameters at the end
By this time I've explained how to support optional parameters, and how to specify formats (constraints) for them, to match a route template.
The usual way to define optional parameters is to do it at the end of the URL template, and, in this case, if there are missing params in a route, they must be all at the end of the route. (Compare this with optional in the middle: they require different routes).
In this example, if you want to make optional the param2, and the startDate and endDate, you must define them in the route registration, and set default parameter values in the action method.
The final code would look like this:
[HttpGet]
public object TestAction(int param1, int? param2 = null, DateTime? startDate = null,
DateTime? endDate = null)
{
return new
{
param1 = param1,
param2 = param2,
startDate = startDate,
endDate = endDate
}.ToString();
}
config.Routes.MapHttpRoute(
name: "Multiparam1",
routeTemplate: "api/{controller}/{action}/{param1}/{startDate}/{endDate}",
constraints: new
{
startDate = #"20\d\d-[0-1]?\d-[0-3]?\d",
endDate = #"20\d\d-[0-1]?\d-[0-3]?\d"
},
defaults: new
{
param2 = RouteParameter.Optional,
startDate = RouteParameter.Optional,
endDate = RouteParameter.Optional
}
);
config.Routes.MapHttpRoute(
name: "Multiparam2",
routeTemplate: "api/{controller}/{action}/{param1}/{param2}/{startDate}/{endDate}",
constraints: new
{
startDate = #"(20\d\d-[0-1]?\d-[0-3]?\d)?",
endDate = #"(20\d\d-[0-1]?\d-[0-3]?\d)?"
},
defaults: new
{
startDate = RouteParameter.Optional,
endDate = RouteParameter.Optional
}
);
Note, that, in this case:
the routes could be mismatched, so they must be registered in the right order, as shown. If you registered first the Multiparam2 route, it would erroneously handle an URL like this: http://localhost:1179/api/test/testaction/1/2014-12-12/2015-1-1, with param1=1; param2="2014-12-12"; startDate="2015-1-1". (You could avoid this with an additional constraint on param2 that only accepts numbers, like param2=#"\d+")
the action must have default values for startDate and endDate.
Conclusions
You can handle default parameters in different positions by carefully:
registering routes in the right order
define default parameters in the route, and also default values in the controller's action
use constraints
If you plan carefully how your routes look like, you can get what you need with a few routes and optional parameters.
JotaBe answer was nice and complete. Just you have to consider if parameters are optional you have to write routeTemplate with the order from lowest parameters to highest.
Just like :
// for the 1st
routeTemplate: "api/{controller}/{action}/{param1}/{startDate}/{endDate}"
// for the 2nd
routeTemplate: "api/{controller}/{action}/{param1}/{param2}/{startDate}/{endDate}"

ASP.NET MVC How to correctly map a parameter in url-routing

I have the following route
routes.MapRoute(
"Segnalazioni_CercaSegnalazioni",
"Segnalazioni/CercaSegnalazioni/{flag}",
new { controller = "Segnalazioni", action = "CercaSegnalazioni", flag = 7 }
);
that maps to the following methon of the class SegnalazioniController:
public ActionResult CercaSegnalazioni(int flag)
{
ViewData["collezioneSegnalazioni"] = Models.Segnalazioni.Recupera(flag);
System.Xml.Linq.XElement x = (System.Xml.Linq.XElement)ViewData["collezioneSegnalazioni"];
return View("Index");
}
How come the link http://localhost:1387/Segnalazioni/CercaSegnalazioni/1 gives me the error
The parameters dictionary contains a null entry for parameter 'flag' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ActionResult CercaSegnalazioni(Int32)' in 'RecuperoPagatiMvc.Controllers.SegnalazioniController'. To make a parameter optional its type should be either a reference type or a Nullable type.
Nome parametro: parameters
Post all your routes. It sounds like your URL is being handled by a different route than this one. Remember, the order you list your routes does matter. Therefore, if you have another route BEFORE this one that this URL can map to, it will.
MvcContrib contains route debugger. Use it and you'll see which route is called for this url. Here are some instructions how to enable it

Categories

Resources