MVC routing with optional parameter - asp.net-mvc

I have this route set up:
routes.MapRoute(
"home3", // Route name
"home3/{id}", // URL with parameters
new {
controller = "home",
action = "Index",
id = UrlParameter.Optional } // Parameter defaults
);
But in my controller I don't know how to get the optional id parameter. Can someone explain how I can access this and how I deal with it being present or not present.
Thanks

your can write your actionmethod like
public ActionResult index(int? id)
{
if(id.HasValue)
{
//do something
}
else
{
//do something else
}
}

How to avoid nullable action parameters (and if statements)
As you've seen by #Muhammad's answer (which is BTW the one to be accepted as the correct answer) it's easy to get optional parameters (any route parameters actually) into controller actions. All you you have to make sure is that they're nullable (because they're optional).
But since they're optional you end up with branched code which is harder to unit test an maintain. Hence by using a simple action method selector it's possible to write something similar to this:
public ActionResult Index()
{
// do something when there's not ID
}
[RequiresRouteValues("id")]
public ActionResult Index(int id) // mind the NON-nullable parameter
{
// do something that needs ID
}
A custom action method selector has been used in this case and you can find its code and detailed explanation in my blog post. These kind of actions are easy to grasp/understand, unit test (no unnecessary branches) and maintain.

Related

Why use "new" for Route Values in "ActionLink" ---- for example

I have 2 examples below:
#Html.ActionLink("Create New", "Create", new { id = Model.Id })
and,
return RedirectToAction("Index", new { id = review.RestaurantId });
My question is regarding the new { id = xxx} part in object route values. Why do we use "new" in this case? What exactly does it do? Does it initialize "id" variable in this case?
Also, it is strange that these methods, create and index definition can only take arguments as defined in the route values...
That is,
public ActionResult create { int id)
{ ...}
is correct but following is wrong....
public ActionResult create { int somethingelse)
{ ...}
So please tell me what is the new {id = xx} in my first 2 examples is doing?
Thanks
new {} creates a new object of type Object. The type is anonymous. You see that syntax when writing linq queries that end in " select new {x = "foo". y="bar"}". It is often used when setting an object to type "var".
What you are doing in your ActionLink is providing Route Values. MVC takes the properties and values in the object and puts them in the QueryString of the request. It is what you might refer to as "magic". You can set a break point in your controller Action and check "HttpContext.Request.QueryString" to see it.
The input values for you Action methods have to match the properties that are being passed in via the QueryString.
That is actually creating an anonymously typed object and passing it into ActionLink(). ActionLink then uses that object, coupled with your routing rules to generate the link. MVC will look for properties on that object that match the routing names (usually of route parameters) and figure out how to build it. Since you likely have the default MVC route (/controller/action/{id}) that is what links everything together.
Further, that is why id "is correct", but somethingelse "is wrong".
If you change "id" to "somethingelse" in your routing rule, you could then see new { soemthingelse = ""} work in your ActionLink().
Does that help?
In both cases your creating a new anonymous object to pass into the query string as a route value. You create a new object because one does not already exist on the view.
The MVC source code:
if (additionalViewData != null) {
foreach (KeyValuePair<string, object> kvp in new RouteValueDictionary(additionalViewData)) {
viewData[kvp.Key] = kvp.Value;
}
}
They use it to create new RouteValueDictionary parameters.
You don't have to use it the this manor. You could create an object on the model and pass it in:
public class SomeModel
{
public SomeModel()
{
MyObject = new { id = 10 };
}
public int Id {get;set;}
public object MyObject {get;set;}
}
#Html.ActionLink("Create New", "Create", Model.MyObject)
This would also work though is probably not something you would attempt.
For the second part of your question. The RouteValueDictionary searches by key and assigns the value to the key that was given.
So whatever you call the key in the anonymous object, MVC will attempt to assign the value to it on the action. The name must match or they key cannot assign the value.

Custom routes management

Is it possible to use custom routes handling code?
For example client requests server on http://server.com/api/v1/json/profile/ and my code calls ApiController, MyAction action with parameters version=1, format=json, action=profile.
Something like this? You'll have to use a different parameter name for action so you don't have a conflict with the controller action.
.MapRoute("name", "api/v{version}/{format}/{_action}", new { controller = "ApiController", action = "MyAction" });
EDIT made version work the way you wanted.
I would start off by renaming the "action" parameter to something else otherwise the route is going to get very confusing (maybe call it purpose?). Also, I believe something like the following would work:
routes.MapRoute(
// name of your route
"MyRoute",
// route template
"api/v{version}/{format}/{purpose}",
// default route values
new {
controller = "ApiController",
action = "MyAction",
},
// constraints placed on parameters (make sure they are valid)
new {
version = #"^\d+$", // number only (can also include decimals)
format = #"^(json|text|xml)$", // if you want filtering...
}
);
Then:
public ApiController : Controller
{
public ActionResult MyAction(Int32 version, String format, String purpose)
{
throw new NotImplementedException();
}
}

How can I set up a simple route with areas in ASP.NET MVC3?

I want to use Areas so I set up the following:
public class ContentAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Content";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Content_default",
"Content/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
}
What I would like is for a person who enters the following URL to be directed to a controller inside my Content area.
www.stackoverflow.com/Content/0B020D/test-data
I would like a person entering any URL with "/Content/" followed by six characters to be sent to:
- Page action in a controller named ItemController
- Six characters passed as the parameter id
- Optional text after that (test-data in this case) to be put into parameter title
How can I do this? I am not very familiar with setting up routes when using areas.
the six digits to be put into a variable called ID
So you're looking for something like
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Content_default",
"Content/{id}/{optional}",
new { controller = "ItemController", action = "TheActionYouWantThisToAllRouteTo" }
}
This would default everything to one controller and action method (which you have to specify in your instance). You can then get the data like so:
public ActionResult TheActionYouWantThisToAllRouteTo (string id, string optional)
{
// Do what you need to do
}
The way the routes are setup, you can name the pieces of information you want in a URL by wrapping it in a pair of { } curly braces. If you'd rather the name of optional to be isTestData then you would just change the route to read "Content/{id}/{isTestData}".
Note: Since you didn't specify the default action method you want this to route to, I substituted it with TheActionYouWantThisToAllRouteTo. Change that string to read the action method you want this to all go to. This also means you can't have a "regular" controller named ContentController, either.
Edit
Stephen Walther has a good blog post on custom route constraints. It can be found here. It should be a good start to get done what you need.

