Como definir uma rota padrão (para uma área) no MVC


122

Ok, isso já foi solicitado antes, mas não existe uma solução sólida por aí. Então, com o objetivo de mim e de outros que possam achar isso útil.

No MVC2 (ASP.NET), quero que quando alguém navega no site, há uma área padrão especificada. Portanto, a navegação no meu site deve enviar você para o ControllerX ActionY no AreaZ.

Usando a seguinte rota no Global.asax

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

Agora isso funciona, pois tenta servir a página correta. No entanto, o MVC continua procurando a Visualização na raiz do site e não na pasta Área.

Existe uma maneira de resolver isso?

EDITAR

Existe uma 'Solução' e que está no ControllerX, ActionY retorna o caminho completo da visualização. Um pouco hack, mas funciona. No entanto, espero que exista uma solução melhor.

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

Editar:

Isso também se torna um problema ao ter um ActionLink HTML da página. Se a área não estiver definida, o Link de ação será exibido em branco.

Tudo isso é intencional ou falha?

Respostas:


98

Este me interessou, e finalmente tive a chance de investigar. Outras pessoas aparentemente não entenderam que esse é um problema para encontrar a exibição , não para o próprio roteamento - e provavelmente porque o título da sua pergunta indica que se trata de roteamento.

De qualquer forma, por se tratar de um problema relacionado à exibição, a única maneira de obter o que você deseja é substituir o mecanismo de exibição padrão . Normalmente, quando você faz isso, é com o simples propósito de alternar o mecanismo de exibição (por exemplo, para Spark, NHaml etc.). Nesse caso, não é a lógica de criação de exibição que precisamos substituir, mas os métodos FindPartialViewe FindViewna VirtualPathProviderViewEngineclasse.

Você pode agradecer às estrelas da sorte que esses métodos são de fato virtuais, porque todo o resto do arquivo VirtualPathProviderViewEnginenem sequer é acessível - é privado e isso torna muito irritante substituir a lógica de localização, porque você precisa reescrever metade do código que já está foi escrito se você deseja que ele seja agradável com o cache do local e os formatos do local. Após algumas pesquisas no Reflector, finalmente consegui encontrar uma solução funcional.

O que eu fiz aqui é primeiro criar um resumo AreaAwareViewEngineque deriva diretamente de em VirtualPathProviderViewEnginevez de WebFormViewEngine. Fiz isso para que, se você deseja criar vistas do Spark (ou o que seja), ainda possa usar essa classe como o tipo base.

O código abaixo é bastante longo, para lhe dar um resumo rápido do que ele realmente faz: Permite {2}inserir um no formato do local, que corresponde ao nome da área, da mesma maneira que {1}corresponde ao nome do controlador. É isso aí! É para isso que precisamos escrever todo esse código:

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

Agora, como afirmado, este não é um mecanismo concreto, então você deve criar isso também. Felizmente, esta parte é muito mais fácil, tudo o que precisamos fazer é definir os formatos padrão e criar as visualizações:

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

Observe que adicionamos poucas entradas ao padrão ViewLocationFormats. Essas são as novas {2}entradas, em que a {2}será mapeada para a areaque inserimos no RouteData. Deixei o MasterLocationFormatssozinho, mas obviamente você pode mudar isso, se quiser.

Agora modifique seu global.asaxpara registrar este mecanismo de exibição:

Global.asax.cs

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

... e registre a rota padrão:

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

Agora crie o AreaControllerque acabamos de referenciar:

DefaultController.cs (em ~ / Controladores /)

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

Obviamente, precisamos da estrutura de diretórios e da visualização para acompanhá-la - manteremos isso super simples:

TestView.aspx (em ~ / Areas / AreaZ / Views / Default / ou ~ / Areas / AreaZ / Views / Shared /)

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

E é isso. Finalmente, terminamos .

Para a maior parte, você deve ser capaz de simplesmente pegar o BaseAreaAwareViewEnginee AreaAwareViewEnginee soltá-lo em qualquer projeto MVC, por isso mesmo que ele teve um monte de código para obter este feito, você só tem que escrever uma vez. Depois disso, é apenas uma questão de editar algumas linhas global.asax.cse criar a estrutura do site.


Esta é provavelmente a melhor solução atual, mas longe do ideal. Como acima, depois de adicionar um Actionlink, existe o mesmo problema.
LiamB

