inbound/outbound url routing in asp.net MVC - asp.net-mvc

The routing I need is quite simple, I must be missing something there. As code example I put the simpler situation where I can reproduce my behavior.
You have this ActionMethod :
public ActionResult Index(string provider)
{
ViewData["Message"] = provider;
return View("Index");
}
And you have this route :
routes.MapRoute(
null,
"{controller}/{action}/{provider}",
new { controller = "Home", action = "Index", provider = "Default" }
); // Parameter defaults
You can call /Home/Index/Custom and provider will take the value "Custom"
What Route would I need if I want the url /?provider=Custom to map the provider to the parameter.
I thought that would just work, because the default controller and the default action would be used, and the provider from the querystring would be used instead of the default one.
but the querystring is just ignored here.
That's a problem in my situation as I have a form using HTTP GET method.
The form action has to be Html.BeginForm(c=>c.Index(null)) which is resolved as / and the value of my form are added in the querystring. (the provider being a dropdown in the form)
So the url built by the form is /?abc=value&cde=value...
UPDATE
The accepted answer below (see the comments) led me to this solution:
routes.MapRoute(
"Search",
"search/",
new { controller = "Home", action = "Index" }
);
routes.MapRoute(
null,
"{controller}/{action}/{provider}",
new { controller = "Home", action = "Index", provider = "Default"}
);
And declare the form like so :
Html.BeginRouteForm("Search", FormMethod.Get){
...
}
This way, the form will work with the provider in the QueryString (when I use the named route search) but in all other case, I will use the default route. :)

When I set the provider to urlparameter.optional instead of a static value, I get the behavior that you are looking for. I don't think I can explain fully why this works whereas having a static default value set does not, but give it a try and see if it helps. If it works, you may also want to develop a custom route for your form so you can maintain your default provider in your routes as opposed to doing custom checking in your controllers.
routes.MapRoute( _
"Default", _
"{controller}/{action}/{provider}", _
New With {.controller = "Home", .action = "Index", .provider = UrlParameter.Optional} _
)
UPDATE:
Also, you do not have to have the parameters in your route to pass them to a controller action method. For instance, using the route above, I can have this URL
http://localhost:49705/home/about/default?otherValue=testme
And this controller method
Function About(ByVal provider As String, ByVal otherValue As String) As ActionResult
ViewData("Message") = provider & "|" & otherValue
Return View()
End Function
Which outputs the string default|testme
This URL does the same as above: http://localhost:49705/home/about/?provider=default&otherValue=testme

Maybe I'm not understanding the question, but if you just remove the {provider} from your route, or use the default {id} instead. Then when you set the URL to /?provider=blah, "blah" is assigned to the "provider" parameter.

Related

ASP.NET MVC 3 Routes Always Have Querystring Value "Area="

