Map Area, Controller and Action names to default values if empty - asp.net-mvc

I am given 3 strings: area, controller and action. Some of them could have empty values, as there are default values defined in my routes. For example, if action is empty it will be mapped to Index.
I wish to pass these 3 strings through the routing of my application and get their actual values after any empty values where mapped to default values. Is this possible?
Maybe it is doable by doing something like this
RouteTable.Routes.GetRouteData(context)
but is there a way to do it without constructing a whole HttpContext object?

The first thing you will have to do is take your three strings and convert that into a URL that can be passed thru the routing system. How you do this depends upon what routes you have created. Let's assume you have the typical default route in your route table:
context.MapRoute(
"Admin_default",
"Admin/{controller}/{action}/{id}",
new { controller="Home", action = "Index", id = UrlParameter.Optional }
);
Then you would have to use the URL pattern "{area}/{controller}/{action}/{id}" to build up your URL. In your case, some of the strings could be empty, so your URL would not have all of the segments. For example, if Area="Admin" and Controller and Action are empty, your URL, based on this route, would be:
~/Admin
Of course, this raises the question of if you already know the routes in your system, why can't you just get the defaults by looking at the routes.
Once you have the URL that you want to test, you will need to create a mock of the HttpContentBase object and then run it thru RouteTable.Routes.GetDate(httpContext). There is straightforward boiler plate code to do this, since you really do not need to mock much of the constituent HttpRequestBase or HttpResponseBase classes. For example, see here for an example of mocking an HttpContextBase object using the Moq mocking framework.

Related

What is the purpose of MVC passing the current "controller" and "action" as parameter values

I had to pass an extra parameter with my action links to indicate where they came from (as I needed to change a back link in the pages accordingly).
As it was a controller name, I decided to name it controller.
e.g. a sample link might be:
#Html.ActionLink(item.Name, "Options", "Questionnaire", new {
id = item.QuestionnaireId,
controller = "templates" }, null)
The receiving action in QuestionnaireController looked like:
public ActionResult Options(int id, string controller)
When the action was hit I noticed the controller value was not template, but instead was the name of the current controller (i.e. QuestionnaireController).
As an experiment I added an action parameter e.g.:
public ActionResult Options(int id, string controller, string action)
the action value was the current action too (i.e. Options).
My work-around for this was simply to rename my parameter to source, but why does MVC bother to map the names controller and action to action parameters? I assume that would apply to any/all Route Mapping values, but what is the purpose of this?
Why does MVC bother to map the names controller and action to action parameters?
I believe it's done as part of the QueryStringValueProvider or one of the other ValueProviders (maybe the RouteDataValueProvider). ASP.Net MVC uses Convention over Configuration, so the framework uses the values provided to populate method parameters. The Controller name, Action name and even the Area name are all values provided in the Url.
I assume that would apply to any/all Route Mapping values, but what is the purpose of this?
The ValueProvider is used for Routing data to determine the matching route to use, it also happens to be the same object that provides the data to populate method parameters. The side affect you are experiencing is most likely not a feature they were trying to implement.
The DefaultModelBinder.GetValue uses the ValueProviders to locate a value and bind it to the model (or method paramater).

What do the curly braces mean in a route in MVC?

