CodingWithSpike
CodingWithSpike

Reputation: 43728

ASP.NET MVC 3 : Is there a way to get a controller's string Name from its Type?

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

Answers (5)

drzaus
drzaus

Reputation: 25034

Based on accepted answer:

Usage

var url = Url.RouteUrl<MyController>(c => c.MyAction(dummy,args));

or:

@using (Html.BeginForm(Url.Route<MyController>(c => c.MyAction(dummy,args))))
{
    // yadda yadda
}

Extension Method

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

tvanfosson
tvanfosson

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

Robert Harvey
Robert Harvey

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

keithwarren7
keithwarren7

Reputation: 14278

@Url.Action("Index", typeof(SomewhereController).Name.Replace("Controller", ""))

Upvotes: 3

Chris Fulstow
Chris Fulstow

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

Related Questions