ASP.NET MVCで「ビューを検索」するカスタムの場所を指定できますか?


105

私のmvcプロジェクトには次のレイアウトがあります。

  • / Controllers
    • /デモ
    • / Demo / DemoArea1Controller
    • / Demo / DemoArea2Controller
    • 等...
  • / Views
    • /デモ
    • /Demo/DemoArea1/Index.aspx
    • /Demo/DemoArea2/Index.aspx

しかし、私がこれを持っているときDemoArea1Controller

public class DemoArea1Controller : Controller
{
    public ActionResult Index()
    {
        return View();
    }
}

通常の検索場所で「ビュー「インデックス」またはそのマスターが見つかりませんでした」というエラーが表示されます。

「デモ」ビューサブフォルダーの「デモ」名前空間検索でコントローラーを指定するにはどうすればよいですか?


ここでロブ・コネリーのMVCコマースアプリから簡単なViewEngineの別のサンプルは次のとおりです。ビューエンジンコードとGlobal.asax.csコードはViewEngineを設定する:Global.asax.cs希望、このことができます。
ロバートディーン

回答:


121

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;
    }
}

Global.asax.csのApplication_Startメソッドを変更して、ビューエンジンを必ず登録してください。

protected void Application_Start()
{
    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(new CustomViewEngine());
}

ネストされたマスターページからマスターページのパスにアクセスするにはどうすればよいですか?ネストされたマスターページレイアウトを設定して、CustomViewEngineのパス内を検索する場合と同様
Drahcir

6
すでに登録されているエンジンのクリアをスキップして新しいエンジンを追加するだけで、viewLocationsに新しいエンジンのみが含まれるようにした方がよいでしょうか?
Prasanna 2014

3
ViewEngines.Engines.Clear();なしで実装します。すべて正常に動作します。* .cshtmlを使用する場合は、RazorViewEngineから継承する必要があります
KregHEk

「ビューの追加」および「ビューに移動」オプションをコントローラーから新しいビューの場所にリンクする方法はありますか?私はビジュアルスタジオ2012を使用しています
Neville Nazerane 2016年

@Prasannaが述べたように、新しい場所を追加するために既存のエンジンをクリアする必要はありません。詳細については、この回答を参照してください。
Hooman Bahreini

44

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}-エリア名です。

独自の場所のリストを返すか、デフォルトとマージする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());
    });
}

3
私はこれを10票まで投票できたらいいのに。Asp.net 5 / MVC 6でまさに必要なものです。大規模なサイトまたは論理グループのスーパーエリアにエリアをグループ化する場合、私の場合(およびその他の場合)に非常に役立ちます。
2015年

Startup.cs部分は次のようになります。services.Configure <RazorViewEngineOptions>これは、この方法で行くます。public void ConfigureServices(IServiceCollectionサービス)
OrangeKing89

42

実際には、パスをコンストラクタにハードコーディングするよりもはるかに簡単な方法があります。以下は、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);
}

注意すべき点の1つ:カスタムの場所では、ルートにViewStart.cshtmlファイルが必要です。


23

新しいパスを追加するだけの場合は、デフォルトのビューエンジンに追加して、コードのいくつかの行を節約できます。

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


2
ビューの場合:razorEngine.ViewLocationFormatsを使用します。
Aldentev、2015年

13

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();
}

2
かみそりエンジンのタイプが「RazorViewEngine」ではなく「FixedRazorViewEngine」であったことを除いて、これは私にとってはうまくいきました。また、アプリケーションが正常に初期化されないため、エンジンが見つからなかった場合にも例外をスローします。
Rob

3

最後に確認したところ、これには独自の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;
}

1
実際にははるかに簡単です。WebFormsViewEngineをサブクラス化し、コンストラクターで既に検索しているパスの配列に追加します。
Craig Stuntz 2009年

知ってよかった。最後にそのコレクションを変更する必要があったとき、その方法では不可能でした。
ジョエル

"baseControllerNamespace"変数をベースコントローラーの名前空間( "Project.Controllers"など)に設定する必要があることは言及しておきますが、それ以外は投稿から7年後に、私が必要とすることを正確に行いました。
プロトタイプ14

3

このようなものを試してください:

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);
}

3

注:ASP.NET MVC 2の場合、「エリア」のビューに設定する必要がある追加の場所パスがあります。

 AreaViewLocationFormats
 AreaPartialViewLocationFormats
 AreaMasterLocationFormats

Areaのビューエンジンの作成については、Philのブログで説明されています。

注:これはプレビューリリース1用であり、変更される可能性があります。


1

ここでの答えのほとんどは、電話で既存の場所をクリアViewEngines.Engines.Clear()してから、もう一度追加します。これを行う必要はありません。

次に示すように、既存の場所に新しい場所を追加するだけです。

// note that the base class is RazorViewEngine, NOT WebFormViewEngine
public class ExpandedViewEngine : RazorViewEngine
{
    public ExpandedViewEngine()
    {
        var customViewSubfolders = new[] 
        {
            // {1} is conroller name, {0} is action name
            "~/Areas/AreaName/Views/Subfolder1/{1}/{0}.cshtml",
            "~/Areas/AreaName/Views/Subfolder1/Shared/{0}.cshtml"
        };

        var customPartialViewSubfolders = new[] 
        {
            "~/Areas/MyAreaName/Views/Subfolder1/{1}/Partials/{0}.cshtml",
            "~/Areas/MyAreaName/Views/Subfolder1/Shared/Partials/{0}.cshtml"
        };

        ViewLocationFormats = ViewLocationFormats.Union(customViewSubfolders).ToArray();
        PartialViewLocationFormats = PartialViewLocationFormats.Union(customPartialViewSubfolders).ToArray();

        // use the following if you want to extend the master locations
        // MasterLocationFormats = MasterLocationFormats.Union(new[] { "new master location" }).ToArray();   
    }
}

これRazorViewEngineで、Global.asaxで上記を使用するようにプロジェクトを構成できます。

protected void Application_Start()
{
    ViewEngines.Engines.Add(new ExpandedViewEngine());
    // more configurations
}

詳細については、このチュートリアルを参照してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.