【问题标题】:Magic strings in ASP.NET MVCASP.NET MVC 中的魔术字符串
【发布时间】:2011-12-03 21:16:21
【问题描述】:

我有桌面软件开发背景,正在开始学习 ASP.NET MVC。

在我的默认 HomeController 中,我有 Index 操作,其代码如下所示:

if (!Request.IsAuthenticated)
    return RedirectToAction("Login", "Account");

换句话说,将用户重定向到“/account/login”。然后,AccountController.Login 操作将处理用户并在他成功登录后将他发送回 HomeController。

我觉得这段代码有异味,可能只是因为我习惯于在桌面软件中以不同的方式做事。如果我将登录操作的名称更改为“LogOn”会怎样?如果我完全删除 AccountController 并用其他东西替换它会怎样?我将引入一个新错误,但不会出现编译器错误,而且我的单元测试也可能无法捕捉到它。由于我使用字符串来指定控制器和操作名称,因此重构和重新设计更有可能在各处破坏代码。

我想要的是这样的:

if (!Request.IsAuthenticated)
    return RedirectToAction(() => AccountController.Login);

但是我不确定这是否可能,或者这是否是最好的方法。

是我傻了,还是其他人也有同样的问题?你会怎么做才能绕过它?

【问题讨论】:

    标签: asp.net-mvc refactoring magic-string


    【解决方案1】:

    我认为您正在寻找的是 T4MVC 存在的原因 - 它删除了所有与控制器和操作相关的“魔术字符串”,并将它们替换为类和属性。

    使用 T4MVC,这

    if (!Request.IsAuthenticated)
        return RedirectToAction("Login", "Account");
    

    变成这样

    if (!Request.IsAuthenticated)
        return RedirectToAction(MVC.Account.Login());
    

    可以在 T4MVC 设置中设置一个标志,强制它在每次构建时运行模板,从而在可能发生更改时提前警告您。

    虽然不是您所要求的,但您可以考虑使用 AuthorizeAttribute 来消除检查请求是否在控制器操作中经过身份验证的需要。

    这个

    public class HomeController : Controller
    {
        public ActionResult Index() 
        {
            if (!Request.IsAuthenticated)
                return RedirectToAction("Login", "Account"); 
    
            // .... carry on
        }
    }
    

    变成

    public class HomeController : Controller
    {
        [Authorize]
        public ActionResult Index() 
        {
            // .... carry on
        }
    }
    

    然后在web.config中,设置url指向账号登录URL

    <authentication mode="Forms">
       <forms loginUrl="account/login" timeout="30" />
    </authentication> 
    

    当然,如果您的控制器和操作发生变化(类似于您最初的投诉),这不会给您带来任何安全性,但您始终可以设置路由以将所选 URL 引导到正确的控制器和操作并使用 T4MVC在路由中生成类,如果事情发生变化,会为您提供一些编译时警告。

    【讨论】:

    • 哇,这太有用了。我希望/希望他们将来将其直接集成到 MVC 中。我不明白这些天使用这么多魔术字符串是如何被接受的。
    【解决方案2】:

    在 C# 6 中,您可以利用 nameof 并轻松重构其中许多魔术字符串。

    ... = new SelectList(context.Set<User>(), nameof(User.UserId), nameof(User.UserName));
    
    ...
    return RedirectToAction(nameof(Index));
    

    【讨论】:

      【解决方案3】:

      您可以编写自己的自定义扩展方法来帮助您避免使用魔法字符串。例如你可以在这里看到我的实现: https://github.com/ivaylokenov/ASP.NET-MVC-Lambda-Expression-Helpers 请记住,这会增加一点运行时的性能开销,您可以通过缓存所有链接来解决。如果你想编译时检查 T4MVC 是你的解决方案:http://t4mvc.codeplex.com/

      【讨论】:

        【解决方案4】:

        我知道这不是一个真正的答案,但使用像 Resharper 这样的工具也可以帮助您解决这个问题。 Resharper 会跟踪控制器和操作,并在某些本应是控制器的魔术字符串不存在时发出警告。但它仅适用于 RedirectToAction 或 ActionLink 等标准方法。

        编辑:显然您可以添加注释以使其与您的自定义扩展方法一起使用。见here

        【讨论】:

          【解决方案5】:

          Russ 的回答在金钱上是正确的,但它并没有真正解决您的问题.. 这实际上是“为什么 MVC 中有魔术字符串?”。

          在 MVC 的早期版本中,它实际上是完全不同的。他们没有使用魔术字符串并且有更多基于类型的方法。但是,由于多种原因,这种情况发生了变化。我忘记了细节,但是这种奇怪的回归到非类型安全方法是有逻辑的原因的。

          我似乎记得这可能与 MVC 在多个区域中搜索匹配约定的方式有关,而使用字符串比类型对象更容易实现这一点。这对于视图和局部尤其如此。

          也许记得细节的人可以插话。

          【讨论】:

            【解决方案6】:

            作为 T4MVC 的替代方案,我编写了一个小助手,它允许您使用几乎完全符合您建议的语法,而无需自动生成代码:

            <a href="@(Url.To<MyController>().MyAction("foo", 42))">link</a>
            

            在控制器中:

            return Redirect(Url.To<MyController>().MyAction("foo", 42).ToString());
            

            我的解决方案在运行时执行 T4MVC 在构建时执行的操作。

            有一个NuGet package 和一个GitHub project

            【讨论】:

              【解决方案7】:

              如果您真的只关心重定向到动作和控制器,C# 6 nameof 运算符真的很有帮助 - 直到您需要在控制器之间重定向。我最终编写了一个实用方法来获取控制器名称:

              public class Utilities
              {
                  public static string ControllerName<T>() where T : Controller
                  {
                      var name = typeof(T).Name;
                      return name.Substring(0, Math.Max(name.LastIndexOf(nameof(Controller), 
                                                        StringComparison.CurrentCultureIgnoreCase), 0));
                  }
              }
              

              你可以这样使用它:

              public ActionResult Foo()
              {
                  return RedirectToAction(nameof(HomeController.Index), 
                                          Utilities.ControllerName<HomeController>());
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2013-02-08
                • 1970-01-01
                • 1970-01-01
                • 2012-06-21
                • 1970-01-01
                • 2011-09-30
                • 2011-01-15
                • 2014-10-29
                相关资源
                最近更新 更多