【问题标题】:ASP.NET MVC 3 : Is there a way to get a controller's string Name from its Type?ASP.NET MVC 3:有没有办法从控制器的类型中获取控制器的字符串名称?
【发布时间】:2011-11-24 04:17:07
【问题描述】:

使用 MVC3 中的 UrlHelper,您可以使用字符串为控制器和操作构建 URL:

@Url.Action("Index", "Somewhere")

这会将请求路由到SomewhereController.Index()

我想做的是获取控制器和操作的 URL,但传递控制器的类型:

@Url.Action("Index", typeof(SomewhereController))

有没有办法做到这一点?


编辑/澄清:

我意识到控制器的约定是控制器名称路由到名为 {Name}Controller 的类,因此我可以从 Type.Name 的末尾删除“控制器”。我想我假设有一种方法可以通过一些自定义路由来覆盖这个约定。虽然我看的越多,我不确定这是否可能......

也许 MVC3 只能 路由到名为“*Controller”的类?我正在梳理 MVC3 源代码,寻找硬编码的“控制器”某处,但还没有找到答案……但如果可以将“某处”路由到类SomewhereFoo 而不是SomewhereController,那么仅仅从类名中删除“Controller”是不正确的。

如果有人可以为我提供证据支持或反对“控制器”在某处被硬编码到 MVC3 中,那么我会对“只需从名称中删除控制器”方法感到更自在。

【问题讨论】:

  • 它在某处。上次我检查 ASP.NET MVC 2 源时,我找到了它。这是一个简单的字符串连接,深埋在类结构中。
  • ControllerTypeCache,由DefaultControllerFactory 使用,依赖于具有以字符串结尾的前缀的控制器,只要 Controller 会从在构造缓存的键时键入名称。缓存用于根据路由值中的值查找要使用的控制器。可能还有其他地方。我不建议违反这个约定。
  • 您是否在构建一些第 3 方组件之类的东西,为什么需要涵盖这样的边缘情况?即使有可能,我也想见见违反 {Name}Controller 约定的人,并批评他们使用基于约定的技术,然后违反约定!
  • @keithwarren7 :同意,遵守约定,但是是的,它是用于第 3 方插件(类似于 MvcSitemapProvder),所以我想涵盖所有我能做到的情况。 @tvanfosson:谢谢你的类名。我将在 Reflector 中打开它们并查看一下。我想我担心有人注入了不同的ControllerFactory 实现,然后把所有东西都炸了:)
  • @rally25rs - 你可以在aspnet.codeplex.com查看实际来源

标签: c# asp.net .net asp.net-mvc-3


【解决方案1】:

目前尚无此扩展,但您可以编写自己的扩展,以 MvcFutures 的 ActionLink 为模型。我建议使用像@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;
    }
}

【讨论】:

  • 有用的方法,谢谢!当我第一次开始使用 MVC3 时,我尝试编写其中一个来获取 Lambda,因为我不喜欢“魔术字符串”方法。虽然我从来没有真正工作过,但看起来你做到了。 +1。
  • @rally25rs 未测试 -- 我做了一个小修正,现在它是一种实际的扩展方法。
  • 将其标记为答案,因为它包含.Replace("Controller", "") 答案,并且将其进一步引入了一个不错的扩展方法。感谢大家的帮助!
  • 这正是我需要的起点;我为my answer 简化了一点(在MVC3/4 中测试);我认为您在Expression 中使用了&lt;T&gt; 而不是&lt;TController&gt;
【解决方案2】:
@Url.Action("Index", typeof(SomewhereController).Name.Replace("Controller", ""))

【讨论】:

  • 我同意,但只是想我会记下一个快速的答案。
【解决方案3】:

有多种方法可以做到这一点。如果您使用的是类型,我能想到的最简单的方法是实现 Name 属性,如下所示:

public static string Name { get { return "Somewhere"; } }

然后像这样调用你的 Action:

@Url.Action("Index", SomewhereController.Name);

【讨论】:

  • 我不喜欢的一件事是它不能承受重构,如果类名被改变,你必须更新字符串。也许更好的方法是从基本控制器类中使用 GetName 扩展方法?
【解决方案4】:

你可以创建一个 Razor 助手:

@helper MyAction(string action, Type controller) {
    var controllerName = typeof(controller)
        .Name.Replace("Controller", "");
    @Url.Action(action, controllerName);
}

然后可以用来代替@Url.Action:

@MyAction("ActionName", typeof(MyController))

【讨论】:

  • 我正在研究通过某种显式路由或某种自定义路由解析器覆盖“*Controller”命名约定的假设,因此这种方法不能 100% 工作时间。不过,在 MVC3 中可能无法实现命名的“覆盖”?我对我原来的问题进行了澄清...
【解决方案5】:

基于accepted answer:

用法

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

或:

@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 }
        };
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-03-05
    • 2011-02-12
    • 2017-06-15
    • 2020-01-04
    • 1970-01-01
    • 2012-05-09
    • 1970-01-01
    • 2012-03-23
    相关资源
    最近更新 更多