Jak ustawić domyślną trasę (do obszaru) w MVC

122

Ok, o to pytano wcześniej, ale nie ma tam solidnego rozwiązania. Więc dla mnie i innych, którzy mogą uznać to za przydatne.

W MVC2 (ASP.NET) chcę, aby to było, gdy ktoś przejdzie do witryny sieci Web, jest określony obszar domyślny. Więc nawigacja do mojej witryny powinna skierować cię do ControllerX ActionY w AreaZ.

Korzystając z następującej trasy w pliku Global.asax

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

Teraz działa to tak, jak w przypadku próby wyświetlenia właściwej strony. Jednak MVC kontynuuje wyszukiwanie widoku w katalogu głównym witryny, a nie w folderze Area.

Czy istnieje sposób na rozwiązanie tego problemu?

EDYTOWAĆ

Istnieje „rozwiązanie” i to jest w ControllerX, ActionY zwraca pełną ścieżkę widoku. Trochę hack, ale działa. Mam jednak nadzieję, że jest lepsze rozwiązanie.

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

Edytować:

Staje się to również problemem w przypadku posiadania HTML ActionLink strony. Jeśli obszar nie jest ustawiony, łącze do akcji jest puste.

Czy to wszystko z powodu projektu czy wady?

LiamB
źródło

Odpowiedzi:

98

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 FindPartialViewi FindVieww VirtualPathProviderViewEngineklasie.

Możesz podziękować swoim szczęśliwym gwiazdom, że te metody są w rzeczywistości wirtualne, ponieważ wszystko inne w programie VirtualPathProviderViewEnginenie 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, AreaAwareViewEnginektóre pochodzi bezpośrednio z VirtualPathProviderViewEnginezamiast 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 areaumieścimy w RouteData. Zostawiłem MasterLocationFormatssam, ale oczywiście możesz to zmienić, jeśli chcesz.

Teraz zmodyfikuj swój, global.asaxaby 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 AreaControllerktó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ąć BaseAreaAwareViewEnginei AreaAwareViewEnginei 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.csi stworzyć strukturę witryny.

Aaronaught
źródło
Jest to prawdopodobnie najlepsze obecne rozwiązanie, ale dalekie od ideału. Jak powyżej, po dodaniu Actionlink lub taki sam problem istnieje.
LiamB
1
@Pino: Myślę, że powinieneś być w stanie rozwiązać ActionLinkproblem, dodając to samo area = "AreaZ"do "domyślnego" mapowania trasy w programie global.asax.cs. Nie jestem jednak pewien; spróbuj i zobacz.
Aaronaught
W MVC4 „Default” deklaracja trasy została przeniesiona z Global.asax do ~ / App_Start / RouteConfig.cs / RegisterRoutes ()
Andriy F.
3
Nienawidzę głosować przeciw, ale naprawdę nie mogę uwierzyć, że poniższa odpowiedź od @Chris Alderson nie otrzymała więcej głosów. Jest to znacznie prostsze rozwiązanie niż to i wydaje się, że rozwiązuje skrajne przypadki (ActionLink itp.).
jdmcnair
Wygląda na to, że jest tu błąd. Na przykład widoki obszaru o nazwie „Re” znajdowałyby się w ~ / Areas / Re / Views / Ctrlr / blah.aspx, ale kod tutaj używa ~ / {2} / {1} / {0}, co byłoby ~ /Re/Ctrl/blah.aspx, w ścieżce brakuje katalogu obszarów krytycznych. Powinien to być „~ / Areas / {2} / Views / {1} / {0} .aspx”
Chris Moschini,
100

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.

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

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 .

context.MapRoute(
                    "Home_Default", 
                    "", 
                    new {controller = "Home", action = "index" },
                    new[] { "IPC.Web.Core.Controllers" }
                   );
Chris Alderson
źródło
To działa. Uważaj na nowy plik web.config, może on zastąpić stare globalne konfiguracje.
Mert Akcakaya
56

nawet na to już odpowiedziano - oto krótka składnia (ASP.net 3, 4, 5):

routes.MapRoute("redirect all other requests", "{*url}",
    new {
        controller = "UnderConstruction",
        action = "Index"
        }).DataTokens = new RouteValueDictionary(new { area = "Shop" });
