web-dev-qa-db-ja.com

カスタムの場所を使用するときにasp.netコアmvc​​でビューの場所を指定する方法は?

属性ベースのルーティングを使用して/ 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を使用しています)に、そのような場所でコントローラーのビューを探すように指示するにはどうすればよいですか?

28
Ron C

ビューロケーションエクスパンダを実装することにより、ビューエンジンがビューを探す場所を拡張できます。アプローチを示すサンプルコードを次に示します。

_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_を介して現在のリクエストのパスにアクセスできます。

44
Ron C

素晴らしいニュース... 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")]

うまくいけば、私が今まで経験していた何時間ものフラストレーションの時間を一部の人々に与えることができます。 :)

67
Brian MacKay

.netコアでは、ビューへのパス全体を指定できます。

return View("~/Views/booking/checkout.cshtml", checkoutRequest);

21
Brian Rizo

これにはカスタム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();
    }
}

これで、アプリケーション構造は次のようになります。

sample project structure

2
Will Ray

そのため、掘り下げた後、別のスタックオーバーフローで問題が見つかったと思います。私は同じ問題を抱えており、非エリアセクションからViewImportsファイルをコピーすると、リンクが予想どおりに機能し始めました。
ここに見られるように: Asp.Netコア2.0 MVCアンカータグヘルパーが機能していません
別の解決策は、ビューレベルでコピーすることです。
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

0
greendave11