web-dev-qa-db-ja.com

MapMvcAttributeRoutes:このメソッドは、アプリケーションの起動前初期化フェーズでは呼び出すことができません

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_ファイルもないため、他の何らかの理由でエラーが発生しているようです。テストを実行できるように、コードをこの「開始前」の状態から移動するにはどうすればよいですか?

HttpConfiguration.EnsureInitialized()が呼び出された後、ApiControllerの属性の同等のコードは正常に動作します。

35
Anthony

最近、プロジェクトをASP.NET MVC 5にアップグレードしましたが、まったく同じ問題が発生しました。 dotPeek を使用して調査したところ、コントローラータイプのリストを期待するパラメーターとして_IEnumerable<Type>_を持つ内部MapMvcAttributeRoutes拡張メソッドがあることがわかりました。リフレクションを使用し、属性ベースのルートをテストできる新しい拡張メソッドを作成しました。

_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());
    }
}
_

ここでの1つの問題は、RouteConfig.RegisterRoutes(route)内ではroutes.MapMvcAttributeRoutes()を呼び出せないため、代わりにその呼び出しをGlobal.asaxファイルに移動したことです。

もう1つの懸念は、RouteCollectionAttributeRoutingExtensionsの上記のメソッドは内部にあり、いつでも削除できるため、このソリューションは潜在的に壊れやすいことです。予防的なアプローチは、mapMvcAttributeRoutesMethod変数がnullかどうかを確認し、nullの場合は適切なエラー/例外メッセージを提供することです。

注:これはASP.NET MVC 5.0でのみ機能します。 ASP.NET MVC 5.1では属性ルーティングに大きな変更があり、mapMvcAttributeRoutesMethodメソッドが内部クラスに移動されました。

19
Steven

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 });
}
9
Seb Nilsson

まあ、それは本当に醜く、テストの複雑さの価値があるかどうかはわかりませんが、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用に作成されたアプリドメインでのみ機能するビルドマネージャーにアクセスする必要があります。

5
dmatson