36

我在使用 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()

4

4 回答 4

19

我最近将我的项目升级到 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方法被移至内部类。

于 2013-11-28T14:56:17.747 回答
11

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 });
}
于 2014-06-26T13:52:52.170 回答
5

好吧,它真的很丑,我不确定它是否值得测试复杂性,但这里是你如何在不修改 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 创建的应用程序域。

于 2013-11-20T21:51:57.423 回答
-5

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

于 2013-11-18T01:24:00.450 回答