Poważny M.
źródło
6
To działa świetnie dla mnie. Nie mam żadnych kontrolerów w katalogu głównym i używam tylko obszarów. W przypadku MVC 4 mam to zastąpić domyślne w RouteConfig.cs. Dzięki!
Marc
2
Używam MVC4 i to było dla mnie najprostsze rozwiązanie. Zezwala aplikacji na używanie widoku indeksu w określonym obszarze jako „strony głównej” witryny.
JTech
2
To rozwiązanie nie będzie działać w przyszłości (z Asp.Net MVC6 i nowszych).
Patrick Desjardins,
@PatrickDesjardins: Czy jest jakiś powód, aby nie wspierać powyższego rozwiązania?
Akash KC
@SeriousM Twoja odpowiedź jest wiecznie zielona. To wciąż pomocne. Uratowałeś mi noc.
skpaul
16

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:

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

w Application_Start(), upewnij się, że używasz następującej kolejności;

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

w rejestracji obszaru, użyj

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

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 ViewEngineto najlepsze rozwiązanie w tym przypadku. (Brak reputacji, nie mogę komentować). Rozpoznaje WebFormsViewEngineobszar i zawiera, AreaViewLocationFormatsktóry jest zdefiniowany domyślnie jako

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",
    };

Uważam, że nie przestrzegasz tej konwencji. Wysłałeś

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

jako działający hack, ale tak powinno być

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

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ób Application_Start:

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

Oczywiście należy to zrobić z większą ostrożnością, ale myślę, że to pokazuje pomysł. Pola te są publicw VirtualPathProviderViewEnginew MVC 2 RC.

mnemosyn
źródło
Warto zauważyć, że dotyczy to tylko MVC 2 RC - MVC 1 VirtualPathProviderViewEnginenie 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.
Aaronaught
Nie jest to również „pseudo-silnik widoku” - klasy silnika widoku zostały celowo stworzone tak, aby były rozszerzalne, aby można było używać różnych rodzajów widoków.
Aaronaught
To nie miało cię urazić, przepraszam. Jest to „pseudo”, ponieważ nie zmienia znacząco sposobu obsługi widoków, a jedynie zastępuje niektóre wartości.
mnemosyn
Nie obraziłem się, chciałem tylko wyjaśnić, że nie jest to szczególnie niezwykły powód, aby wyprowadzić niestandardowy silnik widoku, o czym świadczy fakt, że odpowiednie metody można zastąpić.
Aaronaught
2
Świetna wskazówka dotycząca RegisterAreaswcześniejszej wizyty RegisterRoutes. Zastanawiałem się, dlaczego mój kod nagle przestał działać i zauważyłem ten refaktor;)
webnoob
6

Wydaje mi się, że chcesz, aby użytkownik był przekierowywany do ~/AreaZadresu URL, gdy odwiedził ~/URL. Osiągnąłbym za pomocą następującego kodu w twoim katalogu głównym HomeController.

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

I następująca trasa w Global.asax.

routes.MapRoute(
    "Redirection to AreaZ",
    String.Empty,
    new { controller = "Home ", action = "Index" }
);
Anthony Serdyukov
źródło
To działa, ale zmienia się na adres URL w przeglądarce użytkowników. Nie jest to naprawdę idealne.
LiamB
2

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.csTwojej okolicy możesz zarejestrować jakąś domyślną trasę, np

        context.MapRoute(
            "ShopArea_default",
            "{controller}/{action}/{id}",
            new { action = "Index", id = "", controller="MyRoute" }
        );

Powyższy kod domyślnie wyśle ​​użytkownika do MyRouteControllernaszego ShopArea.

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.asaxaby 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.

mnemosyn
źródło
Dziękuję, ale nie jestem pewien, czy to rozwiązuje problem wyjaśniony w pytaniu. I jego MVC RC
LiamB
2

Dodanie następujących elementów do mojego Application_Start działa dla mnie, chociaż nie jestem pewien, czy masz to ustawienie w RC:

var engine = (WebFormViewEngine)ViewEngines.Engines.First();

// These additions allow me to route default requests for "/" to the home area
engine.ViewLocationFormats = new string[] { 
    "~/Views/{1}/{0}.aspx",
    "~/Views/{1}/{0}.ascx",
    "~/Areas/{1}/Views/{1}/{0}.aspx", // new
    "~/Areas/{1}/Views/{1}/{0}.ascx", // new
    "~/Areas/{1}/Views/{0}.aspx", // new
    "~/Areas/{1}/Views/{0}.ascx", // new
    "~/Views/{1}/{0}.ascx",
    "~/Views/Shared/{0}.aspx",
    "~/Views/Shared/{0}.ascx"
};
Derek Hunziker
źródło
1

