Могу ли я указать пользовательское местоположение для “поиска представлений” в ASP.NET MVC?
-
08-07-2019 - |
Вопрос
У меня есть следующий макет для моего проекта mvc:
- /Контроллеры
- /Демо-версия
- /Демонстрационный/DemoArea1Контроллер
- /Демонстрационный/DemoArea2Controller
- и т.д...
- /Просмотры
- /Демо-версия
- /Демо/DemoArea1/Index.aspx
- /Демо/DemoArea2/Index.aspx
Однако, когда у меня есть это для DemoArea1Controller
:
public class DemoArea1Controller : Controller
{
public ActionResult Index()
{
return View();
}
}
Я получаю сообщение об ошибке "Не удалось найти представление 'index' или его master" с обычными местоположениями поиска.
Как я могу указать, что контроллеры в пространстве имен "Demo" ищут во вложенной папке просмотра "Demo"?
Решение
Вы можете легко расширить WebFormViewEngine, чтобы указать все местоположения, в которых вы хотите выполнить поиск:
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;
}
}
Убедитесь, что вы не забыли зарегистрировать механизм просмотра, изменив метод Application_Start в вашем Global.asax.cs
protected void Application_Start()
{
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new CustomViewEngine());
}
Другие советы
Теперь в MVC 6 вы можете реализовать IViewLocationExpander
интерфейс без возни с движками просмотра:
public class MyViewLocationExpander : IViewLocationExpander
{
public void PopulateValues(ViewLocationExpanderContext context) {}
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
{
return new[]
{
"/AnotherPath/Views/{1}/{0}.cshtml",
"/AnotherPath/Views/Shared/{0}.cshtml"
}; // add `.Union(viewLocations)` to add default locations
}
}
где {0}
является именем целевого представления, {1}
- имя контроллера и {2}
- название района.
Вы можете вернуть свой собственный список местоположений, объединив его с default viewLocations
(.Union(viewLocations)
) или просто измените их (viewLocations.Select(path => "/AnotherPath" + path)
).
Чтобы зарегистрировать свой пользовательский расширитель местоположения просмотра в MVC, добавьте следующие строки в ConfigureServices
метод в Startup.cs
файл:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<RazorViewEngineOptions>(options =>
{
options.ViewLocationExpanders.Add(new MyViewLocationExpander());
});
}
На самом деле есть намного более простой метод, чем жесткое кодирование путей в вашем конструкторе.Ниже приведен пример расширения движка Razor для добавления новых путей.В чем я не совсем уверен, так это в том, будут ли пути, которые вы добавляете сюда, кэшироваться:
public class ExtendedRazorViewEngine : RazorViewEngine
{
public void AddViewLocationFormat(string paths)
{
List<string> existingPaths = new List<string>(ViewLocationFormats);
existingPaths.Add(paths);
ViewLocationFormats = existingPaths.ToArray();
}
public void AddPartialViewLocationFormat(string paths)
{
List<string> existingPaths = new List<string>(PartialViewLocationFormats);
existingPaths.Add(paths);
PartialViewLocationFormats = existingPaths.ToArray();
}
}
И ваш 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);
}
Следует отметить одну вещь:для вашего пользовательского местоположения потребуется файл ViewStart.cshtml в его корневом каталоге.
Если вы хотите просто добавить новые пути, вы можете добавить к механизмам просмотра по умолчанию и сэкономить несколько строк кода:
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);
То же самое относится и к WebFormEngine
Вместо того чтобы создавать подкласс RazorViewEngine или полностью заменять его, вы можете просто изменить существующее свойство RazorViewEngine PartialViewLocationFormats .Этот код находится в 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();
}
Последний раз, когда я проверял, для этого требуется, чтобы вы создали свой собственный ViewEngine.Хотя я не знаю, упростили ли они это в RC1.
Основной подход, который я использовал до первого RC, состоял в том, чтобы в моем собственном ViewEngine разделить пространство имен контроллера и искать папки, которые соответствовали частям.
Редактировать:
Вернулся назад и нашел код.Вот общая идея.
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;
}
Попробуйте что-то вроде этого:
private static void RegisterViewEngines(ICollection<IViewEngine> 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);
}
Примечание:для ASP.NET MVC 2 у них есть дополнительные пути к местоположению, которые вам нужно будет установить для представлений в "Областях".
AreaViewLocationFormats
AreaPartialViewLocationFormats
AreaMasterLocationFormats
Создание механизма просмотра для области - это описано в блоге Фила.
Примечание:Это для предварительной версии 1, поэтому может быть изменено.