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.
Related
I can't figure out why I need to create an empty method signature to allow a Get rest call with 3 null parameters to work. I have the following Code:
public class SessionPresenterController : ApiController
{
public HttpResponseMessage Get()
{
return Get(null, null, null);
}
public HttpResponseMessage Get(int? codeCampYearId, int? sessionId, int? attendeesId)
{
and in my WebApiConfig I have
config.Routes.MapHttpRoute
("API Default Rest", "rest/{controller}/{id}",
new { id = RouteParameter.Optional });
config.Routes.MapHttpRoute
("API Default RPC", "rpc/{controller}/{action}/{id}",
new { id = RouteParameter.Optional });
If I call /rest/SessionPresenter without any parameters and I Don't have the Get() defined, the Get with the three null parameters is not found.
Why not?
Try specifying defaults for your parameters:
public HttpResponseMessage Get(
int? codeCampYearId = null,
int? sessionId = null,
int? attendeesId = null)
{
//...
}
Jacobs' snippet will solve your issue. Let's answert the question why not? why get is not found..
There is a complete documentation of the Routing and Action Selection. Let's use some extratc and reveal what happens:
Action Selection
Create a list of all actions on the controller that match the HTTP request method.
If the route dictionary has an "action" entry, remove actions whose name does not match this value.
Try to match action parameters to the URI, as follows:
For each action, get a list of the parameters that are a simple type, where the binding gets the parameter from the URI. Exclude optional parameters.
From this list, try to find a match for each parameter name, either in the route dictionary or in the URI query string. Matches are case insensitive and do not depend on the parameter order.
Select an action where every parameter in the list has a match in the URI.
If more that one action meets these criteria, pick the one with the most parameter matches.
Other words, if there will be only Get with three parameters (omited Get()), to decided which action should be selected:
Selected was Get(int? codeCampYearId, int? sessionId, int? attendeesId)
still the Get(int? codeCampYearId, int? sessionId, int? attendeesId) is selected
URL is /rest/SessionPresenter
no optional parameters excluded. All have to be found
URL does not have a match for all three parameters
So to solve it, we have to either pass all params (empty, null):
/rest/SessionPresenter?codeCampYearId&attendeesId&sessionid
Or change the signature to have parameters optinal (Jacobs' answer), and skipped during the action selection
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.
I am very new to WebApi and don't understand parameters mapping.
I had a controller with HttpGet method with 2 parameters. In WebApiConfig mapping defined like
config.Routes.MapHttpRoute(
name: "MyActionApi",
routeTemplate: "api/{controller}/{action}/{p},{q}");
which seemed to work fine.
By analogy I've added another controller (DetailsController) that has 3 parameter HttpGet method.
I've added
config.Routes.MapHttpRoute(
name: "MyActionApi2",
routeTemplate: "api/{controller}/{action}/{p},{q},{r}");
But navigating to
http://mysite/api/Details/CrossReport/12,14,Peter
gives 404 error and says
No action was found on the controller 'Details' that matches the request.
But navigating like this
http://mysite/api/Details/FilterByDate/12,14?q=10
gives correct results.
Why is that? I'd like to have it comma separated as in the first case. And why it works in first case but not the second one?
Working controller's method:
public IEnumerable<Order> FilterByDate(DateTime dateStart, DateTime dateEnd).
Not working:
public IEnumerable<Detail> FilterByDate(DateTime dateStart, DateTime dateEnd, int maxCount)
Both have HttpGet attribute.
You need to define a matching action. Try adding an action with the following signature on your DetailsController class:
[HttpGet]
public IEnumerable<Detail> CrossReport(string p, string q, string r)
As you see, the action name and parameter names must match what you have on your route.
One thought, have you made sure that MyActionApi2 ahead of MyActionApi in you routing config? If I am not mistaken, It looks for the first possible match... and so MyActionApi would match (even if there are 3 parameters)
There are two types of parameters in WebApi: parameters in routes and parameters in body/url.
Parameter in route
In this example the id param is in the route.
http://mysite/api/Details/CrossReport/{id}
The route params are separate by "/" and there are part of the route.
http://mysite/api/Details/CrossReport/{id}/{name}/{detailId}
In your web api controller you must be:
public IEnumerable<Order> FilterByDate(int id, string name, int detailId)
Parameter in body/url
The parameters in url are separate by & and all this params are after a ? in route. For example.
http://mysite/api/Details/CrossReport?id=3&name="john"&detailId=5
And in your web api controller is the same:
public IEnumerable<Order> FilterByDate(int id, string name, int detailId)
If the object is compound by others properties:
public class MyObject
{
public int Id { get; set; }
public string Name { get; set; }
}
You can only have one in the body of the message and you have to send in the body on the message and your web api controller receipt them:
public IEnumerable<Order> FilterByDate(MyObject obj)
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
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){}