Route variables received as action variables? - c#

I've got the following route configuration:
routes.MapRoute(
name: "Default",
url: "{project}/{version}/{controller}/{action}",
defaults: new { controller = "Portal", action = "Index" }
);
The idea is to have two custom parameters in the URL, before anything else so an action filter I created can do a few useful things. That works well so far, but here's the problem.
Here's an action method with a parameter called version
public ActionResult SomeMethod(string version)
{
//Some logic has been performed using the custom route variable values before getting in here....
}
If I do the following call: http:/server/RouteValue1/RouteValue2/MyController/SomeMethod?version=1
The value of the version parameter of SomeMethod is RouteValue2.
Is there any way to tell the framework not to do that? Keep route variables seperate from action variables ?

No, you can't use the same parameter name in both the route config and as a query string. You'll need to rename one, i.e.:
http:/server/RouteValue1/RouteValue2/MyController/SomeMethod?ver=1

Related

How to route to url.com/ControllerA/ControllerB/ControllerB's action in MVC

So I have a ProjectsController with the default route url.com/projects/action and I have a Controller for each Project like MosaController. The URL for the project should be url.com/projects/{ProjectName}/action, so in the Mosa example url.com/projects/Mosa/action.
I have set up a route in the RouteConfig that can solve this problem
routes.MapRoute(
name: "ProjectViewRoute",
url: "projects/{controller}/{action}",
defaults: new { controller = "Projects", action = "Index" }
);
This works I can call url.com/projects/action and url.com/projects/Mosa/action and the correct controller is selected, but when I call the URL url.com/{ProjectName}/action it invokes the action, because of the default route. Is there a way to ignore the default route? Or is a there better way of concatenating controllers like this?
Thanks!
Do you need the default route for other portions of your app? If not, why not simply remove it and just have the ProjectViewRoute? Or consider creating "Projects" as an MVC "Area" (https://www.c-sharpcorner.com/article/getting-started-with-area-in-asp-net-mvc/). This would eliminate the project-specific route outside of the Projects area.

MVC MapRoute with parameter ticket-id

I need to create a route with an url like: "{controller}/{action}/{ticket-id}"
routes.MapRoute(
name: "Login",
url: "Auth/Login/{ticket-id}",
defaults: new { controller = "Auth", action = "Login"}
);
I can't create the route because of the parameter name "ticket-id".
No problem with "ticketid"... but I can't change the parameter name.
Do you have any solutions.
Thanks
MVC MapRoute maps the parameter name in the template to the parameter names in the action.
You say that you can't change the parameter name. Problem is that in c# you would not even be allowed to use that parameter name.
c# does not allow you to have hyphens - for parameter/variable names.
This would not be allowed as names must be valid identifiers.
public ActionResult Login(string ticket-id ) {...}

ASP.NET Web Api 2 - Attribute Based Routing - How do I force a parameter to query string only?

I am working on a Web Api 2 project and I am using attribute based routing. Here is a sample route:
[Route("{id:int}", Name = "GetEmployeeById")]
[HttpGet]
public IHttpActionResult GetEmployee(int id)
{
...
}
This works with the following URLs:
://host/employee/12345
://host/employee?id=12345
What I would prefer is that the first form (the parameter in the URI), would not be allowed, and only the second form (query string) would work.
What I've Tried
Mostly, I've tried searching the web for a way to make this work, and I'm not finding much.
This page talks about route constraints but this syntax doesn't seem to work (anymore?).
This page doesn't actually prevent the URI form from working.
There is an attribute called "[FromUri]" that you can use to decorate a method parameter, and the model binder will try to look for that parameter from the Querystring, it may not help you with this scenario but it is good to know about it, so in case you want to pass a search options for example to a Get method.
http://msdn.microsoft.com/en-us/library/system.web.http.fromuriattribute(v=vs.118).aspx
http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api
Hope that helps.
Couple of ways to achieve this. Here are some options
Rename parameter to something else than id (eg. employeeId).
Change the default routing configuration in WebApiConfig:
//Default configuration, you can see here the "id" parameter which enables action/id matching
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
//It should look like this
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}"
);
Also you can do it with custom attributes.
Actually, I was wrong about my original code. The query string parameter did not work with the route I specified. Instead, I could do this:
[Route("", Name = "GetEmployeeById")]
[HttpGet]
public IHttpActionResult GetEmployee(int id)
{
...
}
And this will do what I want. It must be getting the name id from the function's parameter list.
Unfortunately, this means I can't put a constraint on it anymore, but I guess I can just validate within the function.

Prevent ASP.NET Web API route engine from deducting custom method names

