Este me interesó, y finalmente tuve la oportunidad de investigarlo. Otras personas aparentemente no han entendido que este es un problema con la búsqueda de la vista , no un problema con el enrutamiento sí mismo, y eso es probablemente porque el título de su pregunta indica que se trata de enrutamiento.
En cualquier caso, debido a que este es un problema relacionado con la Vista, la única forma de obtener lo que desea es anular el motor de vista predeterminado . Normalmente, cuando haces esto, es con el simple propósito de cambiar tu motor de visualización (es decir, a Spark, NHaml, etc.). En este caso, no es la lógica de creación de vistas lo que necesitamos anular, sino los métodos FindPartialView
y FindView
en la VirtualPathProviderViewEngine
clase.
Puede gracias a su buena suerte que estos métodos son en realidad virtual, porque todo lo demás en el VirtualPathProviderViewEngine
ni siquiera es accesible - que es privado, y eso hace que sea muy molesto anular la lógica de búsqueda porque básicamente tiene que reescribir la mitad del código que ya está disponible. escrito si quieres que se vea bien con el caché de ubicación y los formatos de ubicación. Después de investigar un poco en Reflector, finalmente logré encontrar una solución de trabajo.
Lo que he hecho aquí es crear primero un resumen AreaAwareViewEngine
que se derive directamente de en VirtualPathProviderViewEngine
lugar de WebFormViewEngine
. Hice esto para que si desea crear vistas de Spark en su lugar (o lo que sea), aún pueda usar esta clase como el tipo base.
El siguiente código es bastante largo, por lo que para darle un resumen rápido de lo que realmente hace: le permite poner un {2}
formato de ubicación, que corresponde al nombre del área, de la misma manera{1}
corresponde al nombre del controlador. ¡Eso es! Para eso tuvimos que escribir todo este 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;
}
}
Ahora, como se dijo, este no es un motor concreto, por lo que también debe crearlo. Afortunadamente, esta parte es mucho más fácil, todo lo que tenemos que hacer es establecer los formatos predeterminados y crear las vistas:
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);
}
}
Tenga en cuenta que hemos agregado algunas entradas al estándar ViewLocationFormats
. Estas son las nuevas {2}
entradas, donde {2}
serán asignadas a las que area
colocamos en RouteData
. Lo dejé MasterLocationFormats
solo, pero obviamente puedes cambiar eso si quieres.
Ahora modifique su global.asax
para registrar este motor de vista:
Global.asax.cs
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new AreaAwareViewEngine());
}
... y registra la ruta predeterminada:
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 = "" }
);
}
Ahora cree el AreaController
que acabamos de referenciar:
DefaultController.cs (en ~ / Controllers /)
public class DefaultController : Controller
{
public ActionResult ActionY()
{
return View("TestView");
}
}
Obviamente, necesitamos la estructura del directorio y la vista para acompañarlo; mantendremos esto súper simple:
TestView.aspx (en ~ / Areas / AreaZ / Views / Default / o ~ / Areas / AreaZ / Views / Shared /)
<%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
<h2>TestView</h2>
This is a test view in AreaZ.
Y eso es. Finalmente hemos terminado .
En su mayor parte, usted debería ser capaz de tomar sólo la BaseAreaAwareViewEngine
e AreaAwareViewEngine
y colocarlo en cualquier proyecto de MVC, por lo que a pesar de que tomó una gran cantidad de código para hacer esto, sólo tiene que escribir una vez. Después de eso, solo es cuestión de editar algunas líneas global.asax.cs
y crear la estructura de su sitio.
ActionLink
problema agregando lo mismoarea = "AreaZ"
a la asignación de ruta "predeterminada"global.asax.cs
. Sin embargo, no soy positivo; Pruébalo y verás.Así es como lo hice. No sé por qué MapRoute () no le permite establecer el área, pero sí devuelve el objeto de ruta para que pueda continuar haciendo los cambios adicionales que desee. Utilizo esto porque tengo un sitio MVC modular que se vende a clientes empresariales y necesitan poder colocar archivos dlls en la carpeta bin para agregar nuevos módulos. Les permito cambiar el "HomeArea" en la configuración de AppSettings.
Editar: también puede probar esto en su AreaRegistration.RegisterArea para el área que desea que el usuario vaya de forma predeterminada. No lo he probado, pero AreaRegistrationContext.MapRoute lo configura
route.DataTokens["area"] = this.AreaName;
para usted.fuente
incluso ya fue respondida: esta es la sintaxis corta (ASP.net 3, 4, 5):
fuente
Gracias a Aaron por señalar que se trata de localizar las vistas, no entendí eso.
[ACTUALIZACIÓN] Acabo de crear un proyecto que envía al usuario a un Área por defecto sin meterse con ninguno de los códigos o rutas de búsqueda:
En global.asax, regístrese como de costumbre:
en
Application_Start()
, asegúrese de usar el siguiente orden;en su registro de área, use
Se puede encontrar un ejemplo en http://www.emphess.net/2010/01/31/areas-routes-and-defaults-in-mvc-2-rc/
Realmente espero que esto sea lo que estabas pidiendo ...
////
No creo que escribir un pseudo
ViewEngine
sea la mejor solución en este caso. (Sin reputación, no puedo comentar). ElWebFormsViewEngine
área es consciente y contiene elAreaViewLocationFormats
que se define por defecto comoCreo que no te adhieres a esta convención. Publicaste
como un truco funcional, pero eso debería ser
SI no desea seguir la convención, sin embargo, es posible que desee tomar un camino corto derivando del
WebFormViewEngine
(que se hace en MvcContrib, por ejemplo) donde puede establecer las rutas de búsqueda en el constructor, o -a little hacky- especificando tu convención como esta enApplication_Start
:Esto debería realizarse con un poco más de cuidado, por supuesto, pero creo que muestra la idea. Estos campos están
public
enVirtualPathProviderViewEngine
en MVC 2 RC.fuente
VirtualPathProviderViewEngine
no tiene esta propiedad y no tiene en cuenta el área. Y si bien se dijo que esta pregunta era sobre MVC 2, mucha gente todavía no la usa (y no lo hará por algún tiempo). Entonces, su respuesta es más fácil para la pregunta específica, pero la mía es la única que funcionará para los usuarios de MVC1 que se topan con esta pregunta. Me gusta proporcionar respuestas que no dependen de la funcionalidad de prelanzamiento que está potencialmente sujeta a cambios.RegisterAreas
ir antesRegisterRoutes
. Me preguntaba por qué mi código dejó de funcionar repentinamente y noté ese refactor;)Supongo que desea que el usuario sea redirigido a la
~/AreaZ
URL una vez que haya visitado la~/
URL. Lo lograría mediante el siguiente código dentro de su raízHomeController
.Y la siguiente ruta en
Global.asax
.fuente
Primero, ¿qué versión de MVC2 estás usando? Ha habido cambios significativos de preview2 a RC.
Suponiendo que use el RC, creo que su mapeo de rutas debería tener un aspecto diferente. En
AreaRegistration.cs
su área, puede registrar algún tipo de ruta predeterminada, p. Ej.El código anterior enviará al usuario a
MyRouteController
nuestroShopArea
por defecto.El uso de una cadena vacía como segundo parámetro debería generar una excepción, ya que se debe especificar un controlador.
Por supuesto, tendrá que cambiar la ruta predeterminada en
Global.asax
para que no interfiera con esta ruta predeterminada, por ejemplo, utilizando un prefijo para el sitio principal.Consulte también este hilo y la respuesta de Haack: Orden de rutas de registro de área MVC 2
Espero que esto ayude.
fuente
Agregar lo siguiente a mi Application_Start funciona para mí, aunque no estoy seguro si tiene esta configuración en RC:
fuente
Lo que hice para que esto funcione es lo siguiente:
En el controlador agregué el siguiente código:
En mi RouterConfig.cs agregué lo siguiente:
El truco detrás de todo esto es que hice un constructor predeterminado que siempre será el controlador de inicio cada vez que se inicie mi aplicación. Cuando llegue a ese controlador predeterminado, redirigirá a cualquier controlador que especifique en la Acción de índice predeterminada. Que en mi caso es
.
fuente
¿Has intentado eso?
fuente
La localización de los diferentes bloques de construcción se realiza en el ciclo de vida de la solicitud. Uno de los primeros pasos en el ciclo de vida de la solicitud ASP.NET MVC es asignar la URL solicitada al método de acción del controlador correcto. Este proceso se conoce como enrutamiento. Una ruta predeterminada se inicializa en el archivo Global.asax y describe al marco MVC de ASP.NET cómo manejar una solicitud. Al hacer doble clic en el archivo Global.asax en el proyecto MvcApplication1, se mostrará el siguiente código:
En el controlador de eventos Application_Start (), que se activa cada vez que se compila la aplicación o se reinicia el servidor web, se registra una tabla de ruta. La ruta predeterminada se llama Predeterminada y responde a una URL en forma de http://www.example.com/ {controller} / {action} / {id}. Las variables entre {y} se rellenan con valores reales de la URL de solicitud o con los valores predeterminados si no hay anulación en la URL. Esta ruta predeterminada se asignará al controlador Home y al método de acción Index, de acuerdo con los parámetros de ruta predeterminados. No tendremos ninguna otra acción con este mapa de enrutamiento.
De forma predeterminada, todas las URL posibles se pueden asignar a través de esta ruta predeterminada. También es posible crear nuestras propias rutas. Por ejemplo, vamos a asignar la URL http://www.example.com/Employee/Maarten al controlador de Empleado, la acción Mostrar y el parámetro de nombre. El siguiente fragmento de código se puede insertar en el archivo Global.asax que acabamos de abrir. Dado que el marco ASP.NET MVC utiliza la primera ruta coincidente, este fragmento de código debe insertarse encima de la ruta predeterminada; de lo contrario, la ruta nunca se utilizará.
Ahora, agreguemos los componentes necesarios para esta ruta. En primer lugar, cree una clase llamada EmployeeController en la carpeta Controllers. Puede hacer esto agregando un nuevo elemento al proyecto y seleccionando la plantilla MVC Controller Class ubicada en la Web | Categoría MVC. Elimine el método de acción Índice y reemplácelo con un método o acción llamado Mostrar. Este método acepta un parámetro de nombre y pasa los datos al diccionario ViewData. La vista utilizará este diccionario para mostrar datos.
La clase EmployeeController pasará un objeto Employee a la vista. Esta clase de Empleado debe agregarse en la carpeta Modelos (haga clic con el botón derecho en esta carpeta y luego seleccione Agregar | Clase en el menú contextual). Aquí está el código para la clase Employee:
fuente
Bueno, si bien crear un motor de vista personalizado puede funcionar para esto, aún puede tener una alternativa:
¡Salud!
fuente
La solución aceptada para esta pregunta es, si bien es correcta al resumir cómo crear un motor de vista personalizado, no responde la pregunta correctamente. El problema aquí es que Pino está especificando incorrectamente su ruta predeterminada . Particularmente su definición de "área" es incorrecta. El "Área" se verifica a través de la colección DataTokens y debe agregarse como tal:
El "área" especificada en el objeto predeterminado será ignorado . El código anterior crea una ruta predeterminada, que detecta las solicitudes a la raíz de su sitio y luego llama al controlador predeterminado, acción de índice en el área de administración. Tenga en cuenta también la clave "Espacios de nombres" que se agrega a DataTokens, esto solo es necesario si tiene varios controladores con el mismo nombre. Esta solución se verifica con Mvc2 y Mvc3 .NET 3.5 / 4.0
fuente
mmm, no sé por qué toda esta programación, creo que el problema original se resuelve fácilmente especificando esta ruta predeterminada ...
fuente