属性ベースのルーティングを使用して/ admin/productの要求されたURLを処理するコントローラーがあるとします。
[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を使用しています)に、そのような場所でコントローラーのビューを探すように指示するにはどうすればよいですか?
ビューロケーションエクスパンダを実装することにより、ビューエンジンがビューを探す場所を拡張できます。アプローチを示すサンプルコードを次に示します。
_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
_ファイルを同じディレクトリまたは親ディレクトリに配置すると、この新しいディレクトリ構造にあるビューが検出され、ビューとマージされます。
更新:
このアプローチの優れた点の1つは、ASP.NET Core 2で導入されたアプローチよりも柔軟性が高いことです(新しいアプローチを文書化してくれた@BrianMacKayに感謝します)。そのため、たとえばこのViewLocationExpanderアプローチでは、ビューの階層を指定するだけでなく、ビューと領域だけでなく、レイアウトとビューコンポーネントも検索できます。また、完全なActionContext
にアクセスして、適切なルートを判断することもできます。これにより、多くの柔軟性とパワーが提供されます。したがって、たとえば、現在のリクエストのパスを評価して適切なビューの場所を決定したい場合、_context.ActionContext.HttpContext.Request.Path
_を介して現在のリクエストのパスにアクセスできます。
素晴らしいニュース... ASP.NET Core 2. *では、カスタムViewEngineもExpandViewLocationsも必要ありません。
OdeToCode.AddFeatureFoldersパッケージの使用
これが最も簡単な方法です... K. Scott Allenには、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/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")]
うまくいけば、私が今まで経験していた何時間ものフラストレーションの時間を一部の人々に与えることができます。 :)
.netコアでは、ビューへのパス全体を指定できます。
return View("~/Views/booking/checkout.cshtml", checkoutRequest);
これにはカスタム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}
の使用例に一致する追加の領域形式が作成されます。
次に、Startup.cs
クラスのConfigureServices
メソッドにエンジンを登録します。
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();
}
}
これで、アプリケーション構造は次のようになります。
そのため、掘り下げた後、別のスタックオーバーフローで問題が見つかったと思います。私は同じ問題を抱えており、非エリアセクションからViewImportsファイルをコピーすると、リンクが予想どおりに機能し始めました。
ここに見られるように: Asp.Netコア2.0 MVCアンカータグヘルパーが機能していません
別の解決策は、ビューレベルでコピーすることです。@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers