122 votes

Comment définir une route par défaut (vers une zone) en MVC

Cette question a déjà été posée mais il n'y a pas de solution solide. Donc, pour mon bien et celui des autres qui pourraient trouver cela utile.

En MVC2 (ASP.NET), je veux que lorsque quelqu'un navigue sur le site Web, une zone par défaut soit spécifiée. Ainsi, la navigation sur mon site devrait vous envoyer vers ControllerX ActionY dans AreaZ.

En utilisant la route suivante dans le Global.asax

routes.MapRoute(
                "Area",
                "",
                new { area = "AreaZ", controller = "ControllerX ", action = "ActionY " }
            );

Maintenant, cela fonctionne, car il essaie de servir la bonne page. Cependant, MVC recherche la vue dans la racine du site et non dans le dossier Area.

Y a-t-il un moyen de résoudre ce problème ?

EDIT

Il existe une "solution" qui consiste à renvoyer le chemin complet de la vue dans ControllerX et ActionY. C'est un peu compliqué, mais cela fonctionne. Cependant, j'espère qu'il existe une meilleure solution.

         public ActionResult ActionY()
        {
            return View("~/Areas/AreaZ/views/ActionY.aspx");
        }

Edit :

Cela devient également un problème lorsqu'on a un ActionLink HTML de la page. Si la zone n'est pas définie, le lien d'action est édité en blanc.

Tout cela est-il voulu ou un défaut ?

100voto

Chris Alderson Points 591

Voici comment j'ai procédé. Je ne sais pas pourquoi MapRoute() ne vous permet pas de définir la zone, mais il renvoie l'objet route pour que vous puissiez continuer à faire les modifications que vous souhaitez. J'utilise cette méthode parce que j'ai un site MVC modulaire qui est vendu à des entreprises et celles-ci doivent pouvoir déposer des dll dans le dossier bin pour ajouter de nouveaux modules. Je leur permets de modifier le "HomeArea" dans la configuration AppSettings.

var route = routes.MapRoute(
                "Home_Default", 
                "", 
                new {controller = "Home", action = "index" },
                new[] { "IPC.Web.Core.Controllers" }
               );
route.DataTokens["area"] = area;

Edit : Vous pouvez aussi essayer ceci dans votre AreaRegistration.RegisterArea pour la zone où vous voulez que l'utilisateur aille par défaut. Je ne l'ai pas testé, mais AreaRegistrationContext.MapRoute fixe des paramètres. route.DataTokens["area"] = this.AreaName; pour vous.

context.MapRoute(
                    "Home_Default", 
                    "", 
                    new {controller = "Home", action = "index" },
                    new[] { "IPC.Web.Core.Controllers" }
                   );

0 votes

Ça marche. Attention au nouveau fichier web.config, il peut écraser vos anciennes configurations globales.

100voto

Aaronaught Points 73049

Celui-ci m'a intéressé, et j'ai finalement eu l'occasion de m'y intéresser. Apparemment, d'autres personnes n'ont pas compris qu'il s'agit d'un problème de trouver la vue pas un problème avec le routage lui-même - et c'est probablement parce que le titre de votre question indique qu'il s'agit de routage.