This is an annoyance that I've experienced for a long time, but now my client is asking me to address it.
In every route that gets generated (by a non-Default route), a query string value gets appended: "Area="
As an example:
// RouteConfig.Register():
routes.MapRoute(
"ProfileDetails",
"{slug}",
new { controller = "Profile", action = "Details" }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
To generate a URL to the BadgeController.Index action, the Default route will be applied and the result will be /Badge... and that's what is expected.
But to generate a URL to the ProfileController.Details(someUser) action, the ProfileDetails route will be applied and the result will be /someUser?Area= ... which will work, but the ?Area= is unnecessary and messy.
I have no areas in my project. How do I get rid of that Area= query string value? This happens with all of my routes that are not the predefined Default route, not just the "ProfileDetails" one in this example.
I've tried removing the AreaRegistration.RegisterAllAreas() from my Global.asax file, since I assume it's not required.

How do I do Short URLs in MVC?

Suppose I want to publish (like in paper catalogs) some "short URLs" that are easy to type/remember, but I want them to redirect to a verbose, SEO-friendly URL. How do I accomplish that with MVC routes?
Example:
http://mysite.com/disney
becomes
http://mysite.com/travel/planning-your-disney-vacation (with "travel" as the Controller)
The things I've tried:
Just setup a route for it. Problem: the URL doesn't change in the browser (it stays "/disney".
Use NuGet package RouteMagic (see Haacked's article). Problem: I get an error: The RouteData must contain an item named 'controller' with a non-empty string value. I think this is because I don't have a static word before my controller ("travel") like he did (with "foo" and "bar")???
Use a redirect module (like Ian Mercer's). Problem: the route matches on my HTML.ActionLinks when creating URLs which I don't want (Haacked mentions this in his article and says that's why he has GetVirtualPath return NULL ...?)
I'm out of ideas, so any would be appreciated!
Thanks!
You could set up a catch-all type route, to direct all /something requests to a specific action and controller, something like:
routes.MapRoute(
"ShortUrls",
"{name}",
new {controller = "ShortUrl", action = "Index", name = UrlParameter.Optional}
);
(depending on how the rest of your routing is set up, you probably don't want to do it exactly like this as it will likely cause you some serious routing headaches - but this works here for the sake of simplicity)
Then just have your action redirect to the desired URL, based on the specified value:
public class ShortUrlController : Controller
{
//
// GET: /ShortUrl/
public ActionResult Index(string name)
{
var urls = new Dictionary<string, string>();
urls.Add("disney", "http://mysite.com/travel/planning-your-disney-vacation");
urls.Add("scuba", "http://mysite.com/travel/planning-your-scuba-vacation");
return Redirect(urls[name]);
}
}
I just faced the same problem.
In my Global:
routes.MapRoute(
"ShortUrls",
"{name}",
new { controller = "Home", action = "Index", name = UrlParameter.Optional }
);
In my Home Controller:
public ActionResult Index(string name)
{
return View(name);
}
This way is dynamic, didn't want to have to recompile every time I needed to add a new page.
To shorten a URL you should use URL rewriting technique.
Some tutorials on subject:
url-rewriting-with-urlrewriternet
url-routing-with-asp-net-4
URL rewriting in .Net

How to modify MVC routing to get more than one first-class urls

I have default routing set for my mvc application like:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
I have Home controller with Index() and About(). Tell me please how to modify routing to get both domain.com/Index and domain.com/About urls?
Thank you
Add this before your default route. By adding it before the default route, if it matches it will be used to set the RouteDictionary values. Untested, but it should map urls that only have a single component that is either index or about. Note, that this assumes you don't have an index or about controller. The routing constraint is important as it keeps it from matching on each controller's index action, e.g., controller/.
routes.MapRoute(
"IndexOrAbout",
"{action}",
new { controller = "home", action = "index", id = "" },
new
{
action = "(index)|(about)"
}
);
Note, if you need to expand this to more top-level routes or make it more dynamic you could use a custom routing constraint that could draw the top-level values from a database or configuration. At that point, you'd probably want to change it from using the action parameter to the id parameter and have a single action that use the id to determine what to show rather than have an action per value.

asp.net mvc routing: how to use default action but non-default parameter?

I'm rewriting the question, as the answers so far show me that I have not defined it good enough. I'll leave the original question for reference below.
When you set up your routing you can specify defaults for different url/route parts. Let's consider example that VS wizard generates:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "DefaultPage", action = "Index", id = UrlParameter.Optional } // Parameter defaults
In this example if controller is not specified, DefaultPageController will be used and if action is not specified "Index" action will be used.
The urls will generally look like: http://mysite/MyController/MyAction.
If there is no action in Url like this: http://mysite/MyController then the index action will be used.
Now let's assume I have two actions in my controller Index and AnotherAction. The urls that correspond to them are http://mysite/MyController and http://mysite/MyController/AnotherAction respectively. My "Index" action accepts a parameter, id. So If I need to pass a parameter to my Index action, I can do this: http://mysite/MyController/Index/123. Note, that unlike in URL http://mysite/MyController, I have to specify the Index action explicitly. What I want to do is to be able to pass http://mysite/MyController/123 instead of http://mysite/MyController/Index/123. I do not need "Index" in this URL I want the mvc engine to recognize, that when I ask for http://mysite/MyController/123, that 123 is not an action (because I have not defined an action with this name), but a parameter to my default action "Index". How do I set up routing to achieve this?
Below is the original wording of the question.
I have a controller with two methods definded like this
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(SomeFormData data)
{
return View();
}
This allows me to process Url like http://website/Page both when the user navigates to this url (GET) and when they subsequently post back the form (POST).
Now, when I process the post back, in some cases I want to redirect the browser to this url:
http://website/Page/123
Where 123 is some integer, and I need a method to process this url in my controller.
How do I set up the routing, so this works? Currently I have "default" routing generated by the wizard like this:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "DefaultPage", action = "Index", id = UrlParameter.Optional } // Parameter defaults
I tried adding another controller method like this:
public ActionResult Index(int id)
{
return View();
}
But this doesn't work as ambiguous action exception is thrown:
The current request for action 'Index'
on controller type 'PageController'
is ambiguous between the following
action methods:
System.Web.Mvc.ActionResult Index() on
type PageController
System.Web.Mvc.ActionResult
Index(Int32) on type PageController
I must add, that I also have other actions in this controller. This would have worked if I didn't.
Here is how this can be resolved:
You can create a route constraint, to indicate to the routing engine, that if you have something in url that looks like a number (123) then this is an action parameter for the default action and if you have something that does not look like a number (AnotherAction) then it's an action.
Consider This code:
routes.MapRoute(
"MyController", "MyController/{productId}",
new {controller="My", action="Index"},
new {productId = #"\d+" });
This route definition will only match numeric values after MyController in http://mysite/MyController/123 so it will not interfere with calling another action on the same controller.
Source: Creating a Route Constraint
If you keep the variable name to remain being ID, you don't need to change anything.
Rename the post one to "PostIndex" and add this attribute:
[ActionName("Index")]
Same question on SO here.
Ok, here's a cut/paste answer for you, if that helps.
public ActionResult Index() {
return View();
}
[AcceptVerbs(HttpVerbs.Post), ActionName("Index")]
public ActionResult PostIndex(SomeFormData data) {
return View();
}
Oh i got it now. I think It's not possible with default route, You need to map custom routes.
// /MyController/AnotherAction
routes.MapRoute(
null,
"MyController/AnotherAction",
new { controller = "DefaultPage", action = "AnotherAction" }
);
// /MyController
// /MyController/id
routes.MapRoute(
null,
"MyController/{id}",
new { controller = "DefaultPage", action = "Index", id = UrlParameter.Optional }
);
ps. Default routes like /MyController/id must mapped at last.
I think you want return the same view, but you need understand you are doing two differents things. One is receive post, and another is accessing by get, and another is accessing by get and parameter...
You should do 3 actionresult with different names, and return de same view as View("ThisIsTheResult")

ASP.NET MVC routing conflict - null value for input variable

I'm at a loss as to why my routes are conflicting. I have these in my Global.asax file:
routes.MapRoute(
"CustomerView", "{controller}/{action}/{username}",
new { controller = "Home", action = "Index", username = "" }
);
routes.MapRoute(
"Default", "{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "0" }
);
So far everything has worked fine except when I created a controller action like so:
public ActionResult MyAction(int id)
{
//Do stuff here
return View();
}
When I try viewing it through http://mydomain/MyController/MyAction/5 I get:
Server Error in '/' Application.
The parameters dictionary contains a
null entry for parameter 'id' of
non-nullable type 'System.Int32' for
method 'System.Web.Mvc.ActionResult
Track(Int32)' in
'InTouch.Controllers.OrderController'.
To make a parameter optional its type
should be either a reference type or a
Nullable type. Parameter name:
parameters
suggesting to me that the id value is not being read properly. Sure enoguh, when I swap the order of the routes around it works fine. My (admittedly limited) understanding so far was that, if a variable name specified in a route matches that specified in a controller action definition, it will assume that one regardless of order. Apparently I was wrong. Swapping the order causes other controller actions to break. What is the right way to handle my routes in this instance?
The problem with your example is that the match is happening on the first route and it's seeing "5" as the username parameter. You can use constraints to limit what values are accepted for each parameter to accomplish what you're wanting. Since the "Default" route that accepts an Id is more restrictive than the "CustomerView" route, I would list the "Default" route first with a constraint on the Id parameter:
routes.MapRoute(
"Default", "{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "0" },
new { id = #"\d+" }
);
This will cause the first route to only match if Id is an integer value. All other requests would then fall through to the "CustomerView" route that would pick up any other requests that didn't have integers as that third parameter.
Check out Creating a Route Constraint for an explanation of constraints.
My (admittedly limited) understanding
so far was that, if a variable name
specified in a route matches that
specified in a controller action
definition, it will assume that one
regardless of order.
The binding of route values to action arguments happens AFTER the framework determines which route to use. Route selection is performed using a "first match wins" heuristic: the first route that can successfully match the incoming request is used, even if a "better" route was defined later.
Michael's solution is correct. You need to list the Default route first, using route constraints to only match URLs where the ID is numeric. Your second, less restrictive route should come next.
NOTE: If you follow Michael's solution you'll run into problems if you have any users with a username consisting only of numbers. You might consider adding some other discriminating factor to the routes, like putting the keyword "user" in the 2nd one:
routes.MapRoute(
"Default", "{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "0" },
new { id = #"\d+" }
);
routes.MapRoute(
"CustomerView", "{controller}/{action}/user/{username}",
new { controller = "Home", action = "Index", username = "" }
);

Resources