I have the Web API controller with 2 methods - let's say the first method returns the plain project list and the second one returns all projects assigned to the specific user.
public class ProjectController: ApiController
{
public IQueryable<Project> Get() { ... }
[HttpGet]
public IQueryable<Project> ForUser(int userId) { ... }
}
The method implementation is not important in this case.
Web API route config is also adjusted to support the custom method names.
config.Routes.MapHttpRoute(
"DefaultApi",
"api/v1/{controller}/{id}",
new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
"DefaultApiWithAction",
"api/v1/{controller}/{action}");
It works fine, I can access both /api/v1/projects/ and /api/v1/projects/forUser/ endpoints, but seems that the route engine is too smart, so it decides that /api/v1/projects?userId=1 request may match the ForUser(..) method (due to the userId argument name, I guess) and ignores the {action} part of the route.
Is there any way to avoid this behavior and require the action part to be explicitly specified in the URL?
Couple things. First of all this route:
config.Routes.MapHttpRoute(
"DefaultApiWithAction",
"api/v1/{controller}/{action}",
new { id = RouteParameter.Optional });
Does not have "action" as an optional parameter. You have included id as optional (I assume as a typo), but as it does not exist in the route, you will not get a match with only one supplementary segment. Only URLs containing two parts, a controller and an action, will pass through this route. This url:
/api/v1/projects?userId=1
...contains a single segment and will not. This route, and any other which lacks a second component will default to this route:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/v1/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
...which only takes a controller and an optional ID. You need to either reformat the given URL to take an action parameter, or rewrite your route to make the action optional and set the default as you desire. This will all depend on your application architecture, but always err on the side of simplicity. Routes can get very complicated--simpler is generally better.
As for required/optional route components, keep in mind the following two things:
All route segments are required unless they are set as optional in the anonymous object.
Segments can also be excluded if they have a default value, set by providing one in the anonymous object in the form of placeholder = value.
I don't understand your problem completely.
Shouldn't /api/v1/projects?userId=1 indeed call the ForUser action?
Anyway, to make the action required, make your HttpRoute like this:
name: "DefaultApi",
routeTemplate: "api/v1/{controller}/{action}/{id}",
defaults: new { id = System.Web.Http.RouteParameter.Optional });
Now you can call like this:
/api/v1/projects/ForUser/2
I've finally come up with the solution that satisfies my requirements. I've combined this answer and ideas suggested by levib and user1797792 into the following config:
config.Routes.MapHttpRoute(
"DefaultApiWithActionAndOptionalId",
"api/v1/{controller}/{action}/{id}",
new {id = RouteParameter.Optional});
config.Routes.MapHttpRoute(
"DefaultApiGet",
"api/v1/{controller}",
new { action = "Get" },
new { httpMethod = new HttpMethodConstraint(HttpMethod.Get) });
Note that the config order matters a lot here.
First of all, the /api/v1/projects request with any query string (even with arguments whose names match the other action's parameters) is dispatched to the Get() method via the second route. This is important because in the real project I've got a custom action filter attached to this action that filters the returned IQueryable based on the provided request arguments.
api/v1/projects/forUser/1-like requests are dispatched to ForUser(int id) method by the first route. Renaming userId parameter into id allowed to construct cleaner URLs.
Obviously, this approach has some limitations, but it is all I need in my specific case.

How to define this route in ASP.NET MVC?

I have a controller named Movie, with an action named ByYear, which takes the year as a parameter :
public ActionResult ByYear(int year)
{
ViewData["Title"] = string.Format("Movies released in {0}", year);
var repository = MvcApplication.GetRepository();
var movies = repository.Medias
.OfType<Movie>()
.Where(m => m.Year == year);
return View("Index", movies);
}
I'd like to access this action with the following URL : /Movie/ByYear/{year}, but the only valid route for this action is this : /Movie/ByYear?year={year}.
I tried to add new routes in my application's RegisterRoutes method, but I can't find a way to get the desired result...
Could anyone tell me how to achieve that ?
Note: this is actually very similar to this question, but no answer was accepted, and the highest voted answer makes no sense to me as I'm completely new to MVC...
Change the name of your parameter year to id and this will match the default route that MVC adds to your project.
So for further clarification, let's take a look at the default route added by ASP.NET MVC:
routes.MapRoute(
"default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" }
);
In this route you can see three tokens that are named specifically for controller, action, and the third token which is passed to the action is id. When a request comes into your application, ASP.NET MVC will analyze the routes that are currently mapped and try to find a method signature that matches them by using reflection against your controllers.
When it looks at your Movie controller, it sees an action called ByYear, however that method takes an integer called year, not id. This is why you end up with something like /Movie/ByYear?year={year} when you create an ActionLink for that particular Action. So to fix this, you have two options:
The first and most simple method to fix this is to just change the method signature for your Action to accept a parameter named id which is what I recommended above. This will work fine, but I can see where it might cause a little bit of confusion when you go back to that source later and wonder why you called that parameter id.
The second method is to add another route that matches that method signature. To do this, you should open your Global.asax and just add the following (untested, but should work):
routes.MapRoute(
"MoviesByYear",
"Movies/ByYear/{year}",
new { controller = "Movie", action = "ByYear" }
);
This route is hard-coded, yes, but it won't break the other routes in your system, and it will allow you to call the method parameter year.
EDIT 2: Another thing to note is that the routing engine will stop on the first route it finds that matches your request, so any custom routes like this should be added before the default route so you are sure they will be found.
OK, I just found out how to do it. I just had to create the new route before the default route... I didn't think the order had any significance
routes.MapRoute(
"MovieByYear", // Route name
"Movie/ByYear/{year}", // URL with parameters
new { controller = "Movie", action = "ByYear" } // Parameter defaults
);
EDIT: Isn't there a simpler way ? (not involving renaming the parameters). I'd like to be able to do something like that :
[Route("/Movie/ByYear/{year}")]
public ActionResult ByYear(int year)
{
...
Design considerations aside, if you did not want to rename the parameter, you could add something like the route below, which enforces having the year parameter
routes.MapRoute(
"MovieByYear", // Route name
"Movie/ByYear/{year}", // URL with parameters
new { controller = "Movie", action = "ByYear" },
new { year = #"\d+" } // Parameter defaults
);

Categories