Quoi qu'il en soit, comme il s'agit d'un problème lié à la vue, la seule façon d'obtenir ce que vous voulez est de remplacer le moteur d'affichage par défaut . Normalement, lorsque vous faites cela, c'est dans le simple but de changer votre moteur de vue (c'est-à-dire vers Spark, NHaml, etc.). Dans ce cas, ce n'est pas la logique de création de la vue que nous devons remplacer, mais la logique de création de la vue. FindPartialView y FindView dans les VirtualPathProviderViewEngine classe.

Vous pouvez remercier votre bonne étoile que ces méthodes soient en fait virtuelles, parce que tout le reste du programme VirtualPathProviderViewEngine n'est même pas accessible - c'est privé, et ça le rend muy ennuyeux de remplacer la logique de recherche parce que vous devez réécrire la moitié du code déjà écrit si vous voulez qu'il soit compatible avec le cache de localisation et les formats de localisation. Après quelques recherches dans Reflector, j'ai finalement réussi à trouver une solution fonctionnelle.

Ce que j'ai fait ici est de créer d'abord une abstraction AreaAwareViewEngine qui dérive directement de VirtualPathProviderViewEngine au lieu de WebFormViewEngine . Je l'ai fait pour que si vous voulez créer des vues Spark à la place (ou autre), vous puissiez toujours utiliser cette classe comme type de base.

Le code ci-dessous est assez long, alors pour vous donner un résumé rapide de ce qu'il fait réellement : il vous permet de mettre un fichier de type {2} dans le format de l'emplacement, qui correspond au nom de la zone, de la même manière que {1} correspond au nom du contrôleur. Voilà, c'est fait ! C'est pour ça qu'on a dû écrire tout ce code :

BaseAreaAwareViewEngine.cs

public abstract class BaseAreaAwareViewEngine : VirtualPathProviderViewEngine
{
    private static readonly string[] EmptyLocations = { };

    public override ViewEngineResult FindView(
        ControllerContext controllerContext, string viewName,
        string masterName, bool useCache)
    {
        if (controllerContext == null)
        {
            throw new ArgumentNullException("controllerContext");
        }
        if (string.IsNullOrEmpty(viewName))
        {
            throw new ArgumentNullException(viewName,
                "Value cannot be null or empty.");
        }

        string area = getArea(controllerContext);
        return FindAreaView(controllerContext, area, viewName,
            masterName, useCache);
    }

    public override ViewEngineResult FindPartialView(
        ControllerContext controllerContext, string partialViewName,
        bool useCache)
    {
        if (controllerContext == null)
        {
            throw new ArgumentNullException("controllerContext");
        }
        if (string.IsNullOrEmpty(partialViewName))
        {
            throw new ArgumentNullException(partialViewName,
                "Value cannot be null or empty.");
        }

        string area = getArea(controllerContext);
        return FindAreaPartialView(controllerContext, area,
            partialViewName, useCache);
    }

    protected virtual ViewEngineResult FindAreaView(
        ControllerContext controllerContext, string areaName, string viewName,
        string masterName, bool useCache)
    {
        string controllerName =
            controllerContext.RouteData.GetRequiredString("controller");
        string[] searchedViewPaths;
        string viewPath = GetPath(controllerContext, ViewLocationFormats,
            "ViewLocationFormats", viewName, controllerName, areaName, "View",
            useCache, out searchedViewPaths);
        string[] searchedMasterPaths;
        string masterPath = GetPath(controllerContext, MasterLocationFormats,
            "MasterLocationFormats", masterName, controllerName, areaName,
            "Master", useCache, out searchedMasterPaths);
        if (!string.IsNullOrEmpty(viewPath) &&
            (!string.IsNullOrEmpty(masterPath) || 
              string.IsNullOrEmpty(masterName)))
        {
            return new ViewEngineResult(CreateView(controllerContext, viewPath,
                masterPath), this);
        }
        return new ViewEngineResult(
            searchedViewPaths.Union<string>(searchedMasterPaths));
    }

    protected virtual ViewEngineResult FindAreaPartialView(
        ControllerContext controllerContext, string areaName,
        string viewName, bool useCache)
    {
        string controllerName =
            controllerContext.RouteData.GetRequiredString("controller");
        string[] searchedViewPaths;
        string partialViewPath = GetPath(controllerContext,
            ViewLocationFormats, "PartialViewLocationFormats", viewName,
            controllerName, areaName, "Partial", useCache,
            out searchedViewPaths);
        if (!string.IsNullOrEmpty(partialViewPath))
        {
            return new ViewEngineResult(CreatePartialView(controllerContext,
                partialViewPath), this);
        }
        return new ViewEngineResult(searchedViewPaths);
    }

    protected string CreateCacheKey(string prefix, string name,
        string controller, string area)
    {
        return string.Format(CultureInfo.InvariantCulture,
            ":ViewCacheEntry:{0}:{1}:{2}:{3}:{4}:",
            base.GetType().AssemblyQualifiedName,
            prefix, name, controller, area);
    }

    protected string GetPath(ControllerContext controllerContext,
        string[] locations, string locationsPropertyName, string name,
        string controllerName, string areaName, string cacheKeyPrefix,
        bool useCache, out string[] searchedLocations)
    {
        searchedLocations = EmptyLocations;
        if (string.IsNullOrEmpty(name))
        {
            return string.Empty;
        }
        if ((locations == null) || (locations.Length == 0))
        {
            throw new InvalidOperationException(string.Format("The property " +
                "'{0}' cannot be null or empty.", locationsPropertyName));
        }
        bool isSpecificPath = IsSpecificPath(name);
        string key = CreateCacheKey(cacheKeyPrefix, name,
            isSpecificPath ? string.Empty : controllerName,
            isSpecificPath ? string.Empty : areaName);
        if (useCache)
        {
            string viewLocation = ViewLocationCache.GetViewLocation(
                controllerContext.HttpContext, key);
            if (viewLocation != null)
            {
                return viewLocation;
            }
        }
        if (!isSpecificPath)
        {
            return GetPathFromGeneralName(controllerContext, locations, name,
                controllerName, areaName, key, ref searchedLocations);
        }
        return GetPathFromSpecificName(controllerContext, name, key,
            ref searchedLocations);
    }

    protected string GetPathFromGeneralName(ControllerContext controllerContext,
        string[] locations, string name, string controllerName,
        string areaName, string cacheKey, ref string[] searchedLocations)
    {
        string virtualPath = string.Empty;
        searchedLocations = new string[locations.Length];
        for (int i = 0; i < locations.Length; i++)
        {
            if (string.IsNullOrEmpty(areaName) && locations[i].Contains("{2}"))
            {
                continue;
            }
            string testPath = string.Format(CultureInfo.InvariantCulture,
                locations[i], name, controllerName, areaName);
            if (FileExists(controllerContext, testPath))
            {
                searchedLocations = EmptyLocations;
                virtualPath = testPath;
                ViewLocationCache.InsertViewLocation(
                    controllerContext.HttpContext, cacheKey, virtualPath);
                return virtualPath;
            }
            searchedLocations[i] = testPath;
        }
        return virtualPath;
    }

    protected string GetPathFromSpecificName(
        ControllerContext controllerContext, string name, string cacheKey,
        ref string[] searchedLocations)
    {
        string virtualPath = name;
        if (!FileExists(controllerContext, name))
        {
            virtualPath = string.Empty;
            searchedLocations = new string[] { name };
        }
        ViewLocationCache.InsertViewLocation(controllerContext.HttpContext,
            cacheKey, virtualPath);
        return virtualPath;
    }

    protected string getArea(ControllerContext controllerContext)
    {
        // First try to get area from a RouteValue override, like one specified in the Defaults arg to a Route.
        object areaO;
        controllerContext.RouteData.Values.TryGetValue("area", out areaO);

        // If not specified, try to get it from the Controller's namespace
        if (areaO != null)
            return (string)areaO;

        string namespa = controllerContext.Controller.GetType().Namespace;
        int areaStart = namespa.IndexOf("Areas.");
        if (areaStart == -1)
            return null;

        areaStart += 6;
        int areaEnd = namespa.IndexOf('.', areaStart + 1);
        string area = namespa.Substring(areaStart, areaEnd - areaStart);
        return area;
    }

    protected static bool IsSpecificPath(string name)
    {
        char ch = name[0];
        if (ch != '~')
        {
            return (ch == '/');
        }
        return true;
    }
}

Comme indiqué, il ne s'agit pas d'un moteur concret, vous devez donc le créer également. Cette partie, heureusement, est mucho plus facile, il ne nous reste plus qu'à définir les formats par défaut et à créer réellement les vues :

AreaAwareViewEngine.cs

public class AreaAwareViewEngine : BaseAreaAwareViewEngine
{
    public AreaAwareViewEngine()
    {
        MasterLocationFormats = new string[]
        {
            "~/Areas/{2}/Views/{1}/{0}.master",
            "~/Areas/{2}/Views/{1}/{0}.cshtml",
            "~/Areas/{2}/Views/Shared/{0}.master",
            "~/Areas/{2}/Views/Shared/{0}.cshtml",
            "~/Views/{1}/{0}.master",
            "~/Views/{1}/{0}.cshtml",
            "~/Views/Shared/{0}.master"
            "~/Views/Shared/{0}.cshtml"
        };
        ViewLocationFormats = new string[]
        {
            "~/Areas/{2}/Views/{1}/{0}.aspx",
            "~/Areas/{2}/Views/{1}/{0}.ascx",
            "~/Areas/{2}/Views/{1}/{0}.cshtml",
            "~/Areas/{2}/Views/Shared/{0}.aspx",
            "~/Areas/{2}/Views/Shared/{0}.ascx",
            "~/Areas/{2}/Views/Shared/{0}.cshtml",
            "~/Views/{1}/{0}.aspx",
            "~/Views/{1}/{0}.ascx",
            "~/Views/{1}/{0}.cshtml",
            "~/Views/Shared/{0}.aspx"
            "~/Views/Shared/{0}.ascx"
            "~/Views/Shared/{0}.cshtml"
        };
        PartialViewLocationFormats = ViewLocationFormats;
    }

    protected override IView CreatePartialView(
        ControllerContext controllerContext, string partialPath)
    {
        if (partialPath.EndsWith(".cshtml"))
            return new System.Web.Mvc.RazorView(controllerContext, partialPath, null, false, null);
        else
            return new WebFormView(controllerContext, partialPath);
    }

    protected override IView CreateView(ControllerContext controllerContext,
        string viewPath, string masterPath)
    {
        if (viewPath.EndsWith(".cshtml"))
            return new RazorView(controllerContext, viewPath, masterPath, false, null);
        else
            return new WebFormView(controllerContext, viewPath, masterPath);
    }
}

Notez que nous avons ajouté quelques entrées à la norme ViewLocationFormats . Ce sont les nouvelles {2} les entrées, où le {2} sera mis en correspondance avec le area nous avons mis dans le RouteData . J'ai laissé le MasterLocationFormats seul, mais vous pouvez évidemment changer cela si vous le souhaitez.

Modifiez maintenant votre global.asax pour enregistrer ce moteur de vue :

Global.asax.cs

protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);
    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(new AreaAwareViewEngine());
}