Oto co zrobiłem, aby to zadziałało:

  1. Utworzyłem domyślny kontroler w folderze root / Controllers. Nazwałem mój kontroler DefaultController.
  2. W kontrolerze dodałem następujący kod:

    namespace MyNameSpace.Controllers {
    public class DefaultController : Controller {
        // GET: Default
        public ActionResult Index() {
            return RedirectToAction("Index", "ControllerName", new {area = "FolderName"});
        }
    } }
  3. W moim RouterConfig.cs dodałem:

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new {controller = "Default", action = "Index", id = UrlParameter.Optional});

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

www.myurl.com/FolderName/ControllerName

.

JEuvin
źródło
0
routes.MapRoute(
                "Area",
                "{area}/",
                new { area = "AreaZ", controller = "ControlerX ", action = "ActionY " }
            );

Czy próbowałeś tego?

Barbaros Alp
źródło
Tak, problem polega na tym, że teraz strona szuka widoków w katalogu głównym. Nie znaleziono widoku „ActionY” lub jego wzorca. Przeszukano następujące lokalizacje: ~ / Views / ActionY / ActionY.aspx ~ / Views / ActionY / ActionY.ascx ~ / Views / Shared / ActionY.aspx ~ / Views / Shared / ActionY.ascx
LiamB
2
Rozumiem. Spróbuję znaleźć rozwiązanie. +1 za pytanie
Barbaros Alp
0

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:

using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing;

namespace MvcApplication1 {

   public class GlobalApplication : System.Web.HttpApplication
   {
       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
           );

       }

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

}

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.

routes.MapRoute(

   "EmployeeShow",                    // Route name
   "Employee/{firstname}",            // URL with parameters
    new {                             // Parameter defaults
       controller = "Employee",
       action = "Show", 
       firstname = "" 
   }  

);

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:

namespace MvcApplication1.Models {

   public class Employee
   {
       public string FirstName { get; set; }
       public string LastName { get; set; }
       public string Email { get; set; }
   }

} 
Sanju
źródło
1
Dzięki, nie jestem do końca pewien, jak to się ma do ustawiania domyślnego OBSZARU. : - /
LiamB
0

Cóż, podczas tworzenia niestandardowego silnika widoku może to zadziałać, nadal możesz mieć alternatywę:

  • Zdecyduj, co chcesz wyświetlać domyślnie.
  • To coś ma kontroler i akcję (i obszar), prawda?
  • Otwórz rejestrację tego obszaru i dodaj coś takiego:
public override void RegisterArea(AreaRegistrationContext context)
{
    //this makes it work for the empty url (just domain) to act as current Area.
    context.MapRoute(
        "Area_empty",
        "",
        new { controller = "Home", action = "Index", id = UrlParameter.Optional },
        namespaces: new string[] { "Area controller namespace" }
    );
        //other routes of the area
}

Twoje zdrowie!

Tengiz
źródło
Zgoda. Chociaż myślę, że bardziej odpowiednim miejscem dla tej definicji trasy jest plik Global.asax.
nuhusky2003
W takim przypadku twoje definicje global.asax wiedziałyby o istnieniu przestrzeni nazw kontrolera obszaru, co moim zdaniem nie jest właściwe. Obszary to dodatkowa funkcjonalność, co oznacza, że ​​musisz mieć możliwość dodawania / usuwania jednego bez dotykania definicji global.asax. W moim podejściu do pytania wolę obszar „przejęcia” żądania, zamiast [globalnej] witryny internetowej do „przekazania” żądania.
Tengiz,
0

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:

var defaultRoute = new Route("",new RouteValueDictionary(){{"controller","Default"},{"action","Index"}},null/*constraints*/,new RouteValueDictionary(){{"area","Admin"}},new MvcRouteHandler());
defaultRoute.DataTokens.Add("Namespaces","MyProject.Web.Admin.Controller"); 
routes.Add(defaultRoute);

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

nuhusky2003
źródło
-1

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ę ...

routes.MapRoute("Default", "{*id}", 
                 new { controller = "Home"
                     , action = "Index"
                     , id = UrlParameter.Optional 
                     }
              );
Tarun
źródło