Dieser interessierte mich und ich hatte endlich die Gelegenheit, mich damit zu beschäftigen. Andere Leute haben anscheinend nicht verstanden, dass dies ein Problem beim Finden der Ansicht ist , kein Problem beim Routing selbst - und das liegt wahrscheinlich daran, dass Ihr Fragentitel darauf hinweist, dass es sich um Routing handelt.
In jedem Fall besteht die einzige Möglichkeit, das gewünschte Ergebnis zu erzielen, darin, die Standard-Ansichts-Engine zu überschreiben , da dies ein Problem im Zusammenhang mit der Ansicht ist . Wenn Sie dies tun, dient dies normalerweise dem einfachen Zweck, Ihre Ansichts-Engine zu wechseln (z. B. auf Spark, NHaml usw.). In diesem Fall müssen wir nicht die Logik zum Erstellen von Ansichten überschreiben, sondern die Methoden FindPartialView
und FindView
in der VirtualPathProviderViewEngine
Klasse.
Sie können Ihren Glückssternen danken, dass diese Methoden tatsächlich virtuell sind, da alles andere in der VirtualPathProviderViewEngine
nicht einmal zugänglich ist - es ist privat, und das macht es sehr ärgerlich, die Suchlogik zu überschreiben, da Sie im Grunde die Hälfte des bereits vorhandenen Codes neu schreiben müssen wurde geschrieben, wenn Sie möchten, dass es mit dem Standort-Cache und den Standortformaten gut funktioniert. Nach einigem Graben in Reflector gelang es mir schließlich, eine funktionierende Lösung zu finden.
Was ich hier getan habe, ist, zuerst eine Zusammenfassung zu erstellen AreaAwareViewEngine
, die direkt von VirtualPathProviderViewEngine
statt abgeleitet ist WebFormViewEngine
. Ich habe dies getan, damit Sie diese Klasse weiterhin als Basistyp verwenden können, wenn Sie stattdessen (oder was auch immer) Spark-Ansichten erstellen möchten.
Der folgende Code ist ziemlich langwierig, um Ihnen eine kurze Zusammenfassung der tatsächlichen Funktionsweise zu geben: Sie können ein Code {2}
in das Standortformat einfügen, das dem Bereichsnamen entspricht, genauso wie {1}
der Name des Controllers. Das ist es! Dafür mussten wir den ganzen Code schreiben:
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;
}
}
Nun, wie gesagt, dies ist kein konkreter Motor, also müssen Sie das auch erstellen. Glücklicherweise ist dieser Teil viel einfacher. Wir müssen lediglich die Standardformate festlegen und die Ansichten erstellen:
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);
}
}
Beachten Sie, dass wir dem Standard nur wenige Einträge hinzugefügt haben ViewLocationFormats
. Dies sind die neuen {2}
Einträge, bei denen die Einträge {2}
den von area
uns eingegebenen zugeordnet werden RouteData
. Ich habe das MasterLocationFormats
alleine gelassen, aber natürlich können Sie das ändern, wenn Sie wollen.
Ändern Sie nun Ihre global.asax
, um diese Ansichts-Engine zu registrieren:
Global.asax.cs
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new AreaAwareViewEngine());
}
... und registrieren Sie die Standardroute:
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 = "" }
);
}
Erstellen Sie nun das, auf das AreaController
wir gerade verwiesen haben:
DefaultController.cs (in ~ / Controllers /)
public class DefaultController : Controller
{
public ActionResult ActionY()
{
return View("TestView");
}
}
Natürlich brauchen wir dazu die Verzeichnisstruktur und die Ansicht - wir halten das super einfach:
TestView.aspx (in ~ / Areas / AreaZ / Views / Default / oder ~ / Areas / AreaZ / Views / Shared /)
<%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
<h2>TestView</h2>
This is a test view in AreaZ.
Und das ist es. Endlich sind wir fertig .
Zum größten Teil sollten Sie in der Lage sein, das BaseAreaAwareViewEngine
und einfach AreaAwareViewEngine
in jedes MVC-Projekt zu kopieren. Obwohl dafür viel Code erforderlich war, müssen Sie es nur einmal schreiben. Danach müssen Sie nur noch ein paar Zeilen bearbeiten global.asax.cs
und Ihre Site-Struktur erstellen.
ActionLink
Problem zu lösen , indem Sie dasselbearea = "AreaZ"
zur "Standard" -Routenzuordnung in hinzufügenglobal.asax.cs
. Ich bin allerdings nicht positiv; versuchen Sie es und sehen Sie.So habe ich es gemacht. Ich weiß nicht, warum Sie mit MapRoute () den Bereich nicht festlegen können, aber das Routenobjekt wird zurückgegeben, sodass Sie weitere Änderungen vornehmen können, die Sie möchten. Ich verwende dies, weil ich eine modulare MVC-Site habe, die an Unternehmenskunden verkauft wird und die in der Lage sein müssen, DLLs in den Ordner bin zu legen, um neue Module hinzuzufügen. Ich erlaube ihnen, die "HomeArea" in der AppSettings-Konfiguration zu ändern.
Bearbeiten: Sie können dies auch in Ihrer AreaRegistration.RegisterArea für den Bereich versuchen, in den der Benutzer standardmäßig gehen soll. Ich habe es nicht getestet, aber AreaRegistrationContext.MapRoute macht Sets
route.DataTokens["area"] = this.AreaName;
für Sie.quelle
sogar es wurde bereits beantwortet - dies ist die kurze Syntax (ASP.net 3, 4, 5):
quelle
Vielen Dank an Aaron für den Hinweis, dass es darum geht, die Ansichten zu lokalisieren. Ich habe das falsch verstanden.
[UPDATE] Ich habe gerade ein Projekt erstellt, das den Benutzer standardmäßig in einen Bereich sendet, ohne den Code oder die Suchpfade zu beeinträchtigen:
Registrieren Sie sich in global.asax wie gewohnt:
in
Application_Start()
, stellen Sie sicher , dass die folgende Reihenfolge zu verwenden;Verwenden Sie in Ihrem Bereich Registrierung
Ein Beispiel finden Sie unter http://www.emphess.net/2010/01/31/areas-routes-and-defaults-in-mvc-2-rc/
Ich hoffe wirklich, dass Sie darum gebeten haben ...
////
Ich denke nicht, dass das Schreiben eines Pseudos
ViewEngine
in diesem Fall die beste Lösung ist. (Mangelnder Ruf kann ich nicht kommentieren). DasWebFormsViewEngine
ist bereichsbewusst und enthält,AreaViewLocationFormats
welches standardmäßig als definiert istIch glaube, Sie halten sich nicht an diese Konvention. Du hast geposted
als funktionierender Hack, aber das sollte sein
Wenn Sie der Konvention jedoch nicht folgen möchten, möchten Sie möglicherweise einen kurzen Pfad
WebFormViewEngine
einschlagen, indem Sie entweder von (was beispielsweise in MvcContrib erfolgt) ableiten, wo Sie die Suchpfade im Konstruktor festlegen können, oder -a kleiner Hacky - indem Sie Ihre Konvention wie folgt angebenApplication_Start
:Dies sollte natürlich mit etwas mehr Sorgfalt durchgeführt werden, aber ich denke, es zeigt die Idee. Diese Felder sind
public
inVirtualPathProviderViewEngine
in MVC 2 RC.quelle
VirtualPathProviderViewEngine
hat diese Eigenschaft nicht und ist nicht bereichsbewusst. Und obwohl diese Frage tatsächlich MVC 2 betraf, verwenden viele Leute sie immer noch nicht (und werden es für einige Zeit nicht sein). Ihre Antwort ist für die spezifische Frage einfacher, aber meine ist die einzige, die für MVC1-Benutzer funktioniert, die auf diese Frage stoßen. Ich möchte Antworten geben, die nicht von der Funktionalität vor der Veröffentlichung abhängen, die möglicherweise geändert werden kann.RegisterAreas
vorher zu gehenRegisterRoutes
. Ich habe mich gefragt, warum mein Code plötzlich nicht mehr funktioniert und diesen Refactor bemerkt hat;)Ich denke, Sie möchten, dass der Benutzer zu der
~/AreaZ
URL umgeleitet wird, sobald er die~/
URL besucht hat . Ich würde mit dem folgenden Code in Ihrem Stamm erreichenHomeController
.Und die folgende Route in
Global.asax
.quelle
Erstens, welche Version von MVC2 verwenden Sie? Es gab signifikante Änderungen von Vorschau2 zu RC.
Angenommen, Sie verwenden den RC, sollte die Routenzuordnung meiner Meinung nach anders aussehen. In
AreaRegistration.cs
Ihrer Nähe können Sie eine Standardroute registrieren, zDer obige Code sendet den Benutzer standardmäßig an die
MyRouteController
in unsererShopArea
.Die Verwendung einer leeren Zeichenfolge als zweiten Parameter sollte eine Ausnahme auslösen, da ein Controller angegeben werden muss.
Natürlich müssen Sie die Standardroute ändern,
Global.asax
damit diese Standardroute nicht beeinträchtigt wird, z. B. indem Sie ein Präfix für die Hauptwebsite verwenden.Siehe auch diesen Thread und Haacks Antwort: MVC 2 AreaRegistration Routes Order
Hoffe das hilft.
quelle
Das Hinzufügen von Folgendem zu meinem Application_Start funktioniert für mich, obwohl ich nicht sicher bin, ob Sie diese Einstellung in RC haben:
quelle
Was ich getan habe, um dies zum Laufen zu bringen, ist Folgendes:
In der Steuerung habe ich folgenden Code hinzugefügt:
In meiner RouterConfig.cs habe ich Folgendes hinzugefügt:
Der Trick dahinter ist, dass ich einen Standardkonstruktor erstellt habe, der bei jedem Start meiner App immer der Startcontroller ist. Wenn es diesen Standardcontroller trifft, wird es zu jedem Controller umgeleitet, den ich in der Standardindexaktion angegeben habe. Was in meinem Fall ist
.
quelle
Hast du das versucht?
quelle
Das Auffinden der verschiedenen Bausteine erfolgt im Anforderungslebenszyklus. Einer der ersten Schritte im ASP.NET MVC-Anforderungslebenszyklus besteht darin, die angeforderte URL der richtigen Controller-Aktionsmethode zuzuordnen. Dieser Vorgang wird als Routing bezeichnet. Eine Standardroute wird in der Datei Global.asax initialisiert und beschreibt dem ASP.NET MVC-Framework, wie eine Anforderung verarbeitet wird. Durch Doppelklicken auf die Datei Global.asax im MvcApplication1-Projekt wird der folgende Code angezeigt:
Im Ereignishandler Application_Start (), der bei jedem Kompilieren der Anwendung oder beim Neustart des Webservers ausgelöst wird, wird eine Routentabelle registriert. Die Standardroute heißt Standard und antwortet auf eine URL in Form von http://www.example.com/ {controller} / {action} / {id}. Die Variablen zwischen {und} werden mit tatsächlichen Werten aus der Anforderungs-URL oder mit den Standardwerten gefüllt, wenn die URL keine Überschreibung enthält. Diese Standardroute wird gemäß den Standardroutingparametern dem Home-Controller und der Indexaktionsmethode zugeordnet. Mit dieser Routing-Karte werden wir keine weiteren Aktionen ausführen.
Standardmäßig können alle möglichen URLs über diese Standardroute zugeordnet werden. Es ist auch möglich, eigene Routen zu erstellen. Lassen Sie uns beispielsweise die URL http://www.example.com/Employee/Maarten dem Mitarbeiter-Controller, der Aktion Anzeigen und dem Parameter Vorname zuordnen. Das folgende Codefragment kann in die gerade geöffnete Datei Global.asax eingefügt werden. Da das ASP.NET MVC-Framework die erste übereinstimmende Route verwendet, sollte dieses Codefragment über der Standardroute eingefügt werden. Andernfalls wird die Route niemals verwendet.
Fügen wir nun die erforderlichen Komponenten für diese Route hinzu. Erstellen Sie zunächst eine Klasse mit dem Namen EmployeeController im Ordner Controllers. Sie können dies tun, indem Sie dem Projekt ein neues Element hinzufügen und die MVC Controller Class-Vorlage auswählen, die sich unter Web | befindet MVC-Kategorie. Entfernen Sie die Indexaktionsmethode und ersetzen Sie sie durch eine Methode oder Aktion mit dem Namen Anzeigen. Diese Methode akzeptiert einen Vornamenparameter und übergibt die Daten an das ViewData-Wörterbuch. Dieses Wörterbuch wird von der Ansicht zum Anzeigen von Daten verwendet.
Die EmployeeController-Klasse übergibt ein Employee-Objekt an die Ansicht. Diese Employee-Klasse sollte im Ordner Models hinzugefügt werden (klicken Sie mit der rechten Maustaste auf diesen Ordner und wählen Sie dann Add | Class aus dem Kontextmenü). Hier ist der Code für die Employee-Klasse:
quelle
Nun, während das Erstellen einer benutzerdefinierten Ansichts-Engine dafür funktionieren kann, können Sie dennoch eine Alternative haben:
Prost!
quelle
Eine akzeptierte Lösung für diese Frage ist, dass die Frage, wie eine benutzerdefinierte Ansichts-Engine erstellt wird, zwar korrekt ist, die Frage jedoch nicht richtig beantwortet. Das Problem hierbei ist, dass Pino seine Standardroute falsch angibt . Insbesondere seine "Gebiets" -Definition ist falsch. "Bereich" wird über die DataTokens-Sammlung überprüft und sollte als solcher hinzugefügt werden:
Der angegebene "Bereich" im Standardobjekt wird ignoriert . Der obige Code erstellt eine Standardroute, die Anforderungen an das Stammverzeichnis Ihrer Site abfängt und dann die Standardaktion "Index" im Administratorbereich aufruft. Beachten Sie auch, dass der Schlüssel "Namespaces" zu DataTokens hinzugefügt wird. Dies ist nur erforderlich, wenn Sie mehrere Controller mit demselben Namen haben. Diese Lösung wird mit Mvc2 und Mvc3 .NET 3.5 / 4.0 überprüft
quelle
ähm, ich weiß nicht warum all diese Programmierung, ich denke, das ursprüngliche Problem kann leicht gelöst werden, indem diese Standardroute angegeben wird ...
quelle