1
@Pino: Eu acho que você deve resolver o ActionLinkproblema adicionando o mesmo area = "AreaZ"ao mapeamento de rotas "Padrão" global.asax.cs. Eu não sou positivo; tente e veja.
Aaronaught

No MVC4, a declaração de rota "Padrão" foi movida de Global.asax para ~ / App_Start / RouteConfig.cs / RegisterRoutes ()
Andriy F.

3
Odeio diminuir o voto, mas não acredito que a resposta abaixo de @Chris Alderson não tenha recebido mais votos. É uma solução muito mais simples que esta e parece resolver os casos extremos (ActionLinks, etc).
Jdmcnair

Parece que há um erro aqui. As exibições para uma área chamada "Re", por exemplo, estariam em ~ / Areas / Re / Views / Ctrlr / blah.aspx, mas o código aqui usa ~ / {2} / {1} / {0}, que seria ~ /Re/Ctrl/blah.aspx, sem o diretório Áreas crítico no caminho. Deve ser "~ / Areas / {2} / Views / {1} / {0} .aspx"
Chris Moschini

100

Foi assim que eu fiz. Não sei por que o MapRoute () não permite que você defina a área, mas retorna o objeto de rota para que você possa continuar fazendo as alterações adicionais que desejar. Eu uso isso porque tenho um site MVC modular que é vendido a clientes corporativos e eles precisam poder soltar dlls na pasta bin para adicionar novos módulos. Eu permito que eles alterem o "HomeArea" na configuração do AppSettings.

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

Editar: Você pode tentar isso também em seu AreaRegistration.RegisterArea para a área que você deseja que o usuário vá por padrão. Eu não testei, mas AreaRegistrationContext.MapRoute faz conjuntos route.DataTokens["area"] = this.AreaName;para você.

context.MapRoute(
                    "Home_Default", 
                    "", 
                    new {controller = "Home", action = "index" },
                    new[] { "IPC.Web.Core.Controllers" }
                   );

Funciona. Cuidado com o novo arquivo web.config, ele pode substituir suas configurações globais antigas.
Mert Akcakaya

56

mesmo já foi respondido - esta é a sintaxe curta (ASP.net 3, 4, 5):

routes.MapRoute("redirect all other requests", "{*url}",
    new {
        controller = "UnderConstruction",
        action = "Index"
        }).DataTokens = new RouteValueDictionary(new { area = "Shop" });

6
Isso funciona muito bem para mim. Não tenho controladores na raiz e só uso o Areas. Para o MVC 4, substituí o padrão no RouteConfig.cs. Obrigado!
Marc

2
Estou usando o MVC4 e essa foi a solução mais simples para mim. Permite que o aplicativo use a exibição Índice em uma área específica como a 'página inicial' do site.
JTech

2
Esta solução não funcionará no futuro (do Asp.Net MVC6 e superior).
Patrick Desjardins

@PatrickDesjardins: Algum motivo para não dar suporte à solução acima?
Akash KC 03/03

@SeriousM Você responde é perene. Ainda é útil. Você me salvou uma noite.
Skpaul 18/04/19

16

Agradeço a Aaron por apontar que se trata de localizar os pontos de vista, eu entendi errado.

[UPDATE] Acabei de criar um projeto que envia o usuário para uma área por padrão sem mexer em nenhum dos caminhos de código ou pesquisa:

No global.asax, registre-se como de costume:

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

in Application_Start(), certifique-se de usar a seguinte ordem;

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

no registro da sua área, use

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

Um exemplo pode ser encontrado em http://www.emphess.net/2010/01/31/areas-routes-and-defaults-in-mvc-2-rc/

Eu realmente espero que seja isso que você estava pedindo ...

////

Não acho que escrever um pseudo ViewEngineseja a melhor solução nesse caso. (Sem reputação, não posso comentar). A WebFormsViewEngineárea reconhece e contém o AreaViewLocationFormatsque é definido por padrão como

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

Eu acredito que você não adere a esta convenção. Você postou

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

como um hack de trabalho, mas isso deve ser

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

Se você não deseja seguir a convenção, no entanto, pode optar por um caminho curto, derivando do WebFormViewEngine(que é feito no MvcContrib, por exemplo), onde é possível definir os caminhos de pesquisa no construtor ou -a pouco hacky - especificando sua convenção como esta em Application_Start:

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

Isso deve ser realizado com um pouco mais de cuidado, é claro, mas acho que mostra a ideia. Estes campos são publicem VirtualPathProviderViewEngineno MVC 2 RC.