...et enregistrer la route par défaut :

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.MapRoute(
        "Area",
        "",
        new { area = "AreaZ", controller = "Default", action = "ActionY" }
    );
    routes.MapRoute(
        "Default",
        "{controller}/{action}/{id}",
        new { controller = "Home", action = "Index", id = "" }
    );
}

Créez maintenant le AreaController dont nous venons de parler :

DefaultController.cs (dans ~/Controllers/)

public class DefaultController : Controller
{
    public ActionResult ActionY()
    {
        return View("TestView");
    }
}

Il est évident que nous avons besoin de la structure du répertoire et de la vue qui va avec - nous allons rester très simples :

TestView.aspx (dans ~/Areas/AreaZ/Views/Default/ ou ~/Areas/AreaZ/Views/Shared/)

<%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
<h2>TestView</h2>
This is a test view in AreaZ.

Et c'est tout. Enfin, nous avons terminé .

Dans la plupart des cas, vous devriez être en mesure de simplement prendre le BaseAreaAwareViewEngine y AreaAwareViewEngine et le déposer dans n'importe quel projet MVC. Ainsi, même s'il a fallu beaucoup de code pour réaliser cette opération, vous n'aurez à l'écrire qu'une seule fois. Après cela, il suffit de modifier quelques lignes dans le fichier global.asax.cs et la création de la structure de votre site.

