【问题标题】:MapMvcAttributeRoutes: This method cannot be called during the application's pre-start initialization phaseMapMvcAttributeRoutes:在应用程序的预启动初始化阶段不能调用该方法
【发布时间】:2013-11-30 20:20:21
【问题描述】:

我在一个使用 ASP MVC V5 和属性路由的解决方案中的测试项目中有一个非常简单的测试。属性路由和MapMvcAttributeRoutes 方法是 ASP MVC 5 的一部分。

[Test]
public void HasRoutesInTable()
{
    var routes = new RouteCollection();
    routes.MapMvcAttributeRoutes();
    Assert.That(routes.Count, Is.GreaterThan(0));
}

这会导致:

System.InvalidOperationException : 
This method cannot be called during the applications pre-start initialization phase.

此错误消息的大多数答案都涉及在web.config 文件中配置成员资格提供程序。该项目既没有会员提供者也没有web.config 文件,因此错误似乎是由于其他原因而发生的。如何将代码移出这种“预启动”状态以便测试可以运行?

ApiController 上的属性的等效代码在调用 HttpConfiguration.EnsureInitialized() 后可以正常工作。

【问题讨论】:

    标签: asp.net-mvc unit-testing asp.net-mvc-5 attributerouting


    【解决方案1】:

    我最近将我的项目升级到 ASP.NET MVC 5 并遇到了完全相同的问题。当使用dotPeek 对其进行调查时,我发现有一个内部MapMvcAttributeRoutes 扩展方法有一个IEnumerable<Type> 作为参数,它需要一个控制器类型列表。我创建了一个新的扩展方法,它使用反射并允许我测试基于属性的路由:

    public static class RouteCollectionExtensions
    {
        public static void MapMvcAttributeRoutesForTesting(this RouteCollection routes)
        {
            var controllers = (from t in typeof(HomeController).Assembly.GetExportedTypes()
                                where
                                    t != null &&
                                    t.IsPublic &&
                                    t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) &&
                                    !t.IsAbstract &&
                                    typeof(IController).IsAssignableFrom(t)
                                select t).ToList();
    
            var mapMvcAttributeRoutesMethod = typeof(RouteCollectionAttributeRoutingExtensions)
                .GetMethod(
                    "MapMvcAttributeRoutes",
                    BindingFlags.NonPublic | BindingFlags.Static,
                    null,
                    new Type[] { typeof(RouteCollection), typeof(IEnumerable<Type>) },
                    null);
    
            mapMvcAttributeRoutesMethod.Invoke(null, new object[] { routes, controllers });
        }
    }
    

    这是我的使用方法:

    public class HomeControllerRouteTests
    {
        [Fact]
        public void RequestTo_Root_ShouldMapTo_HomeIndex()
        {
            // Arrange
            var routes = new RouteCollection();
    
            // Act - registers traditional routes and the new attribute-defined routes
            RouteConfig.RegisterRoutes(routes);
            routes.MapMvcAttributeRoutesForTesting();
    
            // Assert - uses MvcRouteTester to test specific routes
            routes.ShouldMap("~/").To<HomeController>(x => x.Index());
        }
    }
    

    现在的一个问题是,在 RouteConfig.RegisterRoutes(route) 内部我无法调用 routes.MapMvcAttributeRoutes(),因此我将该调用移到了我的 Global.asax 文件中。

    另一个问题是这个解决方案可能很脆弱,因为RouteCollectionAttributeRoutingExtensions 中的上述方法是内部的,可以随时删除。一种主动的方法是检查mapMvcAttributeRoutesMethod 变量是否为空,如果是则提供适当的错误/异常消息。

    注意:这仅适用于 ASP.NET MVC 5.0。 ASP.NET MVC 5.1 中的属性路由发生了重大变化,mapMvcAttributeRoutesMethod 方法已移至内部类。

    【讨论】:

    • 这段代码已经被改编并合并到MvcRouteTester中,这里在WebRouteTestMapper类中:github.com/AnthonySteele/MvcRouteTester/blob/mvc5/src/…
    • 我注意到有关此解决方案脆弱的评论。话虽如此,这对我在 MVC 5.1.2 上不起作用。 mapMvcAttributeRoutesMethod 始终为空。手动检查组件时我也找不到该方法。这个扩展方法应该存在于RouteCollectionAttributeRoutingExtensions 中的System.Web.Mvc 中吗?
    • @MEMark:不幸的是,这只适用于 MVC 5.0;在 5.1 中被移到了不同​​的类。可以找到处理 5.1 解决方案的 SO 问题here
    【解决方案2】:

    ASP.NET MVC 5.1 中,此功能被移至其自己的类中,称为 AttributeRoutingMapper

    (这就是为什么人们不应该依赖内部类中的代码破解)

    但这是 5.1(及更高版本?)的解决方法:

    public static void MapMvcAttributeRoutes(this RouteCollection routeCollection, Assembly controllerAssembly)
    {
        var controllerTypes = (from type in controllerAssembly.GetExportedTypes()
                                where
                                    type != null && type.IsPublic
                                    && type.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase)
                                    && !type.IsAbstract && typeof(IController).IsAssignableFrom(type)
                                select type).ToList();
    
        var attributeRoutingAssembly = typeof(RouteCollectionAttributeRoutingExtensions).Assembly;
        var attributeRoutingMapperType =
            attributeRoutingAssembly.GetType("System.Web.Mvc.Routing.AttributeRoutingMapper");
    
        var mapAttributeRoutesMethod = attributeRoutingMapperType.GetMethod(
            "MapAttributeRoutes",
            BindingFlags.Public | BindingFlags.Static,
            null,
            new[] { typeof(RouteCollection), typeof(IEnumerable<Type>) },
            null);
    
        mapAttributeRoutesMethod.Invoke(null, new object[] { routeCollection, controllerTypes });
    }
    

    【讨论】:

    • 是的,它保证会在下一个版本中改变,很快就会出来,因为路由在 vNext 中得到了受欢迎的重写。这就是为什么“内部类中的代码破解”远不如前门的原因。不知道 MS 下次是否会足够明智地实际建造前门。他们直到现在才这样做,这是唯一的选择。
    • 这样做的缺点是我无法让它识别区域。因此,admin 是一个区域的路径“/admin/service/accounts”被映射到默认路由,因此:admin = controller, service = action
    【解决方案3】:

    嗯,它真的很丑,我不确定它是否值得测试复杂性,但这里是你可以在不修改 RouteConfig.Register 代码的情况下做到这一点的方法:

    [TestClass]
    public class MyTestClass
    {
        [TestMethod]
        public void MyTestMethod()
        {
            // Move all files needed for this test into a subdirectory named bin.
            Directory.CreateDirectory("bin");
    
            foreach (var file in Directory.EnumerateFiles("."))
            {
                File.Copy(file, "bin\\" + file, overwrite: true);
            }
    
            // Create a new ASP.NET host for this directory (with all the binaries under the bin subdirectory); get a Remoting proxy to that app domain.
            RouteProxy proxy = (RouteProxy)ApplicationHost.CreateApplicationHost(typeof(RouteProxy), "/", Environment.CurrentDirectory);
    
            // Call into the other app domain to run route registration and get back the route count.
            int count = proxy.RegisterRoutesAndGetCount();
    
            Assert.IsTrue(count > 0);
        }
    
        private class RouteProxy : MarshalByRefObject
        {
            public int RegisterRoutesAndGetCount()
            {
                RouteCollection routes = new RouteCollection();
    
                RouteConfig.RegisterRoutes(routes); // or just call routes.MapMvcAttributeRoutes() if that's what you want, though I'm not sure why you'd re-test the framework code.
    
                return routes.Count;
            }
        }
    }
    

    映射属性路由需要找到您用来获取其属性的所有控制器,这需要访问构建管理器,这显然只适用于为 ASP.NET 创建的应用程序域。

    【讨论】:

    • 谢谢,这至少是一个答案。不过,将其扩展到一个完整的测试库看起来很复杂(而且很脆弱)(这是我正在研究的)。希望有另一种方法,否则从可测试性的角度来看,这里的设计决策很糟糕。奇怪的是,只有网络路由受到影响。 ApiRoutes 工作正常
    • 是的,不幸的是我不知道这里有任何替代方案。映射属性路由需要获取控制器列表(这样才能找到它们所有的属性路由)。理想情况下,会有一种方法来替换提供控制器的服务,但我们在这个版本中没有这样做。我提交了一个错误来跟踪这个问题 - aspnetwebstack.codeplex.com/workitem/1445
    • 感谢您的帮助!
    • 不错的选择。不幸的是,我将不得不对许多测试进行硬编码以使用独立代理,因为许多元素(例如,用于测试路由的 RouteData)未标记为可序列化。 :(
    【解决方案4】:

    你在这里测试什么?看起来您正在测试第 3 方扩展方法。您不应该使用单元测试来测试第 3 方代码。

    【讨论】:

    • 我正在测试我的测试库:github.com/AnthonySteele/MvcRouteTester/tree/mvc5,以使其能够用于测试 ASP MVC 5 网站。当您说“第 3 方扩展方法”时,您的意思是“MapMvcAttributeRoutes()”吗? - 这不是第 3 方,它是 ASP MVC v5 框架的一部分。
    • 没有在你的问题中你说你在写一个测试库。我的印象是您正在构建一个 mvc Web 应用程序 - 因此不应在您的解决方案中完成 Mvc 框架(即第 3 方)提供的测试扩展方法。
    • 控制器上的属性是真正要测试的。它们没有在这里显示。 MapMvcAttributeRoutes 就是将这些属性转化为路由表数据的方式。假定它可以正常工作,因此未在测试中。我给出了一个简化的例子来捕捉这个问题,并且基本上在一个网络应用程序上做了一个测试。我认为 Asp.Net 框架算是前两方之一。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-08
    • 1970-01-01
    • 2021-03-20
    • 1970-01-01
    • 1970-01-01
    • 2011-11-17
    相关资源
    最近更新 更多