Как указать местоположение представления в asp.net core mvc при использовании пользовательских местоположений?
Скажем, у меня есть контроллер, который использует маршрутизацию, основанную на атрибутах, для обработки запрошенного URL-адреса/admin/product следующим образом:
[Route("admin/[controller]")]
public class ProductController: Controller {
// GET: /admin/product
[Route("")]
public IActionResult Index() {
return View();
}
}
Теперь позвольте сказать, что я хочу, чтобы мои представления были организованы в структуре папок, которая приблизительно отражает пути URL, к которым они относятся. Поэтому я хотел бы, чтобы представление для этого контроллера было расположено здесь:
/Views/Admin/Product.cshtml
Чтобы идти дальше, если бы у меня был такой контроллер:
[Route("admin/marketing/[controller]")]
public class PromoCodeListController: Controller {
// GET: /admin/marketing/promocodelist
[Route("")]
public IActionResult Index() {
return View();
}
}
Я хотел бы, чтобы фреймворк автоматически просматривал его здесь:
Views/Admin/Marketing/PromoCodeList.cshtml
В идеале подход для информирования о структуре местоположения представления будет работать в общем режиме на основе информации о маршрутах, основанной на атрибутах, независимо от того, сколько сегментов URL-адресов задействовано (т.е. насколько глубоко оно вложенное).
Как я могу инструктировать структуру Core MVC (я в настоящее время использую RC1), чтобы искать представление контроллера в таком местоположении?
Ответы
Ответ 1
Вы можете расширить местоположения, где механизм представления ищет представления, внедрив расширитель местоположения представления. Вот пример кода для демонстрации подхода:
public class ViewLocationExpander: IViewLocationExpander {
/// <summary>
/// Used to specify the locations that the view engine should search to
/// locate views.
/// </summary>
/// <param name="context"></param>
/// <param name="viewLocations"></param>
/// <returns></returns>
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations) {
//{2} is area, {1} is controller,{0} is the action
string[] locations = new string[] { "/Views/{2}/{1}/{0}.cshtml"};
return locations.Union(viewLocations); //Add mvc default locations after ours
}
public void PopulateValues(ViewLocationExpanderContext context) {
context.Values["customviewlocation"] = nameof(ViewLocationExpander);
}
}
Затем в ConfigureServices(IServiceCollection services)
в файле startup.cs добавьте следующий код, чтобы зарегистрировать его в контейнере IoC. Сделайте это сразу после services.AddMvc();
services.Configure<RazorViewEngineOptions>(options => {
options.ViewLocationExpanders.Add(new ViewLocationExpander());
});
Теперь у вас есть способ добавить любую пользовательскую структуру каталогов, которую вы хотите, в список мест, которые механизм просмотра ищет для представлений и частичных представлений. Просто добавьте его в string[]
locations
string[]
. Кроме того, вы можете поместить файл _ViewImports.cshtml
в тот же каталог или любой родительский каталог, и он будет найден и объединен с вашими представлениями, расположенными в этой новой структуре каталогов.
Обновить:
Хорошая особенность этого подхода заключается в том, что он обеспечивает большую гибкость, чем подход, который был позже представлен в ASP.NET Core 2 (спасибо @BrianMacKay за документирование нового подхода). Так, например, этот подход ViewLocationExpander позволяет не только указывать иерархию путей для поиска видов и областей, но также для макетов и компонентов видов. Также у вас есть доступ к полному ActionContext
чтобы определить, каким может быть соответствующий маршрут. Это обеспечивает большую гибкость и мощность. Так, например, если вы хотите определить подходящее местоположение представления, оценивая путь текущего запроса, вы можете получить доступ к пути текущего запроса через context.ActionContext.HttpContext.Request.Path
.
Ответ 2
Хорошие новости... В ASP.NET Core 2. * вам больше не нужен пользовательский ViewEngine или даже ExpandViewLocations.
Использование пакета OdeToCode.AddFeatureFolders
Это самый простой способ... У К. Скотта Аллена есть пакет nuget для вас в OdeToCode.AddFeatureFolders, который является чистым и включает дополнительную поддержку областей. Github: https://github.com/OdeToCode/AddFeatureFolders
Установите пакет, и это так просто, как:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddFeatureFolders();
...
}
...
}
DIY
Используйте это, если вам нужен очень точный контроль над структурой вашей папки, или если вам не разрешено/вы не хотите получать зависимость по какой-либо причине. Это также довольно легко, хотя, возможно, более загромождено, чем пакет nuget выше:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
services.Configure<RazorViewEngineOptions>(o =>
{
// {2} is area, {1} is controller,{0} is the action
o.ViewLocationFormats.Clear();
o.ViewLocationFormats.Add("/Controllers/{1}/Views/{0}" + RazorViewEngine.ViewExtension);
o.ViewLocationFormats.Add("/Controllers/Shared/Views/{0}" + RazorViewEngine.ViewExtension);
// Untested. You could remove this if you don't care about areas.
o.AreaViewLocationFormats.Clear();
o.AreaViewLocationFormats.Add("/Areas/{2}/Controllers/{1}/Views/{0}" + RazorViewEngine.ViewExtension);
o.AreaViewLocationFormats.Add("/Areas/{2}/Controllers/Shared/Views/{0}" + RazorViewEngine.ViewExtension);
o.AreaViewLocationFormats.Add("/Areas/Shared/Views/{0}" + RazorViewEngine.ViewExtension);
});
...
}
...
}
И это оно! Никаких специальных классов не требуется.
Работа с Решарпер/Райдер
Бонусный совет: если вы используете ReSharper, вы можете заметить, что в некоторых местах ReSharper не может найти ваши взгляды и выдает раздражающие предупреждения. Чтобы обойти это, вытяните пакет Resharper.Annotations и в свой файл startup.cs (или где-либо еще) добавьте один из этих атрибутов для каждого из ваших мест просмотра:
[assembly: AspMvcViewLocationFormat("/Controllers/{1}/Views/{0}.cshtml")]
[assembly: AspMvcViewLocationFormat("/Controllers/Shared/Views/{0}.cshtml")]
[assembly: AspMvcViewLocationFormat("/Areas/{2}/Controllers/{1}/Views/{0}.cshtml")]
[assembly: AspMvcViewLocationFormat("/Controllers/Shared/Views/{0}.cshtml")]
Надеюсь, это сэкономит некоторым людям часы разочарования, которые я только что пережил. :)
Ответ 3
В ядре .net вы можете указать весь путь к представлению.
return View("~/Views/booking/checkout.cshtml", checkoutRequest);
Ответ 4
Для этого вам понадобится пользовательский RazorviewEngine
.
Во-первых, двигатель:
public class CustomEngine : RazorViewEngine
{
private readonly string[] _customAreaFormats = new string[]
{
"/Views/{2}/{1}/{0}.cshtml"
};
public CustomEngine(
IRazorPageFactory pageFactory,
IRazorViewFactory viewFactory,
IOptions<RazorViewEngineOptions> optionsAccessor,
IViewLocationCache viewLocationCache)
: base(pageFactory, viewFactory, optionsAccessor, viewLocationCache)
{
}
public override IEnumerable<string> AreaViewLocationFormats =>
_customAreaFormats.Concat(base.AreaViewLocationFormats);
}
Это создаст дополнительный формат области, который соответствует варианту использования {areaName}/{controller}/{view}
.
Во-вторых, зарегистрируйте двигатель в методе ConfigureServices
класса Startup.cs
:
public void ConfigureServices(IServiceCollection services)
{
// Add custom engine (must be BEFORE services.AddMvc() call)
services.AddSingleton<IRazorViewEngine, CustomEngine>();
// Add framework services.
services.AddMvc();
}
В-третьих, добавьте маршрутизацию области к вашим маршрутам MVC в методе Configure
:
app.UseMvc(routes =>
{
// add area routes
routes.MapRoute(name: "areaRoute",
template: "{area:exists}/{controller}/{action}",
defaults: new { controller = "Home", action = "Index" });
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
Наконец, измените класс ProductController
на использование AreaAttribute
:
[Area("admin")]
public class ProductController : Controller
{
public IActionResult Index()
{
return View();
}
}
Теперь ваша структура приложения может выглядеть так:
![образец структуры проекта]()
Ответ 5
Так что после копания, я думаю, я нашел проблему в другом стеке потока. У меня была та же проблема, и после копирования в файл ViewImports из раздела, не относящегося к области, ссылки начали функционировать, как и ожидалось.
Как видно здесь: Asp.Net Core 2.0 MVC помощник тега привязки не работает
Другое решение было копировать на уровне представления:
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers