Как установить маршрут по умолчанию (в область) в MVC - PullRequest
118 голосов
/ 26 января 2010

Хорошо, об этом уже спрашивали, но там нет твердого решения. Поэтому для себя и других, которые могут найти это полезным.

В MVC2 (ASP.NET) я хочу, чтобы, когда кто-то переходил на сайт, была указана область по умолчанию. Поэтому переход на мой сайт должен отправить вас в ControllerX ActionY в AreaZ.

Используя следующий маршрут в Global.asax

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

Теперь это работает, так как пытается найти правильную страницу. Однако MVC продолжает поиск View в корне сайта, а не в папке Area.

Есть ли способ решить эту проблему?

РЕДАКТИРОВАТЬ

Существует «Решение», и это в ControllerX, ActionY возвращает полный путь к представлению. Немного взломать, но это работает. Однако я надеюсь, что есть лучшее решение.

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

Редактировать:

Это также становится проблемой при наличии HTML-ссылки ActionLink на странице. Если область не задана, ссылка на действие выводится пустой.

Все это задумано или является недостатком?

Ответы [ 13 ]

96 голосов
/ 29 июля 2010

Вот как я это сделал. Я не знаю, почему MapRoute () не позволяет вам установить область, но он возвращает объект маршрута, чтобы вы могли продолжать вносить любые дополнительные изменения, которые вы хотели бы. Я использую это, потому что у меня есть модульный сайт MVC, который продается корпоративным клиентам, и они должны иметь возможность помещать dll в папку bin для добавления новых модулей. Я разрешаю им изменить «HomeArea» в конфиге AppSettings.

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

Редактировать: Вы можете попробовать это также в вашей AreaRegistration.RegisterArea для области, в которую пользователь хочет попасть по умолчанию. Я не проверял это, но AreaRegistrationContext.MapRoute устанавливает для вас route.DataTokens["area"] = this.AreaName;.

context.MapRoute(
                    "Home_Default", 
                    "", 
                    new {controller = "Home", action = "index" },
                    new[] { "IPC.Web.Core.Controllers" }
                   );
95 голосов
/ 31 января 2010

Это меня заинтересовало, и у меня наконец-то появилась возможность разобраться в этом. Очевидно, что другие люди не поняли, что это проблема с поиском представления , а не проблема с маршрутизацией - и это, вероятно, потому, что заголовок вашего вопроса указывает на то, что речь идет о маршрутизации.

В любом случае, поскольку это проблема, связанная с View, единственный способ получить то, что вы хотите, - это переопределить механизм представления по умолчанию . Обычно, когда вы делаете это, это делается с простой целью - переключить ваш движок представления (то есть на Spark, NHaml и т. Д.). В этом случае нам необходимо переопределить не логику создания представления, а методы FindPartialView и FindView в классе VirtualPathProviderViewEngine.

Вы можете поблагодарить своих счастливых звезд за то, что эти методы на самом деле являются виртуальными, потому что все остальное в VirtualPathProviderViewEngine даже недоступно - это личное, и это делает его очень раздражает переопределение логики поиска, потому что вам нужно переписать половину уже написанного кода, если вы хотите, чтобы он хорошо работал с кэшем местоположения и форматами местоположения. Немного покопавшись в Reflector, мне наконец удалось найти рабочее решение.

То, что я здесь сделал, - это сначала создаю аннотацию AreaAwareViewEngine, которая получается непосредственно из VirtualPathProviderViewEngine вместо WebFormViewEngine. Я сделал это так, что если вы хотите вместо этого создать представления Spark (или как угодно), вы все равно можете использовать этот класс в качестве базового типа.

Приведенный ниже код довольно скучный, поэтому для краткого изложения того, что он на самом деле делает: он позволяет поместить {2} в формат местоположения, соответствующий названию области, таким же образом {1} соответствует названию контроллера. Это оно! Вот для чего мы должны были написать весь этот код:

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

Теперь, как уже говорилось, это не конкретный двигатель, поэтому вы должны его создать. К счастью, эта часть намного проще, все, что нам нужно сделать, это установить форматы по умолчанию и фактически создать представления:

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

Обратите внимание, что мы добавили несколько записей в стандарт ViewLocationFormats. Это новые записи {2}, где {2} будет сопоставлен с area, который мы вставим в RouteData. Я оставил MasterLocationFormats в покое, но, очевидно, вы можете изменить это, если хотите.

Теперь измените ваш global.asax, чтобы зарегистрировать этот механизм просмотра:

Global.asax.cs

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

... и зарегистрируйте маршрут по умолчанию:

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 = "" }
    );
}

Теперь создайте AreaController, на который мы только что ссылались:

DefaultController.cs (в ~ / Controllers /)

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

Очевидно, что нам нужна структура каталогов и представление для этого - мы сделаем это очень просто:

TestView.aspx (в ~ / Areas / AreaZ / Views / Default / или ~ / Areas / AreaZ / Views / Shared /)

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

И это все. Наконец, мы закончили .

В большинстве случаев вы можете просто взять BaseAreaAwareViewEngine и AreaAwareViewEngine и поместить их в любой проект MVC, так что даже если для этого потребовалось много кода, вам нужно только написать это один раз. После этого нужно всего лишь отредактировать несколько строк в global.asax.cs и создать структуру вашего сайта.

55 голосов
/ 20 апреля 2012

