Czy mogę określić lokalizację niestandardową do „wyszukiwania widoków” w ASP.NET MVC?

105

Mam następujący układ dla mojego projektu MVC:

  • / Kontrolery
    • /Próbny
    • / Demo / DemoArea1Controller
    • / Demo / DemoArea2Controller
    • itp...
  • /Wyświetlenia
    • /Próbny
    • /Demo/DemoArea1/Index.aspx
    • /Demo/DemoArea2/Index.aspx

Jednak gdy mam to na DemoArea1Controller:

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

Otrzymuję błąd „Nie można znaleźć indeksu widoku lub jego wzorca” przy zwykłych lokalizacjach wyszukiwania.

Jak mogę określić te kontrolery w obszarze wyszukiwania „Demo” w podfolderze widoku „Demo”?

Daniel Schaffer
źródło
Oto kolejna próbka prostego ViewEngine z aplikacji MVC Commerce Roba Connery'ego: Wyświetl kod silnika i kod Global.asax.cs, aby ustawić ViewEngine: Global.asax.cs Mam nadzieję, że to pomoże.
Robert Dean

Odpowiedzi:

121

Możesz łatwo rozszerzyć WebFormViewEngine, aby określić wszystkie lokalizacje, w których chcesz szukać:

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

Pamiętaj, aby zarejestrować mechanizm widoku, modyfikując metodę Application_Start w pliku Global.asax.cs

protected void Application_Start()
{
    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(new CustomViewEngine());
}
Sam Wessel
źródło
Jak uzyskać dostęp do ścieżki strony wzorcowej z zagnieżdżonej strony wzorcowej? Tak jak w przypadku ustawiania zagnieżdżonego układu strony wzorcowej do wyszukiwania w ścieżkach CustomViewEngine
Drahcir,
6
Czy nie lepiej, jeśli pominiemy Czyszczenie już zarejestrowanych silników i po prostu dodamy nowy, a viewLocations będą miały tylko nowe?
Prasanna
3
Implementuje bez ViewEngines.Engines.Clear (); Wszystko działa dobrze. Jeśli chcesz używać * .cshtml, musisz dziedziczyć po RazorViewEngine
KregHEk,
Czy jest jakiś sposób, aby połączyć opcje „dodaj widok” i „przejdź do widoku” z kontrolerów do nowych lokalizacji widoku? Używam Visual Studio 2012
Neville Nazerane,
Jak wspomniał @Prasanna, nie ma potrzeby czyszczenia istniejących silników w celu dodania nowych lokalizacji, zobacz tę odpowiedź, aby uzyskać więcej informacji.
Hooman Bahreini
45

Teraz w MVC 6 możesz zaimplementować IViewLocationExpanderinterfejs bez mieszania się z silnikami widoku:

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

gdzie {0}jest nazwa widoku docelowego, {1}- nazwa kontrolera i {2}- nazwa obszaru.

Możesz zwrócić własną listę lokalizacji, połączyć ją z domyślnymi viewLocations( .Union(viewLocations)) lub po prostu je zmienić ( viewLocations.Select(path => "/AnotherPath" + path)).

Aby zarejestrować niestandardowy ekspander lokalizacji widoku w MVC, dodaj kolejne wiersze do ConfigureServicesmetody w Startup.cspliku:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<RazorViewEngineOptions>(options =>
    {
        options.ViewLocationExpanders.Add(new MyViewLocationExpander());
    });
}
whyleee
źródło
3
Chciałbym móc zagłosować za tym 10 głosami. Jest dokładnie tym, czego potrzeba w Asp.net 5 / MVC 6. Piękne. Bardzo przydatne w moim przypadku (i innych), gdy chcesz pogrupować obszary w super obszary dla większych witryn lub logicznych grup.
drewid
Część Startup.cs powinna wyglądać następująco: services.Configure <RazorViewEngineOptions> Idzie w tej metodzie: public void ConfigureServices (IServiceCollection services)
OrangeKing89
42

W rzeczywistości jest o wiele łatwiejsza metoda niż zakodowanie ścieżek w konstruktorze. Poniżej znajduje się przykład rozszerzenia silnika Razor w celu dodania nowych ścieżek. Nie jestem do końca pewien, czy ścieżki, które tu dodasz, zostaną zapisane w pamięci podręcznej:

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

A Twój 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);
}

Jedna uwaga: Twoja niestandardowa lokalizacja będzie wymagała pliku ViewStart.cshtml w swoim katalogu głównym.

Chris S.
źródło
23

Jeśli chcesz po prostu dodać nowe ścieżki, możesz dodać do domyślnych silników widoku i zaoszczędzić kilka linii kodu:

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

To samo dotyczy WebFormEngine

Marcelo De Zen
źródło
2
W przypadku widoków: użyj razorEngine.ViewLocationFormats.
Aldentev
13

Zamiast podklasy RazorViewEngine lub zastępowania jej wprost, możesz po prostu zmienić istniejącą właściwość PartialViewLocationFormats na istniejącą RazorViewEngine. Ten kod znajduje się w 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
źródło
2
To zadziałało dla mnie, z wyjątkiem tego, że typ silnika maszynki do golenia to „FixedRazorViewEngine” zamiast „RazorViewEngine”. Również zgłaszam wyjątek, jeśli silnik nie został znaleziony, ponieważ uniemożliwia to pomyślną inicjalizację mojej aplikacji.
Rob
3

Ostatnio sprawdzałem, wymaga to zbudowania własnego ViewEngine. Nie wiem jednak, czy ułatwili to w RC1.

Podstawowym podejściem, które zastosowałem przed pierwszym RC, było podzielenie przestrzeni nazw kontrolera w moim własnym ViewEngine i wyszukanie folderów pasujących do części.

EDYTOWAĆ:

Wróciłem i znalazłem kod. Oto ogólny pomysł.

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
źródło
1
To jest o wiele łatwiejsze. Podklasa WebFormsViewEngine, a następnie po prostu dodaj do tablicy ścieżek, które już przeszukuje w twoim konstruktorze.
Craig Stuntz
Dobrze wiedzieć. Ostatnim razem, gdy musiałem zmodyfikować tę kolekcję, nie było to możliwe w ten sposób.
Joel
Warto wspomnieć, że musisz ustawić zmienną "baseControllerNamespace" na przestrzeń nazw kontrolera podstawowego (np. "Project.Controllers"), ale poza tym zrobiłem dokładnie to, czego potrzebowałem, 7 lat po opublikowaniu.
prototyp 14
3

Spróbuj czegoś takiego:

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
źródło
3

Uwaga: w przypadku ASP.NET MVC 2 mają one dodatkowe ścieżki lokalizacji, które należy ustawić dla widoków w obszarze „Obszary”.

 AreaViewLocationFormats
 AreaPartialViewLocationFormats
 AreaMasterLocationFormats

Tworzenie mechanizmu przeglądania dla obszaru jest opisane na blogu Phila .

Uwaga: dotyczy wersji zapoznawczej 1, więc może ulec zmianie.

Simon_Weaver
źródło
1

Większość odpowiedzi tutaj, wyczyść istniejące lokalizacje , dzwoniąc, ViewEngines.Engines.Clear()a następnie dodaj je ponownie ... nie ma potrzeby tego robić.

Możemy po prostu dodać nowe lokalizacje do istniejących, jak pokazano poniżej:

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

Teraz możesz skonfigurować swój projekt, aby używał powyższego RazorViewEnginew Global.asax:

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

Więcej informacji znajdziesz w tym poradniku .

Hooman Bahreini
źródło