0 votes

Il s'agit très probablement de la meilleure solution actuelle, mais elle est loin d'être idéale. Comme ci-dessus, dès que vous ajoutez un lien d'action ou autre, le même problème se pose.

1 votes

@Pino : Je pense que vous devriez être en mesure de résoudre le problème des ActionLink en ajoutant le même area = "AreaZ" au mappage de route "par défaut" dans global.asax.cs . Je ne suis pas sûr, essayez et voyez.

0 votes

Dans MVC4, la déclaration de route "par défaut" a été déplacée de Global.asax à ~/App_Start/RouteConfig.cs/RegisterRoutes().

57voto

SeriousM Points 941

Même si la réponse a déjà été donnée - il s'agit de la syntaxe courte (ASP.net 3, 4, 5) :

routes.MapRoute("redirect all other requests", "{*url}",
    new {
        controller = "UnderConstruction",
        action = "Index"
        }).DataTokens = new RouteValueDictionary(new { area = "Shop" });

8 votes

Cela fonctionne très bien pour moi. Je n'ai pas de contrôleurs à la racine et je n'utilise que des zones. Pour MVC 4, j'ai remplacé la valeur par défaut dans RouteConfig.cs. Merci !

3 votes

J'utilise MVC4 et c'était la solution la plus simple pour moi. Elle permet à l'application d'utiliser la vue Index d'une zone particulière comme "page d'accueil" du site.

2 votes

Cette solution ne fonctionnera pas à l'avenir (à partir d'Asp.Net MVC6 et plus).

16voto

mnemosyn Points 17378

Merci à Aaron d'avoir précisé qu'il s'agit de localiser les vues, je l'avais mal compris.

[UPDATE] Je viens de créer un projet qui envoie l'utilisateur vers une zone par défaut sans modifier le code ou les chemins de recherche :