Handle action with invalid Id parameter

I am new to ASP.NET MVC and I wonder if the way I handled these cases is the most appropriate.
I have an "ArticleController", which has an action called "Details" (Used the auto-generate edit template).
By default, there is an optional id at the routing table,
and I want to know how to handle the cases when I don't receive any Id or when I receive a wrong id parameter.
In order to fix it I've wrote this (Note the DefaultValue attribute):
public ViewResult Details([DefaultValue(0)]int id)
{
Article article = db.Articles.Find(id);
if (article == null)
{
return View();
}
return View(article);
}
And at the view I've wrote this:
#if (Model == null)
{
<div>Wrong article id was given.</div>
}
else
{
// Handle as a normal case
}
You would have handled these cases differently? If yes, how?
I think the cleanest approach is to set up your routes so that when no ID is present, a user is routed to a different action. That's what the default route does. For example: /Articles/ will invoke ArticleController::Index(), and /Articles/4 will invoke ArticleController::Details(4).
As far as the case goes where an ID is not found, personally, I prefer to return a 404 error:
return new HttpNotFoundResult("This doesn't exist");
You can make your Id nullable like this:
public ViewResult Details(int? id)
If the user provides no id or an incorrect one, the id won't have a value which you can check with id.HasValue. If the id has a value, you can obtain it with id.Value.

ASP.NET MVC: action methods with one param not named ID and non-integer

Consider an ASP.NET MVC 1.0 project using the Areas convention as described on this Nov. 2008 Phil Haack blog post. This solution works great once it's set up!
My trouble is starting thanks to my limited knowledge of ASP.NET MVC's routing rules.
My intention is to create an action method and URL structure like this:
http://mysite/Animals/Dogs/ViewDog/Buster
DogsController.ViewDog() looks like this:
public ActionResult ViewDog(string dogName)
{
if (dogName!= null)
{
var someDog = new DogFormViewModel(dogName); //snip a bunch more
return View(someDog);
}
else { return View("DogNotFound"); }
}
The task at hand is ensuring that the RegisterRoutes() has the correct entries.
UPDATE
Here's the new route being mapped:
routes.MapRoute("ViewDog", "Animals/{controller}/{action}/{dogName}",
new { controller = "Dogs",
action = "ViewDog", dogName = "" });
The link to the URL is created:
<%= Html.RouteLink("Brown Buster", "ViewDog", new RouteValueDictionary(new { controller="Dogs", action="ViewDog", dogName="Buster" }))%>
The URL is created as expected. Thanks to Craig Stuntz and his blog post on Html.RouteLink.
http://mySite/Animals/Dogs/ViewDog/Buster
New Problem: The param dogName doesn't pickup the string value "Buster" from the URL. The call to the method succeeds, but the argument evaluates to null.
Questions
How can you:
make this route work with a string, and remove the default convention int id in the route? I'd like to change the name of the parameter away from int.
Are you sure that ActionLink is actually matching the route you show them the question? When you have more than one route, I strongly recommend using RouteLink instead of ActionLink, as I explain in great detail in this post. When you use RouteLink, there is no possibility that you will match the wrong route, at least in URL generation.
The default parameter "id" doesn't have to be an int. It'll match whatever type you declare in your action method. Why not just do the following?
public ActionResult ViewDog(string id)
{
if (id!= null)
{
var someDog = new DogFormViewModel(id); //snip a bunch more
return View(someDog);
}
else { return View("DogNotFound"); }
}

Resources