I've been pondering this problem for hours now, and it looks to me that while the XML site map functionality was updated to accept external URLs with the URL attribute, that feature was not extended to dynamic definitions via DynamicNode.
We have several applications that share a menu, and thus we need a single definition for these menu items, each pointing to one of our applications in the intranet. We currently use (for ASP.NET) a custom site map that requires the XML files be deployed to each application, and I want the MVC implementation to read the items dynamically - probably from a database.
Since DynamicNode does not have a URL property, it clearly requires that the path be virtual and based upon the other properties (controller, action, routes). I tried implementing a UrlResolver to look at "action" for "HTTP", which worked, and I returned the URL unchanged. However, I receive the dreaded
"http://site.com/App is not a valid virtual path."
error. It appears the change introduced by the author in 3.1.0 for the XML property did not translate to the dynamic equivalent. Can anyone help me? Here is the code I wrote to extend the default classes. This code is executed and works fine. Sometime after ResolveUrl returns my HTTP, the failure occurs, so I guess I'm either missing something or it cannot be done.
public class DynamicSiteMapNodeProvider : DynamicNodeProviderBase
{
override public IEnumerable<DynamicNode> GetDynamicNodeCollection()
{
var nodes = new List<DynamicNode>();
nodes.Add(new DynamicNode
{
Key = "Dynamic1",
Title = "Dynamic One!",
Action = "http://my.site.com/MyApp"
});
return nodes;
}
}
public class FlexibleSiteMapUrlResolver: DefaultSiteMapNodeUrlResolver
{
public override string ResolveUrl(MvcSiteMapNode mvcSiteMapNode, string area, string controller, string action, IDictionary<string, object> routeValues)
{
// when action value is clearly a web site url, simply accept it.
if (action.Substring(0, 5).ToLower() == "http:")
{
return action;
}
return base.ResolveUrl(mvcSiteMapNode, area, controller, action, routeValues);
}
}
5/15/2013: Is there nobody who can help with this?
This was fixed in version 4 - the URL property has been added to the DynamicNode object and works the same as it does everywhere else.
Version 4 was released a couple of weeks ago and is available on the NuGet.
Related
I have a web API exposing ODATA from a SQL stored proc. I want to use a url like /odata/firmhierarchy(225) to pass 225 into a param for the stored proc. It just tells me that it can't find a matching resource. It hits the controller, just skips the method. Thoughts?
In webapiconfig
private static IEdmModel GenerateEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Employee>("Employees");
builder.EntitySet<Employee>("FirmHierarchy");
return builder.GetEdmModel();
}
Context:
public virtual ObjectResult<Employee> sp_EmployeeHierarchy(Nullable<int> managerEmpID)
{
var managerEmpIDParameter = managerEmpID.HasValue ?
new SqlParameter("ManagerEmpID", managerEmpID) :
new SqlParameter("ManagerEmpID", 0);
return ((IObjectContextAdapter)this).ObjectContext.ExecuteStoreQuery<Employee>("sp_EmployeeHierarchy #ManagerEmpID", managerEmpIDParameter);
}
Only method in controller:
[Queryable]
public IQueryable<Employee> GetFirmHierarchy()
{
return db.sp_EmployeeHierarchy(225).AsQueryable();
//return SingleResult.Create(db.Employees.Where(employee => employee.EmpId == key));
}
This should work:
1.Write another method in your controller:
[EnableQuery]
public IQueryable<Employee> Get([FromODataUri] int key)
{
return db.sp_EmployeeHierarchy(key).AsQueryable();
}
Please note that [EnableQuery] is an attribute introduced in Web API for OData V4. If you are still using Web API for OData V1-3, use [Queryable] still.
2.Then you can send the request
GET /odata/firmhierarchy(225)
and get the employees.
I was able to make ODATA work for a table, when auto-generated from entity framework. However, that generation process didn't want to work for a complex type returned by a Table Valued Function (similar scenario to a SP), because it didn't seem to understand where the key was.
What I found was that I could however make it work. First, I check out this article. He sets things up a bit more manually, where his Get on a companyProcessingController ends up routing for id 3 as "http://localhost:10020/odata/companyProcessing(3)" .
This surprised me. My other generated classes set up the pattern that SomeEntity became SomeEntityController, with methods like GetSomeEntities, and a routing that seemed to me to match the method but dropping the word get. Therefore, dropping the entity name from the Get method name seemed different, but it worked. Proving that the path is actually matching the controller name, not the method name.
In this Case you configure the routing using the data type you're querying for, and the beginning of the controller name. Then the actual path utilizes the beginning of the controller name as well.
And then all of this just brings us essentially to the other posted solution, assuming your controller name is firmhierarchyController
So, now, making sense of this... Try going to http://localhost:55063/odata/$metadata , where your port may differ. You'll notice that ODATA exposes a DataType, which is accessed via a DataSet. When a client tries to query into ODATA, they are trying to query against the DataSet, getting items of the DataType.
The DataSet matching the controller name (less Controller), and the Get methods can indeed just be Get without further extension of the name - and otherwise in this scenario was giving me problems.
first of all here is my situation. I am programming an intranet application using ASP.NET MVC 3 with Entity Framework 4.1. My application has been developed using the "Unit of Work" and "Repository" design patterns.
How ever in my opinion it should go the way that my application has an unit of work that provides a central access to all the repositories which further provide access to the entities.
Lets say I have a entity called "ProductApprovalDocument" with the properties "id", "creationDate" and "approvalDecission" stored in the database. Now I want the user to be able to access a PDF file of the document thats shortly described by the entity. Because the files are stored in a central directory on a file server using the URL format "[fileServerDirectoryPath]/[ProductApprovalDocument.id].pdf", I do not want to save an extra property for that filepath on the database. What I would like to do, is give the entity an extra property called "filepath" that automatically constructs the path with the given information and returns it.
Now the Problem:
I use an interface called FileService to abstract file access from the rest of the application. Now in my case I would have to access the UnitOfWork object out of the entity model, to retrieve the current FileService implementetion and get the preconfigured filepath. I think that's the totaly wrong way because to me an entity model should only be used as a data container not more or less.
Now the Question:
How do I handle such a situation. I would not like to always set the filepath property through the controller because ist more or less static and therefore could be done somehow automatic by the model.
Edit (final solution):
Thanks to the answer of Andre Loker I gained another point of view to my problem.
What was the central target I wanted to reach?
I wanted the user to gain access to a file stored on a fileserver.
Do I have to provide every displayed entity with the total filepath?
No! Think about the principle of MVC! User actions get processed by the controller just in time. You don't have to provide information untill it really get's used.
So the solution is just to render all data as usual but instead of displaying a static html link to the files, you have to include an ActionLink to the Controller which calculates the filepath on the fly and automatically redirects the user to the file.
In the View do this:
#Html.ActionLink(Model.ID.ToString(), "ShowProductApprovalDocumentFile", "ProductApprovalDocument", new { ProductApprovalDocumentID = Model.ID }, null)
instead of this:
#Model.ID
And add an corresponding Action to the controller:
public ActionResult ShowProductApprovalDocumentFile(int ProductApprovalDocumentID )
{
return Redirect(_unitOfWork.FileService.GetFilePathForProductApprovalDocument(ProductApprovalDocumentID));
}
Thanks to the guys that took the time to give me an answer and special thanks to Andre who lead me to the satisfying answer! :)
If I understand the property correctly, there are several options:
1) Make the FilePath property use a service locator to find the FileService:
public string FilePath {
get {
FileService fileService = DependencyResolver.Current.GetService<FileService>();
return fileService.GetFilePathForDocument(this);
}
}
While I'm not a hugh fan of static service locators as they make testing more difficult, this could be a viable option. To make it more easily testable you can make the file service locator injectable:
private static readonly Func<FileService> defaultFileServiceLocator = ()=>DependencyResolver.Current.GetService<FileService>():
private Func<FileService> fileServiceLocator = defaultFileServiceLocator;
public Func<FileService> FileServiceLocator {
get { return fileServiceLocator; }
set { fileServiceLocator = value ?? defaultFileServiceLocator; }
}
And then use this in FilePath
public string FilePath {
get {
FileService fileService = fileServiceLocator();
return fileService.GetFilePathForDocument(this);
}
}
This way you can inject your own file service locator during testing.
2) Explicitly require the FileService when retrieving the file path. Instead of a FilePath property you'd have:
public string GetFilePath(FileService service){
service.GetFilePathForDocument(this);
}
The problem with this is of course that now the caller of GetFilePath needs to have a FileService. This isn't much of a problem for controllers, because if you use an IoC you can inject a FileService into the controller constructor. This approach is the cleaner one as it doesn't depend on service locators, but as you see it is slightly more inconvenient for the caller.
3) Inject the FileService into the document class itself.
Instead of using a file service locator you'd inject the file service itself when you construct your ProductApprovalDocument. With this approach you can use a simple FilePath property again. The main problem is that this often doesn't play too well with ORMs, as they often construct the objects using a default constructor and you'd have to somehow hook into the object construction process to inject the dependencies. Also, I'm not a big fan of injection services into domain objects.
4) You set the FilePath from outside the entity. As you said this should be done somewhat automatically as you don't want to do it manually every time. This would require some layer through which all entities need to pass which sets up the FilePath property.
5) Don't make FilePath a property of ProductApprovalDocument at all. This would be a reasonable choice, too. ProductApprovalDocument doesn't know anything about its FilePath, so why should it be a property? Its the FileService that calculates the value. You can still have a distinct view model version of ProductApprovalDocument which does have a FilePath property. You'd set the property when you create your view model:
var model = new ProductApprovalDocumentViewModel();
mapper.Map(realDocument, model); // map common properties with AutoMapper or so
model.FilePath = fileService.GetFilePathForDocument(realDocument);
However, if ProductApprovalDocument needs to do something with its FilePath (why would it?) this approach doesn't work anymore.
Personally I'd go with solution 5, 2 or 1 in that order of precedence, where applicable.
Whilst I would be hesitant to rely on being able to calculate the filepath and I would prefer to store it as part of the entity (in case it ever needs to change for some reason), in your situation if I was adamant I wanted to do it the way you've said, I think I would extend the FileService/ViewModel to have a Filepath property which was derived in the fashion you have stated.
e.g. if I wanted to create a download link I'd do this in the ViewModel
public string FilePath
{
get
{
return String.Format(#"thehardcodedbit{0}.pdf",ID);
}
}
EDIT: If you have an Entity generated by EF4.x then it will have been generated as a partial class so you could always extend it like this (I have done this sort of thing and it works okay):
Say the generated entity looks like this:
Namespace Da_Wolf.Model.Entities.File
{
public partial class UploadedFile
{....}
}
Then you could create a partial class like this:
Namespace Da_Wolf.Model.Entities.File
{
public partial class UploadedFile
{
public string FilePath
{
get
{
return String.Format(#"thehardcodedbit{0}.pdf",ID);
}
}
}
}
Now you have the property you desire available everywhere without adding anything to the ViewModels.
A problem I come up against again and again is handling redirection to the previous page after a user runs some action such as clicking a 'Back To ...' link or saving the record they are editing.
Previously whenever I have needed to know what page to return to, I would provide a returnURL parameter to my current view.
http://blah.com/account/edit/1?returnURL="account/index"
This isn't a very clean way of handling this situation, as sometimes the return URL contains parameters such as search strings, etc, which have to be included in the URL.
http://blah.com/account/edit/1?returnURL="account/index?search="searchTerm""
Also, this creates an issue when a user can go another page forward before coming back to the page with the returnURL because you have to pass the returnURL through all visited pages.
Simply calling the browser's Back functionality isn't really sufficient either, because you might want the page to refresh, e.g. to show the edits you just saved in the previous page.
So my question is, has anyone found a smart way to handle this kind of situation, specifically in an MVC environment?
Note: I am using ASP .NET MVC so if possible I'd like answers to pertain to that, however any ideas are welcome.
What's wrong with setting a cookie, or using a session variable? The only reason you wouldn't is if you don't control the page that calls into you, in which case your only options are query strings, post values, or referrer.
I thought I might add my answer to the question to see if others think it's a good idea.
I'm simply passing it through to the controller using TempViewData:
#{
TempData["returnURL"] = Request.Url.AbsoluteUri;
}
and then accessing it in a similar way to this (in my real version I check that the key is in TempData and that the returnURL is a real URL):
return Redirect(TempData["returnURL"].ToString());
If it needs to continue on past the first page change (i.e. Search page -> Edit page -> Edit Section page) I'm adding it again
TempData["returnURL"] = TempData["returnURL"];
Check my blog post on it: Using cookies to control return page after login on asp.net mvc 3
Just like #Mystere Man mentioned, you can just use a cookie or session for it. I went for cookies back when I had a similar situation a while ago.
Try register a new route of which the url is /{controller}/{action}/{id}/returnurl/{*url} and then use a RedirectToAction in the action that accepts url as a parameter
Request.UrlReferrer.AbsoluteUri
though i'd still argue that you shouldn't be creating your own "back" button.
Use an interceptor or an aspect:
Intercept each request in some fashion (e.g., a #Before aspect) and save the requested URL to the session, overwriting it each time
In your view layer, access that Session object as needed, in your case for the back link.
This kind of design allows you to always have the most recent request available if you want to use it. Here's an example to write an aspect / interceptor in .NET. Additionaly, PostSharp is a .NET aspect project.
At present, a quick and dirty method has eluded me... so I'm using a practical method.
On a conceptual level, the 'back-ability' of a page should be determined by the page that you're currently on. The View can infer this (in most cases) if the parameters captured in the Controller are passed to it via the ViewModel.
Example:
Having visited Foo, I'm going to Bar to view some stuff, and the back button should return to Foo.
Controller
public ActionResult Foo(string fooId) // using a string for your Id, good idea; Encryption, even better.
{
FooModel model = new FooModel() { fooId = fooId }; // property is passed to the Model - important.
model.Fill();
return View("FooView", model);
}
public ActionResult Bar(string fooId, string barId)
{
BarModel model = new BarModel() { fooId = fooId; barId = barId };
model.Fill()
return View("BarView", model)
}
ViewModels
public class FooModel
{
public string fooId { get; set; }
public void Fill()
{
// Get info from Repository.
}
}
public class BarModel
{
public string fooId { get; set; }
public string barId { get; set; }
public void Fill()
{
// Get info from Repository.
}
}
View (Partial) // No pun intended... or maybe it was. :)
Your BarView can now interpret from its model where it needs to go back to (using fooId).
On your BarView (using MVC2 syntax):
Back
You can use Html.ActionLink as well.
Alternatively:
You can inherit your ViewModels from a BaseViewModel, which can have a protected property returnURL. Set this where necessary.
Example:
On your ViewModel:
public class BarModel : BaseViewModel
{
public string fooId { get; set; }
public string barId { get; set; }
public void Fill()
{
returnURL = string.Format("/Foo?fooId={0}", fooId)
// Get info from Repository.
}
}
On View:
Back
Would this be better handled by partial actions that display without leaving the page and using JQuery to make a dialog/wizard workflow?
Then you only need to react to the 'Finish' button on the dialog to refresh the original view.
For the part of your question regarding "saving the record they are editing" I would think the post-redirect-get (PGR) pattern would apply to you.
This might be a good place to read about it if you are not familiar with it.
http://en.wikipedia.org/wiki/Post/Redirect/Get
http://blog.andreloker.de/post/2008/06/Post-Redirect-Get.aspx
Encode the returnUrl using Url.Encode(returnUrl) for inclusion in the URL.
When ready to redirect, use Url.Decode(returnUrl) and use the value for the actual redirect.
Just building a quick, simple site with MVC 3 RC2 and RavenDB to test some things out.
I've been able to make a bunch of projects, but I'm curious as to how Html.ActionLink() handles a raven DB ID.
My example: I have a Document called "reasons" (a reason for something, just text mostly), which has reason text and a list of links. I can add, remove, and do everything else fine via my repository.
Below is the part of my razor view that lists each reason in a bulleted list, with an Edit link as the first text:
#foreach(var Reason in ViewBag.ReasonsList)
{
<li>#Html.ActionLink("Edit", "Reasons", "Edit", new { id = Reason.Id }, null) #Reason.ReasonText</li>
<ul>
#foreach (var reasonlink in Reason.ReasonLinks)
{
<li>#reasonlink.URL</li>
}
</ul>
}
The Problem
This works fine, except for the edit link. While the values and code here appear to work directly (i.e the link is firing directly), RavenDB saves my document's ID as "reasons/1".
So, when the URL happens and it passes the ID, the resulting route is "http://localhost:4976/Reasons/Edit/reasons/2". So, the ID is appended correctly, but MVC is interpreting it as its own route.
Any suggestions on how I might be able to get around this? Do I need to create a special route to handle it or is there something else I can do?
I just downloaded the latest version of RavenDB and tried this out.
public class Entity {
public int Id { get;set; }
public string Text { get;set; }
}
When I saved it to RavenDB, the id in Raven was "entities/1", but the mapping in RavenDB client was able to successfully interpret the Id from what was in the database to the integer that I wanted.
var entity = session.Load<Entity>(1);
Assert.IsTrue(entity.Id == 1);
You do not need a extension method, and this way you would not need to alter any routes as mentioned above, because you will be dealing with good ol' integers. The string Ids were almost a deal breaker, but amazingly this works so RavenDB is back in the hunt.
Note: I figured this out from watching a Rob Ashton talk and realizing that all his document classes had integers as Ids.
I was having a similar issue and came up with my own solution before I found the link to Shiju Varghese blog post referenced by #jfar.
It might not be as clean and simple as the solutions provided in the blog post, but I do believe it can compete as a solution none the less. So here goes:
In a standard model class, when using RavenDB, we normally have an id property like so:
public string Id { get; set; }
What I did, was to add another id property like so:
public int? IdInt
{
get { return int.Parse(Id.Substring(Id.IndexOf("/") + 1)); }
}
This will give us the number part of the given RavenDB id.
I then had a class that looked something like this:
[Bind(Exclude = "IdInt")]
public class Item
{
public string Id { get; set; }
public int? IdInt
{
get { return int.Parse(Id.Substring(Id.IndexOf("/") + 1)); }
}
...
}
Note that I have excluded the IdInt property, as I don't want the model binder to handle it.
Furthermore, note, that the IdInt property is nullable. This is to avoid problems with the model binder later on, when we are creating new items and the Id property is null.
Then in the routes I declared a rule similar to this:
routes.MapRoute(
"WithParam", // Route name
"{controller}/{action}/{id}" // URL with parameters
);
Later on using an ActionLink or something similar we can do the following:
#Html.ActionLink("Details", "Details", new { id = item.IdInt })
And lastly when we press the rendered link, we are sent to the appropriate action which could look something like this:
public ActionResult Details(int id)
{
var item = _session.Load<Item>(id);
return View(item);
}
The reason why this will work is because of the Load method, which takes a ValueType as parameter and then automatically resolves the number to the correct RavenDB id.
What we have achieved with this approach is a url that looks something like this:
/items/details/1
As I said in the beginning, this solution is less elegant than the previous suggested solutions, however, this approach will give you a cleaner url as you can work with the id's as you normally would.
FYI, the link posted by #jfar was exactly the article. The following is the solution I used from the article:
Solution 2 - Modify ASP.NET MVC Route
Modify the ASP.NET MVC routes in the
Global.asax.cs file, as shown in the
following code:
routes.MapRoute(
"WithParam", // Route name
"{controller}/{action}/{*id}" // URL with parameters
);
We just put "*" in front of the id
variable that will be working with the
default Id separator of RavenDB
I'm working with a client that wants the URLs in our web application to be in French. I'm an English developer and we also have English clients. This is an interesting problem but I don't think its something the ASP.NET MVC Framework would support.
Here's the scenario. The route...
Specific EXAMPLE
English URL
www.stackoverflow.com/questions/ask
would also support
French URL
www.stackoverflow.com/problème/poser
Generic EXAMPLE
English URL
http://clientA.product.com/AreaNameEnglish/ControllerNameEnglish/ActionNameEnglish/params
also needs to support
French URL
http://clientB.product.com/AreaNameFrench/ControllerNameFrench/ActionNameFrench/params
So in MVC my Area, Controller and Actions all need to have both English and French translations.
Obviously maintainability would be a HUGE issue if I were to go and hardcode all my Controllers, Views and Action names to French. Is there anyway to localize the route that is presented in the browser without doing this? Keeping in mind there are lots of different routes in the application. A couple Areas each with a handful of Controller each with many Actions?
Thanks,
Justin
EDIT
Thanks to #womp here is what I've come up with so far... Although in the end I took the approach which I posted as an answer.
public class LocalizedControllerFactory : DefaultControllerFactory
{
public override IController CreateController(RequestContext requestContext, string controllerName)
{
if (string.IsNullOrEmpty(controllerName))
throw new ArgumentNullException("controllerName");
if (CultureInfo.CurrentCulture.TwoLetterISOLanguageName == "fr")
{
controllerName = this.ReplaceControllerName(requestContext, controllerName);
this.ReplaceActionName(requestContext);
this.ReplaceAreaName(requestContext);
}
return base.CreateController(requestContext, controllerName);
}
private string ReplaceControllerName(RequestContext requestContext, string controllerName)
{
// would use the language above to pick the propery controllerMapper. For now just have french
Dictionary<string, string> controllerMapper = new Dictionary<string, string>()
{
{"frenchControllerA", "englishControllerA"},
{"frenchControllerB", "englishControllerB"}
};
return this.ReplaceRouteValue(requestContext, "controller", controllerMapper);
}
private void ReplaceAreaName(RequestContext requestContext)
{
// would use the language above to pick the propery areaMapper. For now just have french
Dictionary<string, string> areaMapper = new Dictionary<string, string>()
{
{"frenchAreaX", "englishAreaX"},
{"frenchAreaY", "englishAreaY"}
};
this.ReplaceRouteValue(requestContext, "area", areaMapper);
}
private void ReplaceActionName(RequestContext requestContext)
{
// would use the language above to pick the propery actionMapper. For now just have french
Dictionary<string, string> actionMapper = new Dictionary<string, string>()
{
{"frenchAction1", "englishAction1"},
{"frenchAction2", "englishAction2"}
};
this.ReplaceRouteValue(requestContext, "action", actionMapper);
}
private string ReplaceRouteValue(RequestContext requestContext, string paramName, Dictionary<string, string> translationLookup)
{
if (requestContext.RouteData.Values[paramName] == null)
{
return null;
}
string srcRouteValue = requestContext.RouteData.Values[paramName] as string;
if (srcRouteValue != null && translationLookup.ContainsKey(srcRouteValue))
{
requestContext.RouteData.Values[paramName] = translationLookup[srcRouteValue];
}
return requestContext.RouteData.Values[paramName] as string;
}
}
A decent start. If I localize just the ControllerName and ActionName in the Url it will find and render the proper View. However I have the following problems.
Area Name can't be translated
Localizing the Area means the Controller.View() method fails to find Views.
Even though I've replaced the Area name in the request context the ViewEngineCollection.Find() method doesn't seem to pick it up. Anywhere in my Controller class that does "return View()" fails to find the default view for its action. If I don't localize the Area then the other steps work.
RedirectToAction or Html.ActionLink
Anytime the application calls RedirectToAction or if I use an Html.ActionLink helper or something similiar the Urls generate are the English ones. It looks like I'm going to have to add logic somewhere possibly in multiple spots to convert an English Url to the French (or other language) one.
The following blog contains a complete solution this exact problem. Its actually a very elegant solution which I highly recommend.
https://blog.maartenballiauw.be/post/2010/01/26/translating-routes-(aspnet-mvc-and-webforms).html
Note to get it working for AREAs I had to add the following extension method to his "TranslatedRouteCollectionExtensions.cs" class:
public static Route MapTranslatedRoute(this AreaRegistrationContext areaContext, string name, string url, object defaults, object routeValueTranslationProviders, bool setDetectedCulture)
{
TranslatedRoute route = new TranslatedRoute(
url,
new RouteValueDictionary(defaults),
new RouteValueDictionary(routeValueTranslationProviders),
setDetectedCulture,
new MvcRouteHandler());
route.DataTokens["area"] = areaContext.AreaName;
// disabling the namespace lookup fallback mechanism keeps this areas from accidentally picking up
// controllers belonging to other areas
bool useNamespaceFallback = (areaContext.Namespaces == null || areaContext.Namespaces.Count == 0);
route.DataTokens["UseNamespaceFallback"] = useNamespaceFallback;
areaContext.Routes.Add(route);
return route;
}
However, even with this a translated route with an AREA can be read and interpreted the routes generated always seem to include an English AREA name but localized everything else.
I was directed to a blog via the same question asked on the ASP.NET MVC Forums
The MVC framework supports pretty much any routing scenario you can think of, but not necessarily with the default routing classes.
Most localization solutions I've run across involve using the same Controller and Action method names, but specifying a culture parameter in the route which dictates which translated version of the View is presented. For example,
http://clientA.product.com/AreaName/Controller/Action //en-US
http://clientB.product.com/es-MX/AreaName/Controller/Action // spanish
If you really must have translated URL's though, I don't see much other choice then to maintain a mapping table somewhere. If I understand your question correctly, you need to be able to map all the different language translations of "questions" (controller) and "ask" (action) to the same controller/action method combination.
However, once you've built this table somewhere (resource files?), you can easily override the DefaultControllerFactory that the framework is using, and implement your own logic for determining the controller to instantiate. So instead of just matching the {controller} token from the URL as a simple string comparison, you can implement logic to check it against your mapping table to pick the right controller.
For a walkthrough of creating a custom controller factory, check this great blog post. It's actually a localization example as well, but it's based on the user's culture settings, rather than the language of the URL.