Reputation: 43728
With the UrlHelper
in MVC3, you can build a URL for a controller and an action using strings:
@Url.Action("Index", "Somewhere")
Which will route a request to SomewhereController.Index()
.
What I would like to do, is get a URL to a controller and action, but passing the Type for the controller:
@Url.Action("Index", typeof(SomewhereController))
Is there a way to do this?
Edit / Clarification:
I realize the convention for the Controllers is that the controller name routes to a class named {Name}Controller
so I could just remove 'Controller' from the end of my Type.Name. I guess I was assuming that there was a way to override this convention with some custom routing. Though the more I look at it, I'm not sure that is possible...
Maybe MVC3 can only ever route to classes named "*Controller"? I'm combing through the MVC3 source looking for "Controller" hard coded somewhere, but haven't found the answer yet... But if it is possible to route "Somewhere" to the class SomewhereFoo
instead of SomewhereController
, then just removing "Controller" from the class name would be incorrect.
If someone can give me evidence for or against "Controller" being hard-coded into MVC3 somewhere, then I would feel more comfortable with the "Just remove Controller from the name" approach.
Upvotes: 10
Views: 4112
Reputation: 25034
Based on accepted answer:
var url = Url.RouteUrl<MyController>(c => c.MyAction(dummy,args));
or:
@using (Html.BeginForm(Url.Route<MyController>(c => c.MyAction(dummy,args))))
{
// yadda yadda
}
public static class ActionRouteHelper
{
/// <summary>
/// Given a controller/action selector, return its URL route
/// </summary>
/// <typeparam name="TController"></typeparam>
/// <param name="url">helper extended</param>
/// <param name="action">A lambda for choosing an action from the indicated controller. May need to provide dummy method arguments.</param>
/// <returns></returns>
public static string RouteUrl<TController>(this UrlHelper url, Expression<Action<TController>> action) where TController : Controller
{
return url.RouteUrl(url.Route(action));
}
/// <summary>
/// Given a controller/action selector, return its URL route
/// </summary>
/// <remarks>See inspiration from https://stackoverflow.com/a/8252301/1037948 </remarks>
/// <typeparam name="TController">the controller class</typeparam>
/// <param name="url">helper extended</param>
/// <param name="action">A lambda for choosing an action from the indicated controller. May need to provide dummy method arguments.</param>
/// <returns></returns>
public static RouteValueDictionary Route<TController>(this UrlHelper url, Expression<Action<TController>> action) where TController : Controller
{
// TODO: validate controller name for suffix, non-empty result, if that's important to you here rather than the link just not working
var controllerName = typeof(TController).Name.Replace("Controller", string.Empty);
// strip method name out of the expression, the lazy way (https://stackoverflow.com/questions/671968/retrieving-property-name-from-lambda-expression/17220748#17220748)
var actionName = action.ToString(); // results in something like "c => c.MyAction(arg1,arg2)"
var startOfMethodName = actionName.IndexOf('.')+1;
var startOfArgList = actionName.IndexOf('(');
actionName = actionName.Substring(startOfMethodName, startOfArgList - startOfMethodName);
return new RouteValueDictionary {
{ "Controller", controllerName },
{ "Action", actionName }
};
}
}
Upvotes: 0
Reputation: 532605
There is no existing extension for this but you could write your own, modeled on the ActionLink from MvcFutures. I suggest a generic method used like @Url.Action<SomewhereController>( c => c.Index )
public static UrlHelperExtensions
{
public static string Action<TController>( this UrlHelper helper, Expression<Action<T>> action ) where TController : Controller
{
var routeValues = GetRouteValuesFromExpression( action );
return helper.Action( routeValues["action"], routeValues );
}
// copied from MvcFutures
// http://aspnet.codeplex.com/SourceControl/changeset/view/72551#266392
private static RouteValueDictionary GetRouteValuesFromExpression<TController>(Expression<Action<TController>> action) where TController : Controller
{
if (action == null) {
throw new ArgumentNullException("action");
}
MethodCallExpression call = action.Body as MethodCallExpression;
if (call == null) {
throw new ArgumentException(MvcResources.ExpressionHelper_MustBeMethodCall, "action");
}
string controllerName = typeof(TController).Name;
if (!controllerName.EndsWith("Controller", StringComparison.OrdinalIgnoreCase)) {
throw new ArgumentException(MvcResources.ExpressionHelper_TargetMustEndInController, "action");
}
controllerName = controllerName.Substring(0, controllerName.Length - "Controller".Length);
if (controllerName.Length == 0) {
throw new ArgumentException(MvcResources.ExpressionHelper_CannotRouteToController, "action");
}
// TODO: How do we know that this method is even web callable?
// For now, we just let the call itself throw an exception.
var rvd = new RouteValueDictionary();
rvd.Add("Controller", controllerName);
rvd.Add("Action", call.Method.Name);
AddParameterValuesFromExpressionToDictionary(rvd, call);
return rvd;
}
}
Upvotes: 6
Reputation: 180878
There's a number of ways you could do this. If you are using a type, the easiest way I can think of is to implement a Name
property, like this:
public static string Name { get { return "Somewhere"; } }
And then call your Action like this:
@Url.Action("Index", SomewhereController.Name);
Upvotes: 1
Reputation: 14278
@Url.Action("Index", typeof(SomewhereController).Name.Replace("Controller", ""))
Upvotes: 3
Reputation: 41902
You could create a Razor helper:
@helper MyAction(string action, Type controller) {
var controllerName = typeof(controller)
.Name.Replace("Controller", "");
@Url.Action(action, controllerName);
}
Which can then be used in place of @Url.Action
:
@MyAction("ActionName", typeof(MyController))
Upvotes: 0