Ten mnie zainteresował i wreszcie miałem okazję się temu przyjrzeć. Inni ludzie najwyraźniej nie zrozumieli, że jest to problem ze znalezieniem widoku , a nie z samym routingiem - a to prawdopodobnie dlatego, że tytuł twojego pytania wskazuje, że dotyczy routingu.
W każdym razie, ponieważ jest to problem związany z widokiem, jedynym sposobem na uzyskanie tego, czego chcesz, jest zastąpienie domyślnego silnika widoku . Zwykle, gdy to robisz, dzieje się to w prostym celu przełączenia silnika widoku (np. Na Spark, NHaml itp.). W tym przypadku to nie logika tworzenia widoku, którą musimy przesłonić, ale metody FindPartialView
i FindView
w VirtualPathProviderViewEngine
klasie.
Możesz podziękować swoim szczęśliwym gwiazdom, że te metody są w rzeczywistości wirtualne, ponieważ wszystko inne w programie VirtualPathProviderViewEngine
nie jest nawet dostępne - jest prywatne, a to bardzo denerwuje nadpisywanie logiki wyszukiwania, ponieważ musisz w zasadzie przepisać połowę kodu, który już jest został napisany, jeśli chcesz, aby działał dobrze z pamięcią podręczną lokalizacji i formatami lokalizacji. Po pewnym czasie zagłębiania się w Reflector w końcu udało mi się znaleźć działające rozwiązanie.
To, co tutaj zrobiłem, to najpierw stworzyć streszczenie, AreaAwareViewEngine
które pochodzi bezpośrednio z VirtualPathProviderViewEngine
zamiast WebFormViewEngine
. Zrobiłem to, aby zamiast tego utworzyć widoki Spark (lub cokolwiek innego), nadal możesz używać tej klasy jako typu podstawowego.
Poniższy kod jest dość rozwlekły, więc aby dać ci krótkie podsumowanie tego, co faktycznie robi: pozwala wstawić {2}
do formatu lokalizacji, który odpowiada nazwie obszaru, tak samo {1}
odpowiada nazwie kontrolera. Otóż to! Do tego musieliśmy napisać cały ten kod:
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;
}
}
Jak już wspomniano, nie jest to konkretny silnik, więc musisz go również stworzyć. Ta część na szczęście jest dużo prostsza, wystarczy ustawić domyślne formaty i właściwie stworzyć widoki:
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);
}
}
Zwróć uwagę, że dodaliśmy kilka wpisów do standardu ViewLocationFormats
. To są nowe {2}
wpisy, w których {2}
zostaną zmapowane do pliku, który area
umieścimy w RouteData
. Zostawiłem MasterLocationFormats
sam, ale oczywiście możesz to zmienić, jeśli chcesz.
Teraz zmodyfikuj swój, global.asax
aby zarejestrować ten silnik widoku:
Global.asax.cs
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new AreaAwareViewEngine());
}
... i zarejestruj trasę domyślną:
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 = "" }
);
}
Teraz utwórz plik, do AreaController
którego właśnie się odwołaliśmy:
DefaultController.cs (w ~ / Controllers /)
public class DefaultController : Controller
{
public ActionResult ActionY()
{
return View("TestView");
}
}
Oczywiście potrzebujemy struktury katalogów i widoku, aby do tego pasować - zachowamy to bardzo proste:
TestView.aspx (w ~ / Areas / AreaZ / Views / Default / lub ~ / Areas / AreaZ / Views / Shared /)
<%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
<h2>TestView</h2>
This is a test view in AreaZ.
I to wszystko. Wreszcie skończyliśmy .
W przeważającej części, powinieneś być w stanie po prostu wziąć BaseAreaAwareViewEngine
i AreaAwareViewEngine
i upuść go w dowolnym projekcie MVC, więc chociaż zajęło dużo kodu, aby to zrobić, trzeba tylko napisać to raz. Potem wystarczy edytować kilka wierszy global.asax.cs
i stworzyć strukturę witryny.
ActionLink
problem, dodając to samoarea = "AreaZ"
do "domyślnego" mapowania trasy w programieglobal.asax.cs
. Nie jestem jednak pewien; spróbuj i zobacz.Tak to zrobiłem. Nie wiem, dlaczego MapRoute () nie pozwala na ustawienie obszaru, ale zwraca obiekt trasy, więc możesz kontynuować wprowadzanie dodatkowych zmian, które chcesz. Używam tego, ponieważ mam modułową witrynę MVC, która jest sprzedawana klientom korporacyjnym i muszą mieć możliwość upuszczania bibliotek dll do folderu bin, aby dodawać nowe moduły. Pozwalam im zmienić „HomeArea” w konfiguracji AppSettings.
Edycja: Możesz to również wypróbować w swoim AreaRegistration.RegisterArea dla obszaru, do którego użytkownik ma domyślnie przejść. Nie testowałem tego, ale AreaRegistrationContext.MapRoute robi
route.DataTokens["area"] = this.AreaName;
za Ciebie zestawy .źródło
nawet na to już odpowiedziano - oto krótka składnia (ASP.net 3, 4, 5):
źródło
Dzięki Aaronowi za wskazanie, że chodzi o lokalizowanie widoków, źle to zrozumiałem.
[AKTUALIZACJA] Właśnie utworzyłem projekt, który domyślnie wysyła użytkownika do obszaru bez ingerowania w żaden kod ani ścieżki wyszukiwania:
W global.asax zarejestruj się jak zwykle:
w
Application_Start()
, upewnij się, że używasz następującej kolejności;w rejestracji obszaru, użyj
Przykład można znaleźć pod adresem http://www.emphess.net/2010/01/31/areas-routes-and-defaults-in-mvc-2-rc/
Naprawdę mam nadzieję, że o to prosiłeś ...
////
Nie sądzę, aby pisanie pseudo
ViewEngine
to najlepsze rozwiązanie w tym przypadku. (Brak reputacji, nie mogę komentować). RozpoznajeWebFormsViewEngine
obszar i zawiera,AreaViewLocationFormats
który jest zdefiniowany domyślnie jakoUważam, że nie przestrzegasz tej konwencji. Wysłałeś
jako działający hack, ale tak powinno być
Jeśli nie chcesz przestrzegać konwencji, możesz jednak wybrać krótką ścieżkę, wyprowadzając ją z
WebFormViewEngine
(na przykład jest to zrobione w MvcContrib), gdzie możesz ustawić ścieżki wyszukiwania w konstruktorze lub -a mały hacky - określając swoją konwencję w następujący sposóbApplication_Start
:Oczywiście należy to zrobić z większą ostrożnością, ale myślę, że to pokazuje pomysł. Pola te są
public
wVirtualPathProviderViewEngine
w MVC 2 RC.źródło
VirtualPathProviderViewEngine
nie ma tej właściwości i nie rozpoznaje obszaru. I chociaż rzeczywiście stwierdzono, że to pytanie dotyczy MVC 2, wiele osób nadal go nie używa (i przez jakiś czas nie będzie). Twoja odpowiedź jest więc łatwiejsza na konkretne pytanie, ale moja jest jedyną, która zadziała dla użytkowników MVC1, którzy natkną się na to pytanie. Lubię udzielać odpowiedzi, które nie zależą od funkcji przedpremierowej, która może ulec zmianie.RegisterAreas
wcześniejszej wizytyRegisterRoutes
. Zastanawiałem się, dlaczego mój kod nagle przestał działać i zauważyłem ten refaktor;)Wydaje mi się, że chcesz, aby użytkownik był przekierowywany do
~/AreaZ
adresu URL, gdy odwiedził~/
URL. Osiągnąłbym za pomocą następującego kodu w twoim katalogu głównymHomeController
.I następująca trasa w
Global.asax
.źródło
Po pierwsze, jakiej wersji MVC2 używasz? Od wersji Preview2 do RC nastąpiły znaczące zmiany.
Zakładając, że używasz RC, myślę, że mapowanie tras powinno wyglądać inaczej. W
AreaRegistration.cs
Twojej okolicy możesz zarejestrować jakąś domyślną trasę, npPowyższy kod domyślnie wyśle użytkownika do
MyRouteController
naszegoShopArea
.Użycie pustego ciągu jako drugiego parametru powinno spowodować zgłoszenie wyjątku, ponieważ należy określić kontroler.
Oczywiście będziesz musiał zmienić domyślną trasę w,
Global.asax
aby nie kolidowała z tą domyślną trasą, np. Używając prefiksu dla strony głównej.Zobacz także ten wątek i odpowiedź Haacka: Kolejność tras MVC 2 AreaRegistration
Mam nadzieję że to pomoże.
źródło
Dodanie następujących elementów do mojego Application_Start działa dla mnie, chociaż nie jestem pewien, czy masz to ustawienie w RC:
źródło
Oto co zrobiłem, aby to zadziałało:
W kontrolerze dodałem następujący kod:
W moim RouterConfig.cs dodałem:
Sztuczka w tym wszystkim polega na tym, że stworzyłem domyślnego konstruktora, który zawsze będzie kontrolerem uruchamiania przy każdym uruchomieniu mojej aplikacji. Kiedy trafi na ten domyślny kontroler, przekieruje do dowolnego kontrolera, który określę w domyślnej akcji indeksu. W moim przypadku tak jest
.
źródło
Czy próbowałeś tego?
źródło
Lokalizowanie różnych bloków konstrukcyjnych odbywa się w cyklu życia żądania. Jednym z pierwszych kroków w cyklu życia żądania ASP.NET MVC jest mapowanie żądanego adresu URL na poprawną metodę akcji kontrolera. Ten proces jest nazywany routingiem. Trasa domyślna jest inicjowana w pliku Global.asax i opisuje w strukturze ASP.NET MVC sposób obsługi żądania. Dwukrotne kliknięcie pliku Global.asax w projekcie MvcApplication1 spowoduje wyświetlenie następującego kodu:
W procedurze obsługi zdarzeń Application_Start (), która jest uruchamiana za każdym razem, gdy aplikacja jest kompilowana lub serwer WWW jest restartowany, rejestrowana jest tablica tras. Trasa domyślna nosi nazwę Default i odpowiada na adres URL w postaci http://www.example.com/ {controller} / {action} / {id}. Zmienne między {a} są wypełniane rzeczywistymi wartościami z adresu URL żądania lub wartościami domyślnymi, jeśli nie ma nadpisania w adresie URL. Ta domyślna trasa zostanie zmapowana na kontroler Home i metodę akcji Index, zgodnie z domyślnymi parametrami routingu. Nie będziemy mieć żadnej innej akcji z tą mapą routingu.
Domyślnie wszystkie możliwe adresy URL mogą być mapowane przez tę domyślną trasę. Możliwe jest również tworzenie własnych tras. Na przykład zamapujmy adres URL http://www.example.com/Employee/Maarten na kontroler Employee, akcję Show i parametr firstname. Poniższy fragment kodu można wstawić do właśnie otwartego pliku Global.asax. Ponieważ struktura ASP.NET MVC używa pierwszej pasującej trasy, ten fragment kodu należy wstawić powyżej trasy domyślnej; w przeciwnym razie trasa nigdy nie będzie używana.
Teraz dodajmy niezbędne komponenty dla tej trasy. Przede wszystkim utwórz klasę o nazwie EmployeeController w folderze Controllers. Możesz to zrobić, dodając nowy element do projektu i wybierając szablon klasy kontrolera MVC znajdujący się w sieci Web | Kategoria MVC. Usuń metodę akcji Index i zamień ją na metodę lub akcję o nazwie Show. Ta metoda akceptuje parametr firstname i przekazuje dane do słownika ViewData. Ten słownik będzie używany przez widok do wyświetlania danych.
Klasa EmployeeController przekaże obiekt Employee do widoku. Tę klasę pracownika należy dodać do folderu Modele (kliknij prawym przyciskiem myszy ten folder, a następnie wybierz Dodaj | klasę z menu kontekstowego). Oto kod klasy Pracownik:
źródło
Cóż, podczas tworzenia niestandardowego silnika widoku może to zadziałać, nadal możesz mieć alternatywę:
Twoje zdrowie!
źródło
Przyjęte rozwiązanie tego pytania jest takie, że choć poprawne podsumowując sposób tworzenia niestandardowego silnika widoku, nie odpowiada poprawnie na pytanie. Problem polega na tym, że Pino nieprawidłowo określa swoją domyślną trasę . W szczególności jego definicja „obszaru” jest niepoprawna. „Obszar” jest sprawdzany za pośrednictwem kolekcji DataTokens i należy go dodać jako taki:
Określony „obszar” w obiekcie defaults zostanie zignorowany . Powyższy kod tworzy domyślną trasę, która przechwytuje żądania do katalogu głównego witryny, a następnie wywołuje domyślny kontroler, akcję Indeks w obszarze administracyjnym. Należy również pamiętać, że klucz „Namespaces” jest dodawany do DataTokens, jest to wymagane tylko wtedy, gdy masz wiele kontrolerów o tej samej nazwie. To rozwiązanie jest weryfikowane z Mvc2 i Mvc3 .NET 3.5 / 4.0
źródło
hmmm, nie wiem po co to całe programowanie, myślę, że oryginalny problem można łatwo rozwiązać, podając tę domyślną trasę ...
źródło