I'm setting up my routes with an MVC project but im a little confused about the curly braces...
If I have...
routes.MapRoute( "Music", "Music/{name}", new { } );
What is the purpose of the curly braces around name, does this get passed to something? Or does this map to something if I pass a default object in?
They are parameter names that are used in routing requests. For example the default route defines three of them:
{controller}/{action}/{id}
controller and action parameters are for finding your controller action. id parameter can be used as an input in those actions.
When you define a custom route you have to provide controller and action parameters. If they are not defined in your URL, you should provide default values so MVC knows what action to run when a request matches that route.
routes.MapRoute("Music",
"Music/{name}",
new { controller="Music", action="SomeAction" });
Other parameters like id or name like you defined can be used to provide input to actions. In your example, name parameter is passed to matching action like this:
public ActionResult SomeAction(string name)
{
//do something
}
The curlybraces indicate a kind of named wildcard.
The "Music/Index" route will only match the URL Music/Index and nothing else
The "Music/{Name}" route will match any URLs starting with Music, and having anything after the slash. It will match both the URLs Music/metallica and Music/madonna.
With the curly brace, you'll be able to pick up "metallica" or "madonna" from the above URLS as routevalues.
As a final example: With ASP.NET MVC, there's always a standard route. {controller}/{action}/{id}. This route will catch URLs like Music/genre/rock or Product/edit/5.
The resulting routevalues for these two will be:
controller=music, action=genre and id=rock for the first one
controller=product, action=edit and id=5 for the last one.
I'll try to provide a less contrived example.
Routes in ASP.NET MVC are placed into a dictionary, and when there's an incoming request, the MVC pipeline looks at the request and tries to determine what Controller and Action to route it to.
So let's say I have the following controllers: Home, Forum, and Article
And while we're at it, let's say I have the following actions: View, Edit, Create on both the Forum and Article controllers.
Those braces allow me to create one route for both:
routes.MapRoute("Viewing",
{controller}/{action}/{id},
new {controller = "Article", action="" }, //The article controller has precedence
new { controller = "Article|Forum" } //contrived for this example
);
Those braces mean that whatever controller they put in (as long as it's Article or Forum based on the Constraints), the same route works. This keeps me from having to have a route for each and every action in the Forum and Article controller.
I could have just as easily made two routes:
routes.MapRoute("Articles",
article/{action}/{id},
new {controller = "Article" } //The article controller has precedence
);
routes.MapRoute("Forums",
forum/{action}/{id},
new { controller = "forum" }
);
But there's duplication there that doesn't need to be there.
Routes are also pretty tricky things, in that order matters. The top route will be evaluated before the bottom route. If it matches the top route's structure, it will go to that action, even if that's not the right action.
Phil Haack has a Route Debugger that helps with this. And I've also taken his source code and modified it so that you can make it a control and put it on all your pages as a partial (and hopefully you will also put code on there that would only allow internal folks to see it).

How to route legacy type urls in ASP.NET MVC

Due to factors outside my control, I need to handle urls like this:
http://www.bob.com/dosomething.asp?val=42
I would like to route them to a specific controller/action with the val already parsed and bound (i.e. an argument to the action).
Ideally my action would look like this:
ActionResult BackwardCompatibleAction(int val)
I found this question: ASP.Net MVC routing legacy URLs passing querystring Ids to controller actions but the redirects are not acceptable.
I have tried routes that parse the query string portion but any route with a question mark is invalid.
I have been able to route the request with this:
routes.MapRoute(
"dosomething.asp Backward compatibility",
"{dosomething}.asp",
new { controller = "MyController", action = "BackwardCompatibleAction"}
);
However, from there the only way to get to the value of val=? is via Request.QueryString. While I could parse the query string inside the controller it would make testing the action more difficult and I would prefer not to have that dependency.
I feel like there is something I can do with the routing, but I don't know what it is. Any help would be very appreciated.
The parameter val within your BackwardCompatibleAction method should be automatically populated with the query string value. Routes are not meant to deal with query strings. The solution you listed in your question looks right to me. Have you tried it to see what happens?
This would also work for your route. Since you are specifying both the controller and the action, you don't need the curly brace parameter.
routes.MapRoute(
"dosomething.asp Backward compatibility",
"dosomething.asp",
new { controller = "MyController", action = "BackwardCompatibleAction"}
);
If you need to parametrize the action name, then something like this should work:
routes.MapRoute(
"dosomething.asp Backward compatibility",
"{action}.asp",
new { controller = "MyController" }
);
That would give you a more generic route that could match multiple different .asp page urls into Action methods.
http://www.bob.com/dosomething.asp?val=42
would route to MyController.dosomething(int val)
and http://www.bob.com/dosomethingelse.asp?val=42
would route to MyController.dosomethingelse(int val)

ASP.NET MVC class that represents a controller+action set?

Is there a class in the ASP.NET MVC framework that represents a controller name and an action name?
I'm writing a method that will return a collection of URLs and I want them to be returned as objects that contain a 'Controller' and 'Action' property or something similar.
This is because sometimes I'll need to isolate just the controller name or just the action name.
There's the Route object, but it seems way too heavyweight for what I'm trying to do.
Is there a simpler abstraction in ASP.NET MVC?
There is a Controller Base class not a name though, an action name is simply an string which the ActionInvoker will use to find the correct action via reflection.
I think you'll have to use the Route Values Dictionary of key/value pairs to represent the route.
RouteValueDictionary myValues = new RouteValueDictionary();
myValues.Add("Controller", "myController");
You could use a normal dictionary and then convert it in code to a RouteValueDictionary if you don't want/can't have to have access to the Routing namespace.
In this approach you can then using the keys of the either a standard dictionary or the routeValue Dictionary to do the isolation of the controller or action.
String ControlString = myValues["Controller"]
Then do what you want with it. Maybe use constants for the keys you wish to access.

Areas And Routes

I'm using areas everywhere and I'm wanting something like the following:
http://localhost/MyArea/MySection/MySubSection/Delete/20
Usually I access things by doing the following:
http://localhost/MyArea/MySection/MySubSection/20
But if I want to delete then I have to say
http://localhost/MyArea/MySection/DeleteEntryFromMySubSection/20
With routes, how do you do this? (the routes aren't realistic by the way, they're much more concise than this in my system)
EDIT: This is specifically related to the use of Areas, an ASP.NET MVC 2 Preview 2 feature.
It would depend on how your routes & controllers are currently structured.
Here's an example route you might want to use.
If you want to be able to call the following route to delete:
http://localhost/MyArea/MySection/MySubSection/Delete/20
And let's assume you have a controller called "MyAreaController", with an action of "Delete", and for the sake of simplicity let's assume section and subsection are just strings e.g.:
public class MyAreaController : Controller
{
public ActionResult Delete(string section, string subsection, long id)
{
Then you could create a route in the following way (in your Global.asax.cs, or wherever you define your routes):
var defaultParameters = new {controller = "Home", action = "Index", id = ""};
routes.MapRoute("DeleteEntryFromMySubSection", // Route name - but you may want to change this if it's used for edit etc.
"{controller}/{section}/{subsection}/{action}/{id}", // URL with parameters
defaultParameters // Parameter defaults
);
Note: I'd normally define enums for all the possible parameter values. Then the params can be of the appropriate enum type, and you can still use strings in your path. E.g. You could have a "Section" enum that has a "MySection" value.

Resources