Structuring MVC Routes - asp.net-mvc-3

I have the following url structure, and just trying to figure out the best Routes to configure.
EDIT: Added more url's
/cars/{name} (shows general information about a car)
/cars/{name}/models (shows a list of models for a particular car)
/cars/{name}/models/{id} (shows a specific model for a particular car)
/cars/{name}/models/edit (add a new model which would be an action)
/cars/{name}/models/{id}/owners (a list of owners for a particular model)
/cars/{name}/models/{id}/owners/create (add a new owner)
So far, I have
routes.MapRoute(
name: "CarReleases",
url: "cars/{name}/models/{id}",
defaults:
new
{
controller = "Releases",
action = "Index",
id = UrlParameter.Optional
}
);
This works if I use /cars/{name}/models, but obviously, I don't have the action available for the models page. Do I have to create a new route map for this situation?
I also have the CarController, which is mapped as follows:
routes.MapRoute(
name: "Cars",
url: "cars/{name}/{action}",
defaults: new { controller = "Cars", action = "Details", id = UrlParameter.Optional }
);
As you can see, I have a general mixture of actions and dynamic requests. Bit confused the best way to put this into maproutes.

Order your routes from the most specific to the least specific. In my example all actions are mapped to the controller Cars; however, you may separate them. For example:
Owners:
routes.MapRoute(
name: "owners",
url: "cars/{name}/models/{id}/owners/{action}/{ownerId} ",
defaults: new { controller = "Cars", action = "OwnerIndex", id = UrlParameter.Optional, ownerId = UrlParameter.Optional }
);
Models:
routes.MapRoute(
name: "models",
url: "cars/{name}/models/{action}/{id}",
defaults: new { controller = "Cars", action = "ModelIndex", id = UrlParameter.Optional }
);
Cars:
routes.MapRoute(
name: "Cars",
url: "/cars/{name}/{action}/{id}",
defaults: new { controller = "Cars", action = "Index", id = UrlParameter.Optional }
);
Notice the default action was changed to Index so it lists all when you omit the action and Id (you may need to change it if you decide to keep them all in one controller)
Regarding your question whether you should keep them in one single controller, I think that's fine unless you would like to separate admin functions (edit, delete, etc) from viewing. In any case you can still have them in one controller and just add the Authorize attribute.
[Authorize(Roles = "admin")]
public ViewResult Delete(int id){}

Related

Asp.Net MVC 3 variable in url