даже на него уже ответили - это короткий синтаксис (ASP.net 3, 4, 5):

routes.MapRoute("redirect all other requests", "{*url}",
    new {
        controller = "UnderConstruction",
        action = "Index"
        }).DataTokens = new RouteValueDictionary(new { area = "Shop" });
16 голосов
/ 31 января 2010

Спасибо Аарону за то, что он указал на то, что речь идет о поиске точек зрения, я неправильно понял это.

[ОБНОВЛЕНИЕ] Я только что создал проект, который отправляет пользователя в область по умолчанию, не вмешиваясь ни в один из кодов или путей поиска:

В global.asax зарегистрируйтесь как обычно:

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

в Application_Start(), убедитесь, что используете следующий порядок;

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

в вашей области регистрации, используйте

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

Пример можно найти на http://www.emphess.net/2010/01/31/areas-routes-and-defaults-in-mvc-2-rc/

Я действительно надеюсь, что это то, что вы просили ...

////

Я не думаю, что написание псевдо ViewEngine - лучшее решение в этом случае. (Не имея репутации, я не могу комментировать). WebFormsViewEngine является Знанием и содержит AreaViewLocationFormats, который по умолчанию определен как

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

Я считаю, что вы не придерживаетесь этого соглашения. Вы отправили

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

как рабочий взлом, но это должно быть

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

Если вы не хотите следовать соглашению, вы можете выбрать короткий путь, либо производный от WebFormViewEngine (например, в MvcContrib), где вы можете установить пути поиска конструктор, или - немного хакерский - указав ваше соглашение следующим образом: Application_Start:

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

Это должно быть выполнено с большей осторожностью, конечно, но я думаю, что это показывает идею. Эти поля public в VirtualPathProviderViewEngine в MVC 2 RC.

6 голосов
/ 31 января 2010

Полагаю, вы хотите, чтобы пользователь перенаправлялся на ~/AreaZ URL-адрес один раз, когда он посетил ~/ URL-адрес. Я достиг бы с помощью следующего кода в вашем корне HomeController.

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

И следующий маршрут в Global.asax.

routes.MapRoute(
    "Redirection to AreaZ",
    String.Empty,
    new { controller = "Home ", action = "Index" }
);
2 голосов
/ 11 августа 2010

Добавление следующего в мой Application_Start работает для меня, хотя я не уверен, есть ли у вас этот параметр в 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"
};
2 голосов
/ 30 января 2010

Во-первых, какую версию MVC2 вы используете? От предварительного просмотра2 до RC произошли значительные изменения.

Предполагая, что вы используете RC, я думаю, что ваше отображение маршрута должно выглядеть по-другому. В AreaRegistration.cs в вашем регионе вы можете зарегистрировать какой-либо маршрут по умолчанию, например,

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

Приведенный выше код отправит пользователя на MyRouteController в нашем ShopArea по умолчанию.

Использование пустой строки в качестве второго параметра должно вызвать исключение, поскольку должен быть указан контроллер.

Конечно, вам придется изменить маршрут по умолчанию в Global.asax, чтобы он не мешал этому маршруту по умолчанию, например, используя префикс для основного сайта.

Также смотрите эту ветку и ответ Хаака: MVC 2 AreaRegistration Routes Order

Надеюсь, это поможет.

1 голос
/ 13 июля 2016

То, что я сделал, чтобы заставить это работать, следующее:

  1. Я создал контроллер по умолчанию в папке root / Controllers. Я назвал свой контроллер DefaultController.
  2. В контроллере я добавил следующий код:

    namespace MyNameSpace.Controllers {
    public class DefaultController : Controller {
        // GET: Default
        public ActionResult Index() {
            return RedirectToAction("Index", "ControllerName", new {area = "FolderName"});
        }
    } }
    
  3. В моем RouterConfig.cs я добавил следующее:

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

Хитрость во всем этом заключается в том, что я создал конструктор по умолчанию, который всегда будет контроллером запуска при каждом запуске моего приложения. Когда он попадает на контроллер по умолчанию, он перенаправляется на любой контроллер, указанный в действии индекса по умолчанию. Который в моем случае

www.myurl.com / FolderName / ControllerName

.

0 голосов
/ 11 апреля 2012

Принятое решение этого вопроса, хотя и правильное изложение того, как создать собственный механизм просмотра, не дает правильного ответа на вопрос. Проблема здесь в том, что Пино неправильно указывает свой маршрут по умолчанию . В частности, его определение "области" неверно. «Площадь» проверяется с помощью коллекции DataTokens и должна быть добавлена ​​следующим образом:

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

Указанная «область» в объекте по умолчанию будет игнорироваться . Приведенный выше код создает маршрут по умолчанию, который отслеживает запросы к корню вашего сайта, а затем вызывает контроллер по умолчанию, действие Index в области администратора. Также обратите внимание, что ключ «Пространства имен» добавляется в DataTokens, это требуется только в том случае, если у вас есть несколько контроллеров с одинаковым именем. Это решение проверено с Mvc2 и Mvc3 .NET 3.5 / 4.0

0 голосов
/ 01 марта 2012

ммм, я не знаю, почему все это программирование, я думаю, что оригинальная проблема легко решается путем указания маршрута по умолчанию ...

routes.MapRoute("Default", "{*id}", 
                 new { controller = "Home"
                     , action = "Index"
                     , id = UrlParameter.Optional 
                     }
              );
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...