Mapmvcattributeroutestes: cette méthode ne peut pas être appelée pendant la phase d'initialisation de l'application avant le démarrage

j'ai un test très simple dans un projet de test dans une solution utilisant ASP MVC V5 et attribut routing. Attribut de routage et d' MapMvcAttributeRoutes la méthode fait partie de L'ASP MVC 5.

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

résultat:

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

la plupart des réponses à ce message d'erreur impliquent la configuration des fournisseurs de membres dans le web.config fichier. Ce projet n'a pas de fournisseurs d'adhésion ou un web.config fichier donc l'erreur semble se produire pour une autre raison. Comment déplacer le code sortir de cet état de "pré-démarrage" pour que les tests puissent fonctionner?

Le code équivalent pour les attributs sur ApiController fonctionne bien après HttpConfiguration.EnsureInitialized() est appelée.

34
demandé sur Anthony 2013-11-17 23:15:26

4 réponses

j'ai récemment amélioré mon projet pour ASP.NET MVC 5 et a connu le même problème. Lors de l'utilisation de dotPeek pour l'étudier, j'ai découvert qu'il y a unMapMvcAttributeRoutes méthode d'extension qui a un IEnumerable<Type> comme paramètre qui attend une liste de types de Controllers. J'ai créé une nouvelle méthode d'extension qui utilise la réflexion et me permet de tester mes routes basées sur les attributs:

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

Et voici comment je l'utilise:

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

Un problème est maintenant qu'à l'intérieur de RouteConfig.RegisterRoutes(route) je ne peux pas appeler routes.MapMvcAttributeRoutes() alors j'ai déplacé cet appel à mon Global.asax fichier à la place.

une autre préoccupation est que cette solution est potentiellement fragile puisque la méthode ci-dessus dans RouteCollectionAttributeRoutingExtensions est interne et peut être retiré à tout moment. Une approche proactive consisterait à vérifier si le mapMvcAttributeRoutesMethod variable est null et d'erreur/exceptionmessage si il est.

NOTE: cela ne fonctionne qu'avec ASP.NET MVC 5.0. Il y avait modifications importantes à l'attribut routage dans ASP.NET MVC 5.1 et le mapMvcAttributeRoutesMethod la méthode a été déplacée dans une classe interne.

18
répondu chartek 2014-05-05 13:32:59
ASP.NET MVC 5.1 cette fonctionnalité a été déplacé dans sa propre classeAttributeRoutingMapper.

(C'est pourquoi on ne devrait pas se fier au piratage de code dans les classes internes)

mais c'est la solution pour 5.1 (et plus?):

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
répondu Seb Nilsson 2014-06-26 13:52:52

Eh bien, c'est vraiment laid et je ne suis pas sûr que ça vaudra la complexité du test, mais voici comment vous pouvez le faire sans modifier votre RouteConfig.Code du registre:

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

cartographier les routes d'attributs doit trouver tous les contrôleurs que vous utilisez pour obtenir leurs attributs, ce qui nécessite d'accéder au gestionnaire de construction, qui ne fonctionne apparemment que dans les domaines d'applications créés pour ASP.NET.

5
répondu dmatson 2013-11-20 21:51:57

Quoi êtes-vous le test ici? On dirait que vous testez une méthode d'extension de tierce partie. Vous ne devriez pas utiliser vos tests unitaires pour tester le code d'une tierce partie.

-5
répondu ozczecho 2013-11-18 01:24:00