Vale a pena notar que isso se aplica apenas ao MVC 2 RC - o MVC 1 VirtualPathProviderViewEnginenão possui essa propriedade e não reconhece a área. E, embora essa questão tenha sido afirmada sobre o MVC 2, muitas pessoas ainda não a estão usando (e não serão por algum tempo). Portanto, sua resposta é mais fácil para uma pergunta específica, mas a minha é a única que funcionará para usuários do MVC1 que se deparam com essa pergunta. Gosto de fornecer respostas que não dependem da funcionalidade de pré-lançamento que está potencialmente sujeita a alterações.
Aaronaught 31/01

Além disso, não é um "pseudo mecanismo de exibição" - as classes de mecanismo de exibição foram deliberadamente feitas para serem extensíveis para que diferentes tipos de exibição possam ser usados.
Aaronaught 31/01

Isso não foi para te insultar, desculpe. É 'pseudo', pois não altera significativamente a maneira como as Views são tratadas, mas apenas substitui alguns valores.
Mnemosyn

Não fiquei ofendido, só queria esclarecer o fato de que não é um motivo particularmente incomum derivar um mecanismo de exibição personalizado, como evidenciado pelo fato de que os métodos relevantes são substituíveis.
Aaronaught 31/01

2
Ótima dica sobre RegisterAreasir antes RegisterRoutes. Queria saber por que meu código de repente parou de funcionar e notou que refatorar;)
webnoob

6

Eu acho que você deseja que o usuário seja redirecionado para o ~/AreaZURL uma vez que ele visitou o ~/URL. Eu alcançaria por meio do seguinte código em sua raiz HomeController.

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

E a seguinte rota em Global.asax.

routes.MapRoute(
    "Redirection to AreaZ",
    String.Empty,
    new { controller = "Home ", action = "Index" }
);

Isso funciona, mas muda para o URL no navegador dos usuários. Na verdade, não é o ideal.
LiamB

2

Primeiro, qual versão do MVC2 você está usando? Houve mudanças significativas de preview2 para RC.

Supondo que você use o RC, acho que o mapeamento de rotas deve ter uma aparência diferente. Na AreaRegistration.cssua área, você pode registrar algum tipo de rota padrão, por exemplo,

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

O código acima irá enviar o usuário à MyRouteControllerna nossa ShopAreapor padrão.

O uso de uma string vazia como segundo parâmetro deve gerar uma exceção, porque um controlador deve ser especificado.

É claro que você terá que alterar a rota padrão Global.asaxpara que não interfira com essa rota padrão, por exemplo, usando um prefixo para o site principal.

Veja também este tópico e a resposta de Haack: MVC 2 AreaRegistration Routes Order

Espero que isto ajude.


Obrigado, mas não tenho certeza se isso resolve o problema explicado na pergunta. E seu MVC RC
LiamB

2

Adicionar o seguinte ao meu Application_Start funciona para mim, embora não tenha certeza se você tem essa configuração no 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"
};

1

O que eu fiz para que isso funcionasse é o seguinte:

  1. Eu criei um controlador padrão na pasta raiz / controladores. Chamei meu controlador de DefaultController.
  2. No controlador, adicionei o seguinte código:

    namespace MyNameSpace.Controllers {
    public class DefaultController : Controller {
        // GET: Default
        public ActionResult Index() {
            return RedirectToAction("Index", "ControllerName", new {area = "FolderName"});
        }
    } }
  3. No meu RouterConfig.cs, adicionei o seguinte:

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

O truque por trás de tudo isso é que eu criei um construtor padrão que sempre será o controlador de inicialização toda vez que meu aplicativo for iniciado. Quando ele atinge esse controlador padrão, ele será redirecionado para qualquer controlador que eu especificar na ação padrão do índice. Que no meu caso é

www.myurl.com/FolderName/ControllerName

.


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

Você já tentou isso?


Sim, o problema está no fato de que agora o site procura as visualizações na raiz. A exibição 'ActionY' ou seu mestre não foi encontrado. Os seguintes locais foram pesquisados: ~ / Views / ActionY / ActionY.aspx ~ / Views / ActionY / ActionY.ascx ~ / Views / Shared / ActionY.aspx ~ / Views / Shared / ActionY.ascx
LiamB

2
Compreendo. Vou tentar encontrar uma solução. +1 para a pergunta
Barbaros Alp

0

