Dapatkah saya menentukan lokasi khusus untuk "mencari tampilan" di ASP.NET MVC?

105

Saya memiliki tata letak berikut untuk proyek mvc saya:

  • / Pengendali
    • / Demo
    • / Demo / DemoArea1Controller
    • / Demo / DemoArea2Controller
    • dll ...
  • / Tampilan
    • / Demo
    • /Demo/DemoArea1/Index.aspx
    • /Demo/DemoArea2/Index.aspx

Namun, ketika saya memiliki ini untuk DemoArea1Controller:

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

Saya mendapatkan kesalahan "Tampilan 'indeks' atau masternya tidak dapat ditemukan", dengan lokasi pencarian biasa.

Bagaimana cara menentukan pengontrol itu dalam pencarian namespace "Demo" di subfolder tampilan "Demo"?

Daniel Schaffer
sumber
Berikut adalah contoh ViewEngine sederhana dari aplikasi MVC Commerce Rob Connery: Lihat Kode Mesin Dan kode Global.asax.cs untuk menyetel ViewEngine: Global.asax.cs Semoga ini bisa membantu.
Robert Dean

Jawaban:

121

Anda dapat dengan mudah memperluas WebFormViewEngine untuk menentukan semua lokasi yang ingin Anda lihat:

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

Pastikan Anda ingat untuk mendaftarkan mesin tampilan dengan mengubah metode Application_Start di Global.asax.cs Anda

protected void Application_Start()
{
    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(new CustomViewEngine());
}
Sam Wessel
sumber
Bagaimana Anda bisa mengakses jalur Halaman Master dari halaman Master Bersarang? Seperti dalam mengatur tata letak halaman master bersarang untuk mencari dalam jalur CustomViewEngine
Drahcir
6
Bukankah lebih baik jika kita melewatkan Kliring mesin yang sudah terdaftar dan hanya menambahkan yang baru dan viewLocations hanya akan memiliki yang baru?
Prasanna
3
Menerapkan tanpa ViewEngines.Engines.Clear (); Semua bekerja dengan baik. Jika Anda ingin menggunakan * .cshtml Anda harus mewarisi dari RazorViewEngine
KregHEk
adakah cara agar kita dapat menautkan opsi "tambahkan tampilan" dan "buka tampilan" dari pengontrol ke lokasi tampilan baru? saya menggunakan studio visual 2012
Neville Nazerane
Seperti yang disebutkan oleh @Prasanna, tidak perlu membersihkan mesin yang ada untuk menambah lokasi baru, lihat jawaban ini untuk lebih jelasnya.
Hooman Bahreini
45

Sekarang di MVC 6 Anda dapat mengimplementasikan IViewLocationExpanderantarmuka tanpa mengotak-atik mesin tampilan:

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

dimana {0}nama tampilan target, {1}- nama pengontrol dan {2}- nama area.

Anda dapat mengembalikan daftar lokasi Anda sendiri, menggabungkannya dengan default viewLocations( .Union(viewLocations)) atau hanya mengubahnya ( viewLocations.Select(path => "/AnotherPath" + path)).

Untuk mendaftarkan expander lokasi tampilan kustom Anda di MVC, tambahkan baris berikutnya ke ConfigureServicesmetode dalam Startup.csfile:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<RazorViewEngineOptions>(options =>
    {
        options.ViewLocationExpanders.Add(new MyViewLocationExpander());
    });
}
whyleee
sumber
3
Saya berharap saya dapat memilih ini hingga 10 suara. Persis seperti yang dibutuhkan di Asp.net 5 / MVC 6. Cantik. Sangat berguna dalam kasus saya (dan lainnya) ketika Anda ingin mengelompokkan area menjadi super area untuk situs yang lebih besar atau pengelompokan logis.
diundi
Bagian Startup.cs harus: services.Configure <RazorViewEngineOptions> Ini berjalan dalam metode ini: public void ConfigureServices (IServiceCollection services)
OrangeKing89
42

Sebenarnya ada metode yang jauh lebih mudah daripada melakukan hardcode jalur ke konstruktor Anda. Di bawah ini adalah contoh memperluas mesin Razor untuk menambahkan jalur baru. Satu hal yang saya tidak sepenuhnya yakin adalah apakah jalur yang Anda tambahkan di sini akan disimpan dalam cache:

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

Dan Global.asax.cs Anda

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

Satu hal yang perlu diperhatikan: lokasi khusus Anda akan membutuhkan file ViewStart.cshtml di akarnya.

Chris S
sumber
23

Jika Anda hanya ingin menambahkan jalur baru, Anda dapat menambahkan ke mesin tampilan default dan menyisihkan beberapa baris kode:

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

Hal yang sama berlaku untuk WebFormEngine

Marcelo De Zen
sumber
2
Untuk Views: gunakan razorEngine.ViewLocationFormats.
Aldentev
13

Daripada membuat subclass RazorViewEngine, atau menggantinya secara langsung, Anda bisa mengubah properti PartialViewLocationFormats RazorViewEngine yang ada. Kode ini masuk di 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();
}
Simon Giles
sumber
2
Ini berfungsi untuk saya, dengan pengecualian bahwa jenis mesin pisau cukur adalah 'FixedRazorViewEngine' bukan 'RazorViewEngine'. Juga saya melempar pengecualian jika mesin tidak ditemukan karena mencegah aplikasi saya berhasil diinisialisasi.
Rob
3

Terakhir saya periksa, ini mengharuskan Anda untuk membangun ViewEngine Anda sendiri. Saya tidak tahu apakah mereka membuatnya lebih mudah di RC1.

Pendekatan dasar yang saya gunakan sebelum RC pertama adalah, di ViewEngine saya sendiri, untuk membagi namespace pengontrol dan mencari folder yang cocok dengan bagian-bagiannya.

EDIT:

Kembali dan menemukan kodenya. Inilah gambaran umumnya.

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;
}
Joel
sumber
1
Sebenarnya jauh lebih mudah. Subclass WebFormsViewEngine dan kemudian tambahkan ke array jalur yang sudah dicari di konstruktor Anda.
Craig Stuntz
Senang mendengarnya. Terakhir kali saya perlu mengubah koleksi itu, tidak mungkin dengan cara itu.
Joel
Perlu disebutkan bahwa Anda perlu menyetel variabel "baseControllerNamespace" ke ruang nama pengendali dasar Anda (misalnya "Project.Controllers"), tetapi sebaliknya melakukan apa yang saya butuhkan, 7 tahun setelah diposting.
prototipe14
3

Coba sesuatu seperti ini:

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);
}
Vitaliy Ulantikov
sumber
3

Catatan: untuk ASP.NET MVC 2 mereka memiliki jalur lokasi tambahan yang perlu Anda atur untuk tampilan di 'Area'.

 AreaViewLocationFormats
 AreaPartialViewLocationFormats
 AreaMasterLocationFormats

Membuat mesin tampilan untuk Area dijelaskan di blog Phil .

Catatan: Ini untuk pratinjau rilis 1 jadi dapat berubah.

Simon_Weaver
sumber
1

Sebagian besar jawaban di sini, hapus lokasi yang ada dengan menelepon ViewEngines.Engines.Clear()dan kemudian menambahkannya kembali ... tidak perlu melakukan ini.

Kami cukup menambahkan lokasi baru ke yang sudah ada, seperti yang ditunjukkan di bawah ini:

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

Sekarang Anda dapat mengkonfigurasi proyek Anda untuk menggunakan yang di atas RazorViewEnginedi Global.asax:

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

Lihat tutoral ini untuk info lebih lanjut.

Hooman Bahreini
sumber