Dans global.asax, enregistrez comme d'habitude :

    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoute(
            "Default",                                              // Route name
            "{controller}/{action}/{id}",                           // URL with parameters
            new { controller = "Home", action = "Index", id = ""}  // Parameter defaults,
        );
    }

sur Application_Start() veillez à respecter l'ordre suivant ;

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        RegisterRoutes(RouteTable.Routes);
    }

dans votre enregistrement de zone, utilisez

    public override void RegisterArea(AreaRegistrationContext context)
    {
        context.MapRoute(
            "ShopArea_default",
            "{controller}/{action}/{id}",
            new { action = "Index", id = "", controller = "MyRoute" },
            new { controller = "MyRoute" }
        );
    }

Un exemple peut être trouvé à l'adresse suivante http://www.emphess.net/2010/01/31/areas-routes-and-defaults-in-mvc-2-rc/

J'espère vraiment que c'est ce que vous demandiez...

////

Je ne pense pas qu'écrire un pseudo ViewEngine est la meilleure solution dans ce cas. (Faute de réputation, je ne peux pas me prononcer). Le site WebFormsViewEngine est conscient de la zone et contient AreaViewLocationFormats qui est défini par défaut comme

AreaViewLocationFormats = new[] {
        "~/Areas/{2}/Views/{1}/{0}.aspx",
        "~/Areas/{2}/Views/{1}/{0}.ascx",
        "~/Areas/{2}/Views/Shared/{0}.aspx",
        "~/Areas/{2}/Views/Shared/{0}.ascx",
    };

Je crois que vous n'adhérez pas à cette convention. Vous avez posté

public ActionResult ActionY() 
{ 
    return View("~/Areas/AreaZ/views/ActionY.aspx"); 
} 

comme un hack de travail, mais cela devrait être

   return View("~/Areas/AreaZ/views/ControllerX/ActionY.aspx"); 

Cependant, si vous ne souhaitez pas suivre la convention, vous pouvez prendre un chemin plus court en dérivant de l'élément WebFormViewEngine (c'est ce qui est fait dans MvcContrib, par exemple) où vous pouvez définir les chemins de recherche dans le constructeur, ou - un peu plus compliqué - en spécifiant votre convention comme ceci dans le fichier Application_Start :

((VirtualPathProviderViewEngine)ViewEngines.Engines[0]).AreaViewLocationFormats = ...;

Cela devrait être fait avec un peu plus de soin, bien sûr, mais je pense que cela montre l'idée. Ces champs sont public en VirtualPathProviderViewEngine dans MVC 2 RC.

0 votes

Il convient de noter que cela ne s'applique qu'à la RC MVC 2 - la RC MVC 1 VirtualPathProviderViewEngine ne possède pas cette propriété et n'est pas sensible à la superficie. Et bien que cette question ait été formulée comme concernant MVC 2, de nombreuses personnes ne l'utilisent toujours pas (et ne l'utiliseront pas avant un certain temps). Ainsi, votre réponse est plus facile pour la question spécifique, mais la mienne est la seule qui fonctionnera pour les utilisateurs de MVC1 qui tombent sur cette question. J'aime fournir des réponses qui ne dépendent pas d'une fonctionnalité de pré-version qui est potentiellement sujette à changement.

0 votes

Il ne s'agit pas non plus d'un "pseudo-moteur de vues" - les classes du moteur de vues ont été délibérément conçues pour être extensibles afin que différents types de vues puissent être utilisés.

0 votes

Je ne voulais pas vous insulter, je suis désolé. Il s'agit d'un "pseudo" dans la mesure où il ne modifie pas de manière significative la façon dont les vues sont traitées, mais remplace simplement certaines valeurs.

6voto

Anthony Serdyukov Points 1593

Je suppose que vous voulez que l'utilisateur soit redirigé vers ~/AreaZ URL une fois qu'il a visité ~/ URL. Je réaliserais au moyen du code suivant dans votre Racine HomeController .

public class HomeController
{
    public ActionResult Index()
    {
        return RedirectToAction("ActionY", "ControllerX", new { Area = "AreaZ" });
    }
}

Et la route suivante dans Global.asax .

routes.MapRoute(
    "Redirection to AreaZ",
    String.Empty,
    new { controller = "Home ", action = "Index" }
);

0 votes

Cela fonctionne, mais l'URL change sur le navigateur de l'utilisateur. Ce n'est pas vraiment l'idéal.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X