¿Puedo especificar una ubicación personalizada para “buscar vistas” en ASP.NET MVC?

Tengo el siguiente diseño para mi proyecto mvc:

  • / Controladores
    • /Manifestación
    • / Demo / DemoArea1Controller
    • / Demo / DemoArea2Controller
    • etc …
  • /Puntos de vista
    • /Manifestación
    • /Demo/DemoArea1/Index.aspx
    • /Demo/DemoArea2/Index.aspx

Sin embargo, cuando tengo esto para DemoArea1Controller :

 public class DemoArea1Controller : Controller { public ActionResult Index() { return View(); } } 

Obtengo el error “The view ‘index’ or the master could not find found” con las ubicaciones de búsqueda habituales.

¿Cómo puedo especificar que los controladores en la búsqueda del espacio de nombres “Demo” en la subcarpeta de la vista “Demo”?

Puede extender fácilmente WebFormViewEngine para especificar todas las ubicaciones en las que desea buscar:

 public class CustomViewEngine : WebFormViewEngine { public CustomViewEngine() { var viewLocations = new[] { "~/Views/{1}/{0}.aspx", "~/Views/{1}/{0}.ascx", "~/Views/Shared/{0}.aspx", "~/Views/Shared/{0}.ascx", "~/AnotherPath/Views/{0}.ascx" // etc }; this.PartialViewLocationFormats = viewLocations; this.ViewLocationFormats = viewLocations; } } 

Asegúrese de recordar registrar el motor de vista modificando el método Application_Start en su Global.asax.cs

 protected void Application_Start() { ViewEngines.Engines.Clear(); ViewEngines.Engines.Add(new CustomViewEngine()); } 

En realidad, es mucho más fácil que codificar las rutas en tu constructor. A continuación se muestra un ejemplo de ampliación del motor Razor para agregar nuevas rutas. Una cosa de la que no estoy del todo seguro es si las rutas que agrega aquí se almacenarán en caché:

 public class ExtendedRazorViewEngine : RazorViewEngine { public void AddViewLocationFormat(string paths) { List existingPaths = new List(ViewLocationFormats); existingPaths.Add(paths); ViewLocationFormats = existingPaths.ToArray(); } public void AddPartialViewLocationFormat(string paths) { List existingPaths = new List(PartialViewLocationFormats); existingPaths.Add(paths); PartialViewLocationFormats = existingPaths.ToArray(); } } 

Y su Global.asax.cs

 protected void Application_Start() { ViewEngines.Engines.Clear(); ExtendedRazorViewEngine engine = new ExtendedRazorViewEngine(); engine.AddViewLocationFormat("~/MyThemes/{1}/{0}.cshtml"); engine.AddViewLocationFormat("~/MyThemes/{1}/{0}.vbhtml"); // Add a shared location too, as the lines above are controller specific engine.AddPartialViewLocationFormat("~/MyThemes/{0}.cshtml"); engine.AddPartialViewLocationFormat("~/MyThemes/{0}.vbhtml"); ViewEngines.Engines.Add(engine); AreaRegistration.RegisterAllAreas(); RegisterRoutes(RouteTable.Routes); } 

Una cosa a tener en cuenta: su ubicación personalizada necesitará el archivo ViewStart.cshtml en su raíz.

Ahora en MVC 6 puede implementar la interfaz IViewLocationExpander sin tener que lidiar con los motores de visualización:

 public class MyViewLocationExpander : IViewLocationExpander { public void PopulateValues(ViewLocationExpanderContext context) {} public IEnumerable ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable viewLocations) { return new[] { "/AnotherPath/Views/{1}/{0}.cshtml", "/AnotherPath/Views/Shared/{0}.cshtml" }; // add `.Union(viewLocations)` to add default locations } } 

donde {0} es el nombre de la vista de destino, {1} – nombre del controlador y {2} – nombre del área.

Puede devolver su propia lista de ubicaciones, combinarla con viewLocations defecto ( .Union(viewLocations) ) o simplemente cambiarlas ( viewLocations.Select(path => "/AnotherPath" + path) ).

Para registrar su expansor de ubicación de vista personalizada en MVC, agregue las siguientes líneas al método ConfigureServices en el archivo Startup.cs :

 public void ConfigureServices(IServiceCollection services) { services.Configure(options => { options.ViewLocationExpanders.Add(new MyViewLocationExpander()); }); } 

Si solo desea agregar nuevas rutas, puede agregar a los motores de vista predeterminados y reservar algunas líneas de código:

 ViewEngines.Engines.Clear(); var razorEngine = new RazorViewEngine(); razorEngine.MasterLocationFormats = razorEngine.MasterLocationFormats .Concat(new[] { "~/custom/path/{0}.cshtml" }).ToArray(); razorEngine.PartialViewLocationFormats = razorEngine.PartialViewLocationFormats .Concat(new[] { "~/custom/path/{1}/{0}.cshtml", // {1} = controller name "~/custom/path/Shared/{0}.cshtml" }).ToArray(); ViewEngines.Engines.Add(razorEngine); 

Lo mismo se aplica a WebFormEngine

En lugar de subclasificar RazorViewEngine, o reemplazarlo directamente, puede simplemente modificar la propiedad existente de PartialViewLocationFormats de RazorViewEngine. Este código va en Application_Start:

 System.Web.Mvc.RazorViewEngine rve = (RazorViewEngine)ViewEngines.Engines .Where(e=>e.GetType()==typeof(RazorViewEngine)) .FirstOrDefault(); string[] additionalPartialViewLocations = new[] { "~/Views/[YourCustomPathHere]" }; if(rve!=null) { rve.PartialViewLocationFormats = rve.PartialViewLocationFormats .Union( additionalPartialViewLocations ) .ToArray(); } 

Lo último que revisé, esto requiere que construyas tu propio ViewEngine. No sé si lo hicieron más fácil en RC1 sin embargo.

El enfoque básico que utilicé antes del primer RC fue, en mi propio ViewEngine, dividir el espacio de nombres del controlador y buscar carpetas que coincidieran con las partes.

EDITAR:

Volvimos y encontramos el código. Aquí está la idea general.

 public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName) { string ns = controllerContext.Controller.GetType().Namespace; string controller = controllerContext.Controller.GetType().Name.Replace("Controller", ""); //try to find the view string rel = "~/Views/" + ( ns == baseControllerNamespace ? "" : ns.Substring(baseControllerNamespace.Length + 1).Replace(".", "/") + "/" ) + controller; string[] pathsToSearch = new string[]{ rel+"/"+viewName+".aspx", rel+"/"+viewName+".ascx" }; string viewPath = null; foreach (var path in pathsToSearch) { if (this.VirtualPathProvider.FileExists(path)) { viewPath = path; break; } } if (viewPath != null) { string masterPath = null; //try find the master if (!string.IsNullOrEmpty(masterName)) { string[] masterPathsToSearch = new string[]{ rel+"/"+masterName+".master", "~/Views/"+ controller +"/"+ masterName+".master", "~/Views/Shared/"+ masterName+".master" }; foreach (var path in masterPathsToSearch) { if (this.VirtualPathProvider.FileExists(path)) { masterPath = path; break; } } } if (string.IsNullOrEmpty(masterName) || masterPath != null) { return new ViewEngineResult( this.CreateView(controllerContext, viewPath, masterPath), this); } } //try default implementation var result = base.FindView(controllerContext, viewName, masterName); if (result.View == null) { //add the location searched return new ViewEngineResult(pathsToSearch); } return result; } 

Pruebe algo como esto:

 private static void RegisterViewEngines(ICollection engines) { engines.Add(new WebFormViewEngine { MasterLocationFormats = new[] {"~/App/Views/Admin/{0}.master"}, PartialViewLocationFormats = new[] {"~/App/Views/Admin//{1}/{0}.ascx"}, ViewLocationFormats = new[] {"~/App/Views/Admin//{1}/{0}.aspx"} }); } protected void Application_Start() { RegisterViewEngines(ViewEngines.Engines); } 

Nota: para ASP.NET MVC 2 tienen rutas de ubicación adicionales que deberá configurar para las vistas en ‘Áreas’.

  AreaViewLocationFormats AreaPartialViewLocationFormats AreaMasterLocationFormats 

La creación de un motor de visualización para un Área se describe en el blog de Phil .

Nota: Esto es para la versión de vista previa 1 por lo que está sujeto a cambios.