This project is read-only.

Client side URL generation with client binding...

Dec 7, 2012 at 3:46 AM
Edited Dec 7, 2012 at 3:47 AM

If I have a controller action like this:


public ActionResult MyAction(Guid myParam)

In javasript, I can do this:

var someUrl=@Url.Action(MVC.MyController.MyAction())

That will generate "/MyController/MyAction'....and that is perfect.

Now, if you decide to do some sort of client side binding where the parameter is bound from a datasource you can use


All for the issue.  As you can see, the Guid parameter is required for this method.  So, if I change the route for this action to contain a Guid constraint on the myParam parameter, T4MVC will no longer find the route!  This make sense because it passes the area, controller and action to the Routing engine and there is not a match.   How would you propose we could fix that?

One thing I thought of was our own Url extension method - something like Url.ActionRoot(MVC.MyController.MyAction()) where it is NOT sent to MVC routing engine for outbound url generation, but rather just creates /{area}/{controller/{action} url and returns it?  Basically we are in a situation where we do want to restrict what is required from a routing point of view - but we also need to be able to build that route string dynamically without any magic strings.

Without something like the above, we would have to resort to something like this in javascript:

var someUrl="/"+MVC.MyController.Name+"/"MVC.MyController.MyAction.Name

Davide - any thoughts?   (BTW, really liked the change to having the common classes for T4MVC in their own NuGet dll - made it where I could extend it 'normally' rather than putting code in the hooks class.)

Dec 7, 2012 at 9:48 PM

I think the danger here is to build invalid assumptions on the shape of the route. e.g writing "someUrl+"?myParam="+theparam" assumes that myParam is passed on the query string, while it very well could be part of the path.

For that reason, I don't think there can ever be a Url.ActionRoot method. If your route looks like /{Controller}/{Id}/{Action} (unusual but perfectly valid), then the concept of a 'root' is not really meaningful.

I think your best bet to avoid all these issues is to pass a dummy recognizable value in the T4MVC call, and then replace it by what you want on the client using JS.

Note that this is not really T4MVC specific. The same would be true when using straight MVC.

Dec 7, 2012 at 11:19 PM

Note, in the route shaping - I am never passing a parameters...but yes, it would be more defined as 'convention based' default...


However, your best bet does not work.  If I do this:

MVC.MyController.MyAction(3), the url generated on client side would be (perhaps)


That is very difficult to take that and replace what I want on the client side!

This is T4MVC specific, because T4MVC is helping me eliminate magic strings!

Note, I could easily make the overload I am suggesting as it would be easy...but as you does assume a certain convention....

What would be nice is "/{controller}/{Id}/{action}" is the route....what would be nice would be to get something like this back in javascript:

"/mycontroller/{id}/myaction"   and that could be reliably replaced...humm..I need to think about could work really....


Dec 7, 2012 at 11:32 PM

Well, try MVC.MyController.MyAction(3141592654) and you might have more luck. But sure, it's a hack! It's easier to make it work well when you're dealing with a string, where we can pass any token you want. Actually, can't you write MVC.MyController.MyAction().AddRouteValue("myParam", "[TOKEN]")) and then replace that?

But I still think it's interesting to first answer the question: if you were not using T4MVC, and you wanted to do this without making invalid route shape assumptions, what would you write? My only thought would be to use the exact same token replacement approach.

Dec 8, 2012 at 12:44 AM
Edited Dec 8, 2012 at 12:45 AM

So, as you know we have bounced stuff off each other for years on this T4MVC and resulted in some nice enhancements.  I think I have another one...

Given whatever route shape you want and whatever tokens you have, we need a javascript url.  So here goes:

You define a route like this:

"/{controller}/{action}/{myParam}/{someId}"  - and that could be in an area and/or could have constraints on it, etc.

Then you call Url.JavaScriptReplacableUrl(MVC.MyController.MyAction()) you will get the following back:

"/MyController/MyAction/{myParam}/{someId}" and then you can just use standard javascript to find and replace the tokens!

I am not sold on the extension method name ;-) but I guess it works.  The code that makes this work actually does a manual query of the routing tables to locate the defined route for this.  Note, normally the Url.Action() works, but in cases where we have parameters that are constrained and such - we of course WILL have a route defined for that specific controller/action, this will find it.  (Note, I have begun to use and really like it - this works well with that since you are able to more easily constrain and make routes).  

I think this could work really well - and if it cannot find a route match could default to exactly what Url.Action() does now.

What do you think?


