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 FindPartialView
e FindView
na VirtualPathProviderViewEngine
classe.
Você pode agradecer às estrelas da sorte que esses métodos são de fato virtuais, porque todo o resto do arquivo VirtualPathProviderViewEngine
nem 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 AreaAwareViewEngine
que deriva diretamente de em VirtualPathProviderViewEngine
vez 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 area
que inserimos no RouteData
. Deixei o MasterLocationFormats
sozinho, mas obviamente você pode mudar isso, se quiser.
Agora modifique seu global.asax
para 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 AreaController
que 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 BaseAreaAwareViewEngine
e AreaAwareViewEngine
e 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.cs
e criar a estrutura do site.
ActionLink
problema adicionando o mesmoarea = "AreaZ"
ao mapeamento de rotas "Padrão"global.asax.cs
. Eu não sou positivo; tente e veja.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.
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ê.fonte
mesmo já foi respondido - esta é a sintaxe curta (ASP.net 3, 4, 5):
fonte
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:
in
Application_Start()
, certifique-se de usar a seguinte ordem;no registro da sua área, use
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
ViewEngine
seja a melhor solução nesse caso. (Sem reputação, não posso comentar). AWebFormsViewEngine
área reconhece e contém oAreaViewLocationFormats
que é definido por padrão comoEu acredito que você não adere a esta convenção. Você postou
como um hack de trabalho, mas isso deve ser
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 emApplication_Start
:Isso deve ser realizado com um pouco mais de cuidado, é claro, mas acho que mostra a ideia. Estes campos são
public
emVirtualPathProviderViewEngine
no MVC 2 RC.fonte
VirtualPathProviderViewEngine
nã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.RegisterAreas
ir antesRegisterRoutes
. Queria saber por que meu código de repente parou de funcionar e notou que refatorar;)Eu acho que você deseja que o usuário seja redirecionado para o
~/AreaZ
URL uma vez que ele visitou o~/
URL. Eu alcançaria por meio do seguinte código em sua raizHomeController
.E a seguinte rota em
Global.asax
.fonte
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.cs
sua área, você pode registrar algum tipo de rota padrão, por exemplo,O código acima irá enviar o usuário à
MyRouteController
na nossaShopArea
por 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.asax
para 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.
fonte
Adicionar o seguinte ao meu Application_Start funciona para mim, embora não tenha certeza se você tem essa configuração no RC:
fonte
O que eu fiz para que isso funcionasse é o seguinte:
No controlador, adicionei o seguinte código:
No meu RouterConfig.cs, adicionei o seguinte:
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 é
.
fonte
Você já tentou isso?
fonte
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:
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.
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:
fonte
Bem, enquanto a criação de um mecanismo de exibição personalizado pode funcionar para isso, você ainda pode ter uma alternativa:
Felicidades!
fonte
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:
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
fonte
ummm, não sei por que toda essa programação, acho que o problema original é resolvido facilmente especificando essa rota padrão ...
fonte