A localização dos diferentes blocos de construção é feita no ciclo de vida da solicitação. Uma das primeiras etapas do ciclo de vida da solicitação do ASP.NET MVC é mapear a URL solicitada para o método de ação do controlador correto. Esse processo é chamado de roteamento. Uma rota padrão é inicializada no arquivo Global.asax e descreve para a estrutura do ASP.NET MVC como lidar com uma solicitação. Clicar duas vezes no arquivo Global.asax no projeto MvcApplication1 exibirá o seguinte código:

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

}

No manipulador de eventos Application_Start (), que é acionado sempre que o aplicativo é compilado ou o servidor da Web é reiniciado, uma tabela de rota é registrada. A rota padrão é denominada Padrão e responde a um URL no formato http://www.example.com/ {controller} / {action} / {id}. As variáveis ​​entre {e} são preenchidas com valores reais da URL de solicitação ou com os valores padrão, se nenhuma substituição estiver presente na URL. Essa rota padrão será mapeada para o controlador Home e para o método de ação Index, de acordo com os parâmetros de roteamento padrão. Não teremos nenhuma outra ação com este mapa de roteamento.

Por padrão, todos os URLs possíveis podem ser mapeados por essa rota padrão. Também é possível criar nossas próprias rotas. Por exemplo, vamos mapear o URL http://www.example.com/Employee/Maarten para o controlador Employee, a ação Show e o parâmetro firstname. O seguinte snippet de código pode ser inserido no arquivo Global.asax que acabamos de abrir. Como a estrutura do ASP.NET MVC usa a primeira rota correspondente, esse trecho de código deve ser inserido acima da rota padrão; caso contrário, a rota nunca será usada.

routes.MapRoute(

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

);

Agora, vamos adicionar os componentes necessários para esta rota. Primeiro, crie uma classe chamada EmployeeController na pasta Controladores. Você pode fazer isso adicionando um novo item ao projeto e selecionando o modelo MVC Controller Class localizado em Web | Categoria MVC. Remova o método de ação Índice e substitua-o por um método ou ação chamado Mostrar. Este método aceita um parâmetro firstname e passa os dados para o dicionário ViewData. Este dicionário será usado pela visualização para exibir dados.

A classe EmployeeController passará um objeto Employee para a exibição. Essa classe Employee deve ser adicionada na pasta Models (clique com o botão direito nessa pasta e selecione Add | Class no menu de contexto). Aqui está o código para a classe Employee:

namespace MvcApplication1.Models {

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

} 

1
Obrigado, não tenho muita certeza de como isso se relaciona com a definição de uma ÁREA padrão. : - /
LiamB 31/01

0

Bem, enquanto a criação de um mecanismo de exibição personalizado pode funcionar para isso, você ainda pode ter uma alternativa:

  • Decida o que você precisa mostrar por padrão.
  • Que algo tem controlador e ação (e Área), certo?
  • Abra o registro dessa área e adicione algo como isto:
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
}

Felicidades!


Acordado. Embora eu ache que um local mais apropriado para essa definição de rota esteja no arquivo Global.asax.
nuhusky2003

Nesse caso, suas definições global.asax saberiam sobre a existência de um namespace de controlador de área, o que eu acho que não está certo. Áreas são uma funcionalidade adicionada, o que significa que você deve poder adicionar / remover uma sem tocar nas definições global.asax. Na minha abordagem à questão, prefiro uma área para "assumir" a solicitação, em vez de um site [global] para "entregar" a solicitação.
Tengiz

0

A solução aceita para esta pergunta é, embora correta ao resumir como criar um mecanismo de exibição personalizado, não responda a pergunta corretamente. O problema aqui é que Pino está especificando incorretamente sua rota padrão . Particularmente, sua definição de "área" está incorreta. "Área" é verificada através da coleção DataTokens e deve ser adicionada como tal:

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

A "área" especificada no objeto padrão será ignorada . O código acima cria uma rota padrão, que captura solicitações à raiz do site e chama Controlador padrão, ação de índice na área Admin. Observe também que a chave "Namespaces" foi adicionada ao DataTokens; isso só é necessário se você tiver vários controladores com o mesmo nome. Esta solução é verificada com o Mvc2 e o Mvc3 .NET 3.5 / 4.0


-1

ummm, não sei por que toda essa programação, acho que o problema original é resolvido facilmente especificando essa rota padrão ...

routes.MapRoute("Default", "{*id}", 
                 new { controller = "Home"
                     , action = "Index"
                     , id = UrlParameter.Optional 
                     }
              );
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.