So I have the basic Hello World service running in DNN 6.2 Services Framework, which is based on MVC. The route, taken from example, is:
routeManager.MapRoute("MyServices", "{controller}/{action}", new[] { "HelloWorldServices" });
When my action go to ~/DesktopModules/MyServices/API/Welcome/HelloWorld, it calls the HelloWorld function and works. So far, I've managed to figure out that I can do this:
routeManager.MapRoute("MyServices", "{controller}/{action}/{userID}", new[] { "HelloWorldServices" });
When I go to ~/DesktopModules/MyServices/API/Welcome/users, it calls the users function, which has an optional parameter for the ID, and that is working. But now I want to make an endpoint ~/DesktopModules/MyServices/API/Welcome/locations, which could take a location ID. I can't just write this:
routeManager.MapRoute("MyServices", "{controller}/{action}/{locationID}", new[] { "HelloWorldServices" });
or even
routeManager.MapRoute("MyServices", "{controller}/locations/{locationID}", new[] { "HelloWorldServices" });
It doesn't work that way. Whichever is first (users or locations) recognizes the ID and whichever is second does not. While I can figure out what does not work, I have no idea what will work, and have not been able to find out despite a lot of searching. I also found some examples that look like this:
routeManager.MapRoute("Html", "default", "", new { controller = "Service", action = "GetTabs" }, new[] { "DotNetNuke.Modules.Html" });
But I can't figure out how that works, either. Eventually I'll want to do "~/DesktopModules/MyServices/API/Welcome/users/userid/locations/" as well. If anyone could give me some examples, that would be much appreciated!
UPDATE: Based on the answer I looked up MVC routing. I realized I had to write it generically, and now have these working:
{controller}/{action}
{controller}/{action}/{id}
{controller}/{action}/{id}/{secondaction}
{controller}/{action}/{id}/{secondaction}/{secondid}
"id", "secondaction", and "secondid" are all optional string parameters. So I can have "users/userid/locations/locationid"... the "locations/locationid" part is handled in the "users" function.
My general recommendation here would be to use a route map that is something like
{controller}/{action}/{id}
This is a typical default ASP.NET MVC route style. Then you could have a Users Method that as an optional id parameter, and a locations method with an optional id parameter, etc.
This will give you a truly dynamic route, and is a LOT easier to manage
Related
I have an actionresult with two parameter:
public ActionResult Index(int a,string b)
{
//some code
return View(b);
}
it creates this url automatically:
mysite.com/a=1&b=http://site.com/b=1
I just need to show first parameter "a" in my url:
mysite.com/a=1
I use the default route of MVC that creates in global.ascx:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
what should i do?
Thanks...
If you are seeing the "b" parameter bleed through from the current request, you can set the "b" parameter explicitly to empty string to avoid this behavior:
#Html.ActionLink("Home", "Index", "Home", new { a = 1, b = "" }, null)
I reported this "feature" as a bug, but the developers at Microsoft seem to think this behavior is supposed to make your URLs easier to configure, and they don't intend to fix it.
What you're seeing here is a feature of routing where "ambient" values (i.e. values that were detected in the incoming request's URL) are used to simplify (sometimes...) the generation of routes to other pages.
You can check our my answer (under the name "Eilon") in this StackOverflow post, where I explain the behavior in a bit more detail:
How Can I Stop ASP.Net MVC Html.ActionLink From Using Existing Route Values?
Ultimately if you want the most control over what gets generated for a URL there are a few options to consider:
Use named routes to ensure that only the route you want will get used to generate the URL (this is often a good practice, though it won't help in this particular scenario)
Specify all route parameters explicitly - even the values that you want to be empty. That is one way to solve this particular problem.
Instead of using Routing to generate the URLs, you can use Razor's ~/ syntax or call Url.Content("~/someurl") to ensure that no extra (or unexpected) processing will happen to the URL you're trying to generate.
Thanks,
Eilon
I wanted to get some expert suggestions on designing urls for our web app. This is not a public domain website, it is a supplychain intranet based web-app used only by a group of authenticated users.
Here're some examples -
/Claim/12/Manage
FORMAT: controller/{ID}/action
The url that points to a "Claim Entry" wizard. Here "12" is the ClaimID. It is further divided into tabs for sub-data entry.
Example: /Claim/12/Print, /Claim/12/FileDetails, ...
/Users/List
FORMAT: controller/action
Display's a list of existing users in Grid. Shud this be shortened to "/Users" ? Likewise we've some other entities as well like "Roles, Organizations, etc..."
/Master/Manage/FileType
FORMAT: controller/action/{argument}
We've a page which allows he user to manage different master table data. Need to know which master table is selected (i.e. sent as argument). Is it better to simplify it as "/Manage/{argument}" instead and then map that url as required above?
Is it sensible in MVC to hide default actions like "Claim/21/Manage" shud be "Claim/21", "/Users/List" shud be "/Users" ...
Arguments - are they better as embedded in url or good to append as query-string
Any generic guidelines or references would also be great.
Ref: Web services url - (Section: Designing the URI Templates)
http://msdn.microsoft.com/en-us/library/dd203052.aspx
You can use Regular Expressions to represent various routes you have. For example
protected void Application_Start()
{
RouteTable
.Routes
.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL template
new { controller="Mycontroller", action="Myaction", id=UrlParameter.Optional },
new { action = #"\d{2}-\d{2}-\d{4}" }
);
}
Well, I conclude that this one is the best (and probably the most detailed) explanation I can find on MSDN - http://msdn.microsoft.com/en-us/library/dd203052.aspx
And that shud be sufficient :-)
I created a new ASP.NET MVC project and implemented a site authorization filter.
When I map the routes to the {controller}/{action} pair, I pass a role = "SomeRole" default to the route.
It works perfectly if I go through the full url (http://localhost/somecontroller/someaction) and I specified the full route
MapRoute("SomeAction", "somecontroller/someaction",
new { controller = "SomeController", action = "SomeAction", role = "SomeRole");
The problem is that when somebody visits http://thesiteaddress.com there has to be a default route that invokes /home/index instead of / and if I specify
MapRoute("Default", new { controller="somecontroller",action="action" });
then I lose the role="SomeRole" from the previous MapRoute.
How can I solve this?
Make sure the Default route is at the BOTTOM of your listed route table. Order matters when it comes to ASP.NET MVC Routing tables.
The correct ordering is your 'most specific' route to your least specific route.
Actually, George is right. MVC Routing respect ordering route. Your last route must be generic as possible, and your previous route must be specific as possible.
In your case, both are generic. You should
MapRoute("SomeAction", "Post/{action}", new {controller = "Post", role = "User");
and then
MapRoute("Default", new {controller="Home", action="Index", role = "Anonymous"});
so, you give specificity to both routes.
Phil Haack released a route debugging tool that can be invaluable in gaining an understanding of problems like this.
With this tool you can view how your MVC application parses a URL and matches it to your RouteTable.
When you don't provide the route name or the action is determined through a HTTP request it will look in order from the order they were added. The first time it finds one that matches, it stops. So what's probably happening is it's matching one previous to the one you've added.
What's the best way to handle a visitor constructing their own URL and replacing what we expect to be an ID with anything they like?
For example:
ASP.Net MVC - handling bad URL parameters
But the user could just as easily replace the URL with:
https://stackoverflow.com/questions/foo
I've thought of making every Controller Function parameter a String, and using Integer.TryParse() on them - if that passes then I have an ID and can continue, otherwise I can redirect the user to an Unknown / not-found or index View.
Stack Overflow handles it nicely, and I'd like to too - how do you do it, or what would you suggest?
Here's an example of a route like yours, with a constraint on the number:
routes.MapRoute(
"Question",
"questions/{questionID}",
new { controller = "StackOverflow", action = "Question" },
new { questionID = #"\d+" } //Regex constraint specifying that it must be a number.
);
Here we set the questionID to have at least one number. This will also block out any urls containing anything but an integer, and also prevents the need for a nullable int.
Note: This does not take into account numbers that larger than the range of Int32 (-2147483647 - +2147483647). I leave this as an exercise to the user to resolve. :)
If the user enters the url "questions/foo", they will not hit the Question action, and fall through it, because it fails the parameter constraint. You can handle it further down in a catchall/default route if you want:
routes.MapRoute(
"Catchall",
"{*catchall}", // This is a wildcard routes
new { controller = "Home", action = "Lost" }
);
This will send the user to the Lost action in the Home controller. More information on the wildcard can be found here.
NB: The Catchall should reside as the LAST route. Placing it further up the chain will mean that this will handle all others below it, given the lazy nature of routes in ASP.NET MVC.
Here is some useful infromation that might help.
If you have a action method
public ActionResult Edit(int? id)
{}
then if someone types in
/Home/Edit/23
the parameter id will be 23.
however if someone types in
/Home/Edit/Junk
then id will be null which is pretty cool. I thought it would throw a cast error or something. It means that if id is not a null value then it is a valid integer and can be passed to your services etc. for db interaction.
Hope this provides you with some info that I have found whilst testing.
In ASP.NET MVC, you can define a filter implementing IActionFilter interface. You will be able to decorate your action with this attribute so that it will be executed on, before or after your action.
In your case, you will define it to be executed "before" your action. So that, you will be able to cancel it if there is an error in the passed parameters. The key benefit here that you only write the code which checking the passed paramaters once (i.e you define it in your filter) and use it wherever you want in your controller actions.
Read more about MVC filters here: http://haacked.com/archive/2008/08/14/aspnetmvc-filters.aspx
You can specify constraints as regular expressions or define custom constraints. Have a look at this blog post for more information:
http://weblogs.asp.net/stephenwalther/archive/2008/08/06/asp-net-mvc-tip-30-create-custom-route-constraints.aspx
You will still need to deal with the situation where id 43243 doesn't map to anything which could be dealt with as an IActionFilter or in your controller directly.
The problem with that approach is that they still might pass an integer which doesn't map to a page. Just return a 404 if they do that, just as you would with "foo". It's not something to worry about unless you have clear security implications.
Up until now I've been able to get away with using the default routing that came with ASP.NET MVC. Unfortunately, now that I'm branching out into more complex routes, I'm struggling to wrap my head around how to get this to work.
A simple example I'm trying to get is to have the path /User/{UserID}/Items to map to the User controller's Items function. Can anyone tell me what I'm doing wrong with my routing here?
routes.MapRoute("UserItems", "User/{UserID}/Items",
new {controller = "User", action = "Items"});
And on my aspx page
Html.ActionLink("Items", "UserItems", new { UserID = 1 })
Going by the MVC Preview 4 code I have in front of me the overload for Html.ActionLink() you are using is this one:
public string ActionLink(string linkText, string actionName, object values);
Note how the second parameter is the actionName not the routeName.
As such, try:
Html.ActionLink("Items", "Items", new { UserID = 1 })
Alternatively, try:
Items
Can you post more information? What URL is the aspx page generating in the link? It could be because of the order of your routes definition. I think you need your route to be declared before the default route.
Firstly start with looking at what URL it generates and checking it with Phil Haack's route debug library. It will clear lots of things up.
If you're having a bunch of routes you might want to consider naming your routes and using named routing. It will make your intent more clear when you re-visit your code and it can potentially improve parsing speed.
Furthermore (and this is purely a personal opinion) I like to generate my links somewhere at the start of the page in strings and then put those strings in my HTML. It's a tiny overhead but makes the code much more readable in my opinion. Furthermore if you have or repeated links, you have to generate them only once.
I prefer to put
<% string action = Url.RouteUrl("NamedRoute", new
{ controller="User",
action="Items",
UserID=1});%>
and later on write
link
Html.ActionLink("Items", "User", new { UserID = 1 })