Celui-ci m'intéressait et j'ai finalement eu la chance de l'examiner. D'autres personnes n'ont apparemment pas compris qu'il s'agissait d'un problème de recherche de la vue , pas d'un problème de routage lui-même - et c'est probablement parce que le titre de votre question indique qu'il s'agit de routage.
Dans tous les cas, comme il s'agit d'un problème lié à la vue, le seul moyen d'obtenir ce que vous voulez est de remplacer le moteur de vue par défaut . Normalement, lorsque vous faites cela, c'est dans le simple but de changer votre moteur de vue (c'est-à-dire sur Spark, NHaml, etc.). Dans ce cas, ce n'est pas la logique de création de vue que nous devons remplacer, mais les méthodes FindPartialView
and FindView
de la VirtualPathProviderViewEngine
classe.
Vous pouvez remercier vos chanceuses étoiles que ces méthodes sont en fait virtuelles, car tout le VirtualPathProviderViewEngine
reste n'est même pas accessible - c'est privé, et cela le rend très ennuyeux de remplacer la logique de recherche parce que vous devez fondamentalement réécrire la moitié du code qui est déjà été écrit si vous voulez qu'il joue bien 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, c'est d'abord créer un résumé AreaAwareViewEngine
qui dérive directement de VirtualPathProviderViewEngine
au lieu de WebFormViewEngine
. J'ai fait cela pour que si vous souhaitez créer des vues Spark à la place (ou autre), vous pouvez toujours utiliser cette classe comme type de base.
Le code ci-dessous est assez long, donc pour vous donner un résumé rapide de ce qu'il fait réellement: il vous permet de mettre un {2}
dans le format de l'emplacement, qui correspond au nom de la zone, de la même manière{1}
correspond au nom du contrôleur. C'est tout! C'est pour cela que nous avons 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;
}
}
Maintenant, comme indiqué, ce n'est pas un moteur concret, vous devez donc le créer également. Cette partie, heureusement, est beaucoup plus facile, il suffit de définir les formats par défaut et de créer 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}
entrées, où le {2}
sera mappé à celui que area
nous avons mis dans leRouteData
. J'ai laissé le MasterLocationFormats
seul, mais évidemment vous pouvez 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 enregistrez 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 = "" }
);
}
Maintenant, créez le AreaController
nous venons de référencer:
DefaultController.cs (dans ~ / Controllers /)
public class DefaultController : Controller
{
public ActionResult ActionY()
{
return View("TestView");
}
}
De toute évidence, nous avons besoin de la structure de répertoires et de la vue qui vont avec - nous allons garder cela très simple:
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é .
Pour la plupart, vous devriez pouvoir simplement prendre le BaseAreaAwareViewEngine
et AreaAwareViewEngine
et le déposer dans n'importe quel projet MVC, donc même s'il a fallu beaucoup de code pour y parvenir, vous n'avez qu'à l'écrire une fois. Après cela, il ne vous reste plus qu'à éditer quelques lignes global.asax.cs
et à créer la structure de votre site.
ActionLink
problème en ajoutant la même chosearea = "AreaZ"
au mappage d'itinéraire "par défaut" dansglobal.asax.cs
. Je ne suis pas sûr cependant; essayez-le et voyez.Voilà comment je l'ai fait. Je ne sais pas pourquoi MapRoute () ne vous permet pas de définir la zone, mais il renvoie l'objet route afin que vous puissiez continuer à apporter les modifications supplémentaires que vous souhaitez. J'utilise cela parce que j'ai un site MVC modulaire qui est vendu aux entreprises clientes et qu'ils doivent pouvoir déposer des dll dans le dossier bin pour ajouter de nouveaux modules. Je leur permets de changer le "HomeArea" dans la configuration AppSettings.
Modifier: vous pouvez également essayer cela dans votre AreaRegistration.RegisterArea pour la zone dans laquelle vous voulez que l'utilisateur se rende par défaut. Je ne l'ai pas testé mais AreaRegistrationContext.MapRoute fait des ensembles
route.DataTokens["area"] = this.AreaName;
pour vous.la source
même il a déjà été répondu - c'est la syntaxe courte (ASP.net 3, 4, 5):
la source
Merci à Aaron d'avoir souligné qu'il s'agissait de localiser les vues, j'ai mal compris cela.
[MISE À JOUR] Je viens de créer un projet qui envoie l'utilisateur dans une zone par défaut sans déranger aucun des codes ou des chemins de recherche:
Dans global.asax, enregistrez-vous comme d'habitude:
dans
Application_Start()
, assurez-vous d'utiliser l'ordre suivant;dans votre zone d'enregistrement, utilisez
Un exemple peut être trouvé à 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
soit la meilleure solution dans ce cas. (Manque de réputation, je ne peux pas commenter). LeWebFormsViewEngine
est sensible à la zone et contientAreaViewLocationFormats
ce qui est défini par défaut commeJe crois que vous n'adhérez pas à cette convention. Vous avez posté
comme un hack de travail, mais cela devrait être
Si vous ne voulez pas suivre la convention, cependant, vous voudrez peut-être emprunter un chemin court en dérivant du
WebFormViewEngine
(qui est fait dans MvcContrib, par exemple) où vous pouvez définir les chemins de recherche dans le constructeur, ou -a petit hacky- en spécifiant votre convention comme ceci surApplication_Start
:Cela devrait être effectué avec un peu plus de soin, bien sûr, mais je pense que cela montre l'idée. Ces champs sont
public
dansVirtualPathProviderViewEngine
dans MVC 2 RC.la source
VirtualPathProviderViewEngine
n'a pas cette propriété et n'est pas sensible à la zone. Et bien que cette question ait effectivement été posée comme concernant MVC 2, beaucoup de gens ne l'utilisent toujours pas (et ne le seront pas avant un certain temps). Donc, 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 des fonctionnalités de pré-version qui sont potentiellement sujettes à changement.RegisterAreas
aller avantRegisterRoutes
. Je me demandais pourquoi mon code a soudainement cessé de fonctionner et a remarqué ce refactor;)Je suppose que vous voulez que l'utilisateur soit redirigé vers l'
~/AreaZ
URL une fois qu'il a visité l'~/
URL. Je réaliserais au moyen du code suivant dans votre racineHomeController
.Et l'itinéraire suivant
Global.asax
.la source
Tout d'abord, quelle version de MVC2 utilisez-vous? Il y a eu des changements significatifs de preview2 à RC.
En supposant que vous utilisez le RC, je pense que la cartographie de l'itinéraire devrait avoir un aspect différent. Dans
AreaRegistration.cs
votre région, vous pouvez enregistrer une sorte d'itinéraire par défaut, par exempleLe code ci-dessus enverra l'utilisateur à
MyRouteController
notreShopArea
par défaut.L'utilisation d'une chaîne vide comme deuxième paramètre doit lever une exception, car un contrôleur doit être spécifié.
Bien sûr, vous devrez changer la route par défaut
Global.asax
pour qu'elle n'interfère pas avec cette route par défaut, par exemple en utilisant un préfixe pour le site principal.Voir également ce fil et la réponse de Haack: Ordre des routes d'enregistrement de zone MVC 2
J'espère que cela t'aides.
la source
L'ajout de ce qui suit à mon Application_Start fonctionne pour moi, même si je ne suis pas sûr que vous ayez ce paramètre dans RC:
la source
Voici ce que j'ai fait pour que cela fonctionne:
Dans le contrôleur, j'ai ajouté le code suivant:
Dans mon RouterConfig.cs, j'ai ajouté ce qui suit:
L'astuce derrière tout cela est que j'ai créé un constructeur par défaut qui sera toujours le contrôleur de démarrage à chaque fois que mon application démarre. Lorsqu'il atteint ce contrôleur par défaut, il sera redirigé vers n'importe quel contrôleur que je spécifie dans l'action d'index par défaut. Ce qui dans mon cas est
.
la source
Avez-vous essayé cela?
la source
La localisation des différents blocs de construction est effectuée dans le cycle de vie de la demande. L'une des premières étapes du cycle de vie de la demande ASP.NET MVC consiste à mapper l'URL demandée à la méthode d'action du contrôleur appropriée. Ce processus est appelé routage. Une route par défaut est initialisée dans le fichier Global.asax et décrit au framework ASP.NET MVC comment gérer une demande. Double-cliquer sur le fichier Global.asax dans le projet MvcApplication1 affichera le code suivant:
Dans le gestionnaire d'événements Application_Start (), qui est déclenché chaque fois que l'application est compilée ou que le serveur Web est redémarré, une table de routage est enregistrée. La route par défaut est nommée Default et répond à une URL sous la forme http://www.example.com/ {controller} / {action} / {id}. Les variables entre {et} sont remplies avec les valeurs réelles de l'URL de la requête ou avec les valeurs par défaut si aucun remplacement n'est présent dans l'URL. Cette route par défaut sera mappée au contrôleur Home et à la méthode d'action Index, selon les paramètres de routage par défaut. Nous n'aurons aucune autre action avec cette carte de routage.
Par défaut, toutes les URL possibles peuvent être mappées via cette route par défaut. Il est également possible de créer nos propres itinéraires. Par exemple, mappons l'URL http://www.example.com/Employee/Maarten au contrôleur Employee, à l'action Afficher et au paramètre firstname. L'extrait de code suivant peut être inséré dans le fichier Global.asax que nous venons d'ouvrir. Étant donné que l'infrastructure ASP.NET MVC utilise la première route correspondante, cet extrait de code doit être inséré au-dessus de la route par défaut; sinon, l'itinéraire ne sera jamais utilisé.
Maintenant, ajoutons les composants nécessaires pour cette route. Tout d'abord, créez une classe nommée EmployeeController dans le dossier Controllers. Vous pouvez le faire en ajoutant un nouvel élément au projet et en sélectionnant le modèle MVC Controller Class situé sous le site Web | Catégorie MVC. Supprimez la méthode d'action Index et remplacez-la par une méthode ou une action nommée Show. Cette méthode accepte un paramètre firstname et transmet les données dans le dictionnaire ViewData. Ce dictionnaire sera utilisé par la vue pour afficher les données.
La classe EmployeeController transmettra un objet Employee à la vue. Cette classe d'employé doit être ajoutée dans le dossier Modèles (cliquez avec le bouton droit sur ce dossier, puis sélectionnez Ajouter | Classe dans le menu contextuel). Voici le code de la classe Employee:
la source
Eh bien, même si la création d'un moteur de vue personnalisé peut fonctionner pour cela, vous pouvez toujours avoir une alternative:
À votre santé!
la source
La solution acceptée à cette question est, bien que correcte pour résumer comment créer un moteur de vue personnalisé, ne répond pas correctement à la question. Le problème ici est que Pino spécifie incorrectement son itinéraire par défaut . En particulier, sa définition de «zone» est incorrecte. "Area" est vérifié via la collection DataTokens et doit être ajouté comme tel:
La "zone" spécifiée dans l'objet par défaut sera ignorée . Le code ci-dessus crée une route par défaut, qui intercepte les demandes adressées à la racine de votre site, puis appelle le contrôleur par défaut, l'action d'indexation dans la zone d'administration. Veuillez également noter que la clé «Namespaces» est ajoutée aux DataTokens, ceci n'est requis que si vous avez plusieurs contrôleurs avec le même nom. Cette solution est vérifiée avec Mvc2 et Mvc3 .NET 3.5 / 4.0
la source
ummm, je ne sais pas pourquoi toute cette programmation, je pense que le problème d'origine se résout facilement en spécifiant cette route par défaut ...
la source