--BTW,  MVC.MyController.MyAction().AddRouteValue("myParam", "[TOKEN]"))  will not work because the param has an int constraint on it  - so will not match!!  ----

Dec 8, 2012 at 1:23 AM

Yes, if you can make it work, that sounds interesting. But I'm worried that this can be hard to do. I would go as far as arguing that in the general case, it's impossible to do! :) The reason is that you cannot reliably locate the correct route unless you know the full set of parameters. So with MVC.MyController.MyAction(), you can't always find the right route, because passing a foo param to that vs a bar param might trigger a different route selection.

That being said, I suspect it's possible to make it work so that it is reliable enough in common scenarios, and maybe we can call that good enough, since it's new optional functionality that won't break existing apps. So have a go at it and see how things go!

Dec 8, 2012 at 1:48 AM

I would agree you cannot locate the correct route reliably without all the parameters....however in this case the problem space says we will not have the parameters and will construct the correct URL based on controller/action/area 'base' url.   There are some cases where even this would not work, depending on how complex your routing was (for the same area/controller/action - but then again, it will just pick the first match - so depending again on route precedence....

I have built the extension and it does work in my initial testing of my scenarios.  Here is example of its use.

//get the base url we will be using...

var url="'@Url.JavaScriptReplacableUrl(MVC.MyArea.MyController.MyAction())"';
//on your post/get/calculation - do a simple replace...
var swappedParamsUrl = url.replace('{" + MVC.MyArea.MyController.MyActionParams.someId+ "}', 112);

Note, no magic strings - even captured the parameter name in there in case anything changes.  I am liking this solution - especially considering it is about 25 lines of code.  :-)

Feb 27, 2014 at 5:52 PM
Wayne, see this issue where someone is having problems with it when using areas.
Feb 27, 2014 at 7:59 PM
Wayne and David – I'm looking into this now. Thanks for the help so far David.
Feb 27, 2014 at 8:16 PM
Wayne and David – see my latest comment on the issue. I found a workaround and it might be possible to incorporate it into the JavaScriptReplacableUrl method.
Feb 27, 2014 at 8:25 PM
Thanks Kenny. I'll let Wayne comment as tbh I haven't used this method myself.
Feb 27, 2014 at 8:32 PM
davidebbo wrote:
Thanks Kenny. I'll let Wayne comment as tbh I haven't used this method myself.
I fail to see why adding the "root folder of website" to what JavaScriptReplacableUrl returns would change the output. My only concern is if you were in say...area 'testsite' and used a javascript replacable url from 'mysite'...would it still work correctly.

I have no problem with the fix, I just wish I knew why what you did seemed to work?
Feb 27, 2014 at 10:00 PM
Wayne – in the original description in the issue, the URL for one action method has the 'root folder' and the other doesn't.

I just stepped thru the code and confirmed that, for whatever reason, the first action method hits the following code in the JavaScriptReplacableUrl method:
return urlHelper.RouteUrl(null, result.GetRouteValueDictionary());
The second action method does not and I'm guessing that because, in this case, the final line of the JavaScriptReplacableUrl method is executed:
return "/" + specificActionUrl;
The code that generates the value stored in specificActionUrl doesn't account for any possible root folder and URLs starting with a leading / are interpreted as being relative to the server itself, not the relevant site root folder. Wrapping that final line with Url.Content("~" + ...) just adds the site root folder if there is one.
Feb 27, 2014 at 10:58 PM
Ok, sounds good. If it works for you I have no problem changing it as it does not look like it breaks anything.
Feb 28, 2014 at 2:26 PM
Wayne, I submitted a pull request just now with the change.
Mar 3, 2014 at 1:38 AM
Darn, we have it spelled Replacable instead of Replaceable! :(

I'm going to rename it and live with the fact that 3 people in the world might hate me ;)

Kenny's fix (+ the rename) is in 3.8.0.
Mar 3, 2014 at 2:40 PM
Thanks David and Wayne!
Nov 13, 2014 at 1:40 PM
I'm having an issue where this method isn't working. Original thread: stackoverflow question:

I'm trying to use JavaScriptReplacableUrl to get a url on client side and replace parameters. My method:
virtual ActionResult Details(int id = 0, int acctJobID = 0)
javascript to test JavaScriptReplacableUrl
//output: /Distribution/Details/0/0 
//expected: /Distribution/Details/{id}/{acctJobID}
T4MVC Version 3.10.0
Nov 13, 2014 at 1:44 PM
Try it without the default values on the controller action. It looks like it might not be handling that properly?