假设我有一个控制器使用基于属性的路由来处理请求的/ admin / product网址,如下所示:
[Route("admin/[controller]")]
public class ProductController: Controller {
// GET: /admin/product
[Route("")]
public IActionResult Index() {
return View();
}
}
现在让我们说我希望将我的观点整理成一个大致反映与之相关的网址路径的文件夹结构。所以我希望这个控制器的视图位于这里:
/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
理想情况下,通知视图位置框架的方法将基于基于属性的路径信息以一般方式工作,无论涉及多少网段(即嵌套的深度)。
如何指示Core MVC框架(我目前正在使用RC1)在这样的位置查找控制器的视图?
答案 0 :(得分:63)
好消息......在ASP.NET Core 2. *中,您不再需要自定义ViewEngine甚至ExpandViewLocations。
使用OdeToCode.AddFeatureFolders包
这是最简单的方法...... K. Scott Allen在OdeToCode.AddFeatureFolders上为您提供了一个nuget包,它是干净的,包括对区域的可选支持。 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 / Rider打交道
额外提示:如果您正在使用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")]
希望这让一些人免于我刚刚度过的沮丧时光。 :)
答案 1 :(得分:44)
您可以通过实现视图位置扩展器来展开视图引擎查找视图的位置。以下是一些演示该方法的示例代码:
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);
}
}
然后在startup.cs文件的ConfigureServices(IServiceCollection services)
方法中添加以下代码以将其注册到IoC容器。在services.AddMvc();
services.Configure<RazorViewEngineOptions>(options => {
options.ViewLocationExpanders.Add(new ViewLocationExpander());
});
现在,您可以将所需的任何自定义目录结构添加到视图引擎查找视图和部分视图的位置列表中。只需将其添加到locations
string[]
即可。此外,您可以将_ViewImports.cshtml
文件放在同一目录或任何父目录中,它将被找到并与您在此新目录结构中的视图合并。
更新:
这种方法的一个好处是它提供了比后来在ASP.NET Core 2中引入的方法更多的灵活性(感谢@BrianMacKay记录新方法)。因此,例如,此ViewLocationExpander方法不仅允许指定搜索视图和区域的路径层次结构,还允许指定布局和视图组件。您还可以访问完整的ActionContext
以确定适当的路由。这提供了很多灵活性和功率。因此,例如,如果您想通过评估当前请求的路径来确定适当的视图位置,则可以通过context.ActionContext.HttpContext.Request.Path
访问当前请求的路径。
答案 2 :(得分:20)
在.net核心中,您可以指定视图的整个路径。
return View("~/Views/booking/checkout.cshtml", checkoutRequest);
答案 3 :(得分:2)
您需要为此自定义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();
}
第三,使用Configure
方法将区域路由添加到MVC路由:
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();
}
}
现在,您的应用程序结构如下所示:
答案 4 :(得分:1)
我正在使用核心3.1,只需在Startup.cs内的ConfigureServices方法内执行此操作即可。
services.AddControllersWithViews().AddRazorOptions(
options => {// Add custom location to view search location
options.ViewLocationFormats.Add("/Views/Shared/YourLocation/{0}.cshtml");
});
{0}只是视图名称的占位符。 很好而且很简单。
答案 5 :(得分:0)
因此,在进行挖掘之后,我认为我在另一个stackoverflow上发现了该问题。
我遇到了同样的问题,并且从非区域部分复制了ViewImports文件后,链接开始按预期运行。
如此处所示:Asp.Net core 2.0 MVC anchor tag helper not working
另一个解决方案是在视图级别进行复制:
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
答案 6 :(得分:0)
尽管上面的答案可能是正确的,但我想添加一些“基本”的内容:
-MVC .NET中存在(很多)隐式路由行为
-您也可以使所有内容明确
那么,这对.NET MVC如何起作用?
默认
-默认的“路由”为protocol:// server:port /,例如http://localhost:607888/ 如果您没有任何带有显式路由的控制器,并且未定义任何启动默认值,则将无法使用。 这将:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Special}/{action=Index}");
});
控制器路由
如果您使用Index()方法添加类 Special Controller:Controller ,则您的http://localhost:.../就到了。 注意:名称控制器=>后修复控制器被忽略,隐式命名约定
如果要在控制器上显式定义路由,请使用以下命令:
[Route("Special")]//explicit route
public class SpecialController : Controller
{ ....
=> http://localhost:<port>/Special will end up on this controller
要将http请求映射到控制器方法,还可以添加显式 [Route(...)]信息传递给您的方法:
// GET: explicit route page
[HttpGet("MySpecialIndex")]
public ActionResult Index(){...}
=> http://localhost:<port>/Special/MySpecialIndex will end up on SpecialController.Index()
查看路线
现在假设您的Views文件夹是这样的:
Views\
Special1\
Index1.cshtml
Special\
Index.cshtml
Controller如何“找到”视图的方式? 此处的示例是
[Route("Special")]//explicit route
public class Special1Controller : Controller
{
// GET: Default route page
[HttpGet]
public ActionResult Index()
{
//
// Implicit path, implicit view name: Special1<Controller> -> View = Views/Special/Index.cshtml
//
//return View();
//
// Implicit path, explicit view name, implicit extention
// Special <Controller> -> View = Views/Special/Index.cshtml
//
//return View("Index");
//
// Everything explcit
//
return View("Views/Special1/Index1.cshtml");
}
所以,我们有:
return View(); =>所有隐式的内容,将“方法名称”作为视图,将控制器路径作为视图路径等。 http:// <>:<> /特殊 =>方法= Index(),视图= /Views/Special/Index.cshtml
return View(“ Index”); //显式视图名称,隐式路径和扩展 =>方法= Special1Controller.Index(),视图= /Views/Special/Index.cshtml
return View(“ Views / Special1 / Index1.cshtml”); //方法隐式,显式查看 => http:// <>:<> / Special,方法= Special1Controller.Index(),视图= /Views/Special1/Index1.cshtml
如果将显式映射结合到方法和视图: => http:// <>:<> / Special / MySpecialIndex,方法= Special1Controller.Index(),视图= /Views/Special1/Index1.cshtml
那么,最后,为什么要使所有内容隐含? 优点是管理较少,容易出错,因此您在文件夹的命名和设置中强制执行一些简洁的管理 缺点是正在发生很多魔术,每个人都需要理解。
那你为什么要把一切都明确化? 优点:这对于“每个人”都更易读。无需知道所有隐式规则。更加灵活地更改路线和地图。控制器和路由路径之间发生冲突的机会也要少一些。
最后:当然,您可以混合使用显式和隐式路由。
我的偏好将是所有明确的内容。为什么?我喜欢显式映射和关注点分离。类名和方法名可以具有命名约定,而不会干扰您的请求命名约定。 例如。假设我的类/方法是camelCase,我的查询是小写的,那么就可以很好地工作:http://..:../whatever/something和ControllerX.someThing(请记住,Windows是不区分大小写的,Linux是已知的!而且现代.netcore Docker组件也是如此。可能最终会在Linux平台上使用!) 我也不喜欢带有X000行代码的“大单调”类。通过为控制器分配明确的相同http查询路由,可以拆分控制器而不是完美地执行查询。 底线:了解其工作原理,并明智地选择策略!
答案 7 :(得分:0)
根据这个问题,我认为值得一提的是当您在路线中使用区域时如何这样做。
我将大部分答案归功于@Mike的答案。
就我而言,我有一个名称与区域名称匹配的控制器。我使用自定义约定将控制器的名称更改为“ Home”,以便可以在{area}/{controller=Home}/{action=Index}/{id?}
中创建默认路由MapControllerRoute
。
我之所以落在这个SO问题上是因为现在Razor不在搜索原始控制器的名称视图文件夹,因此找不到我的视图。
我只需要将这段代码添加到ConfigureServices
(这里的区别是使用AreaViewLocationFormats
):
services.AddMvc().AddRazorOptions(options =>
options.AreaViewLocationFormats.Add("/Areas/{2}/Views/{2}/{0}" + RazorViewEngine.ViewExtension));
// as already noted, {0} = action name, {1} = controller name, {2} = area name