The default MVC 3 route config is
{controller}/{action}/{id}
My NEWS application structure is like
/News/Latest10
/News/Critical/10June2013
/Entertainment/Latest10
Bold ones being controller, italics as actions, and normal text are optional params.
Now I want to add new variable, language, into the url structure.
It should be like
/en/News/Latest10
/ja/News/Critical/10June2013
/de/Entertainment/Latest10
I would like to know how to access this language variable in the controller. Is it possible?
Thanks
To meet your needs change the Route config to:
routes.MapRoute(
name: "Language",
url: "{language}/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional, language="en" },
constraints: new {language=new LanguageConstraint()}
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
The two key parts are the route itself, {language}/{controller}/{action}/{id} and the constraint part, new {language=new LanguageConstraint()}.
The first part will select the {language} part as a variable (default being en for now) to the controller. The controller signature:
public ActionResult Index(string language) {
will pick up the new language variable. Since adding language to each and every controller could seem cumbersome you could create a ViewModelBase class to passed to every controller with a property that contains the language value, which every subsequent View Model class inherits from.
Without a constraint the route pattern would pick up all values in the url for the language part and writing a Regex expression to match all wanted language values would be tedious, I think it's easier to write an IRouteConstraint based class similar to the following:
public class LanguageConstraint : IRouteConstraint{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values,
RouteDirection routeDirection) {
//create accepted lanaguages collection from somewhere.
string[] languageArray = new[]{"en","jp", "de"};
string language = values["language"].ToString();
if (string.IsNullOrEmpty(language))
return false;
return languageArray.FirstOrDefault(l=>l.Equals(language,StringComparison.InvariantCultureIgnoreCase)) != null;
}
}
Simply it creates a list of known language values and check the provided language value against that list. If it doesn't exist false is returned and a 404 is thrown.

MVC 3 exception: The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.Web.Mvc

My application seems to run fine, but I keep getting these exceptions in log4net logs:
The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ActionResult Agency(Int32)' in 'COPSGMIS.Controllers.QuestionController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
Not sure whats going wrong?
My controller:
public ActionResult Agency(int id)
{
QuestionDAL qd = new QuestionDAL();
var agency = qd.GetAgencyDetails(id);
agency.Reviews = qd.GetAgencyReviews(id);
return View(agency);
}
My routes:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
This error is thrown if you attempt to call this controller action and you do not specify the id either in the path portion or as query string parameter. Since your controller action takes an id as parameter you should make sure that you always specify this parameter.
Make sure that when you are requesting this action you have specified a valid id in the url:
http://example.com/somecontroller/agency/123
If you are generating an anchor, make sure there's an id:
#Html.ActionLink("click me", "agency", new { id = "123" })
If you are sending an AJAX request, also make sure that the id is present in the url.
If on the other hand the parameter is optional, you could make it a nullable integer:
public ActionResult Agency(int? id)
but in this case you will have to handle the case where the parameter value is not specified.

Web API nested routing not working as expected

I am having difficulty with a nested Web API routing set-up.
Given a routing config of:
config.Routes.MapHttpRoute(
name: "UsersGroups",
routeTemplate: "api/users/{userID}/groups/{groupID}",
defaults: new { controller = "UsersGroups", groupID = UrlParameter.Optional }
);
and controller actions like thus:
public AuthGroup Get(long userID, int groupID)
{
//Get specific group here
}
public IEnumerable<AuthGroup> Get(long userID)
{
//get all groups for user here
}
Calling this route /api/users/1528/groups gives this error:
The parameters dictionary contains a null entry for parameter groupID of non-nullable type System.Int32 for method AuthGroup Get(Int64, Int32) in UsersGroupsController. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
I was expecting it to grab the action with the single long parameter, but obviously for some reason it's ignoring this and going straight for the one with most arguments.
Based on what's available at MS on how Web API interprets routes: http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-and-action-selection I think what I have should be correct, but obviously it is not working as it seems like it should.
You should use RouteParameter.Optional (from Web API), not UrlParameter.Optional (from ASP.NET MVC).
Everything will behave as you want then.
More info:
UrlParameter.Optional - http://msdn.microsoft.com/en-us/library/system.web.mvc.urlparameter.optional(v=vs.108).aspx
RouteParameter.Optional - http://msdn.microsoft.com/en-us/library/system.web.http.routeparameter.optional(v=vs.108).aspx

ASP.NET MVC3 : ActionMethod with same name and different arguments for List and Details view

I have a ProdcutsController where i have 2 Action methods. Index and Details.
Index will return list of products and Details will return details of a selected product id.
So my urls are like
sitename/Products/
will load index view to show a list of products.
sitename/Products/Details/1234
will load details view to show the details of product 1234.
Now i want to avoid the "Details" word from my second url. so that it should look like
sitename/Products/1234
I tried to rename my action method from "Details" to "Index" with a parameter in it. But it showed me the error "Method is is ambiguous"
I tried this
public ActionResult Index()
{
//code to load Listing view
}
public ActionResult Index(string? id)
{
//code to load details view
}
I am getting this error now
The type 'string' must be a non-nullable value type in order to use
it as parameter 'T' in the generic type or method 'System.Nullable<T>
Realized that it does not support method overloading ! How do i handle this ? should i update my route definition ?
Use this:
public ActionResult Index(int? id)
{
//code to load details view
}
Assuming the value is an integer type.
This is another option:
public ActionResult Index(string id)
{
//code to load details view
}
A string is a reference type so a null can already be assigned to it without needing a Nullable<T>.
You can just use one Action method.
Something like:
public ActionResult Index(int? Id)
{
if(Id.HasValue)
{
//Show Details View
}
else
{
//Show List View
}
}
You can create two routes and use route constraints:
Global.asax
routes.MapRoute(
"Details", // Route name
"{controller}/{id}", // URL with parameters
new { controller = "Products", action = "Details" }, // Parameter defaults
new { id = #"\d+" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
First route has a constraint that requires id to have one or more digits. Because of this constraint it won't catch routes like ~/home/about etc.
ProductsController
public ActionResult Index()
{
// ...
}
public ActionResult Details(int id)
{
// ...
}

MVC3 routing with an optional first parameter

My site is currently set up with the following routes:
routes.MapRoute(
"BrandList",
"Brands",
new { controller = "Brand", action = "Index" }
);
routes.MapRoute(
"BrandProducts",
"{brand}/Products",
new { controller = "Brand", action = "Products", manufacturer = "" },
new { manufacturer = new BrandConstraint() }
);
routes.MapRoute(
"Product",
"{brand}/{partNumber}",
new { controller = "Product", action = "Details", brand = "" },
new { manufacturer = new BrandConstraint() }
);
This produces URLs like
http://oursite/Brands -> List of all brands
http://oursite/SomeBrand -> List of one brand's products
http://oursite/SomeBrand/ProductA -> Details for product
I just got the directive, however, that we now need to serve up those same pages on
http://oursite/Brands -> List of all brands
http://oursite/SomeBrand -> List of one brand's products
http://oursite/Brands/SomeBrand -> List of one brand's products
http://oursite/SomeBrand/ProductA -> Details for product
http://oursite/Brands/SomeBrand/ProductA -> Details for product
I know I can create two more Routes, identical to the current BrandProducts and Product routes, with the extra "Brands/" at the beginning. I'll do that if I need to, but I'd really much prefer to not have to duplicate each route entry (there's more than just these two).
Anyone have suggestions?
You may want to just try using Url Rewrites instead of adding the complexity of additional routes pointing to the same place.
Also its not good to have multiple canonical formats of URL's that are valid, one should really 301 to the "correct" url.

Resources