Posso specificare una posizione personalizzata per "cercare le visualizzazioni" in ASP.NET MVC?


105

Ho il seguente layout per il mio progetto mvc:

  • / Controller
    • / Demo
    • / Demo / DemoArea1Controller
    • / Demo / DemoArea2Controller
    • eccetera...
  • /Visualizzazioni
    • / Demo
    • /Demo/DemoArea1/Index.aspx
    • /Demo/DemoArea2/Index.aspx

Tuttavia, quando ho questo per DemoArea1Controller:

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

Ottengo l'errore "Impossibile trovare la vista 'indice' o il suo master", con le solite posizioni di ricerca.

Come posso specificare che i controller nello spazio dei nomi "Demo" cercano nella sottocartella della vista "Demo"?


Ecco un altro esempio di un semplice ViewEngine dall'app MVC Commerce di Rob Connery: Visualizza codice motore e il codice Global.asax.cs per impostare ViewEngine: Global.asax.cs Spero che questo aiuti.
Robert Dean

Risposte:


121

È possibile estendere facilmente WebFormViewEngine per specificare tutte le posizioni in cui si desidera cercare:

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

Assicurati di ricordare di registrare il motore di visualizzazione modificando il metodo Application_Start nel tuo Global.asax.cs

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

Come si accede al percorso di una pagina master da una pagina master annidata? Come nell'impostazione del layout della pagina master annidata per la ricerca nei percorsi di CustomViewEngine
Drahcir

6
Non è meglio se saltiamo la cancellazione dei motori già registrati e aggiungiamo solo quello nuovo e viewLocations avrà solo quelli nuovi?
Prasanna

3
Attrezzi senza ViewEngines.Engines.Clear (); Tutto funziona bene. Se vuoi usare * .cshtml devi ereditare da RazorViewEngine
KregHEk

c'è un modo per collegare le opzioni "aggiungi visualizzazione" e "vai a visualizzazione" dai controller alle nuove posizioni di visualizzazione? Sto usando Visual Studio 2012
Neville Nazerane

Come accennato da @Prasanna, non è necessario cancellare i motori esistenti per aggiungere nuove posizioni, vedere questa risposta per maggiori dettagli.
Hooman Bahreini

44

Ora in MVC 6 puoi implementare l' IViewLocationExpanderinterfaccia senza perdere tempo con i motori di visualizzazione:

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

dove {0}è il nome della vista di destinazione, {1}- nome del controller e {2}- nome dell'area.

Puoi restituire il tuo elenco di posizioni, unirlo a default viewLocations( .Union(viewLocations)) o semplicemente modificarle ( viewLocations.Select(path => "/AnotherPath" + path)).

Per registrare l'espansore della posizione della visualizzazione personalizzata in MVC, aggiungi le righe successive al ConfigureServicesmetodo nel Startup.csfile:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<RazorViewEngineOptions>(options =>
    {
        options.ViewLocationExpanders.Add(new MyViewLocationExpander());
    });
}

3
Vorrei poter votare questo con 10 voti. È esattamente ciò che serve in Asp.net 5 / MVC 6. Bello. Molto utile nel mio caso (e in altri) quando si desidera raggruppare aree in super aree per siti più grandi o raggruppamenti logici.
ritirato il

La parte Startup.cs dovrebbe essere: services.Configure <RazorViewEngineOptions> Va in questo metodo: public void ConfigureServices (IServiceCollection services)
OrangeKing89

42

In realtà esiste un metodo molto più semplice rispetto all'hardcoding dei percorsi nel tuo costruttore. Di seguito è riportato un esempio di estensione del motore Razor per aggiungere nuovi percorsi. Una cosa di cui non sono del tutto sicuro è se i percorsi che aggiungi qui verranno memorizzati nella 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();
    }
}

E il tuo 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);
}

Una cosa da notare: la tua posizione personalizzata avrà bisogno del file ViewStart.cshtml nella sua radice.


23

Se vuoi semplicemente aggiungere nuovi percorsi, puoi aggiungerli ai motori di visualizzazione predefiniti e risparmiare alcune righe di codice:

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

Lo stesso vale per WebFormEngine


2
Per le visualizzazioni: usa razorEngine.ViewLocationFormats.
Aldentev

13

Invece di creare una sottoclasse di RazorViewEngine o sostituirlo completamente, puoi semplicemente modificare la proprietà PartialViewLocationFormats di RazorViewEngine esistente. Questo codice va in 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
Questo ha funzionato per me, con l'eccezione che il tipo di motore del rasoio era "FixedRazorViewEngine" invece di "RazorViewEngine". Inoltre lancio un'eccezione se il motore non è stato trovato poiché impedisce alla mia applicazione di inizializzarsi correttamente.
Rob il

3

L'ultima volta che ho controllato, questo richiede di costruire il tuo ViewEngine. Non so se l'hanno reso più facile in RC1.

L'approccio di base che ho usato prima del primo RC era, nel mio ViewEngine, di dividere lo spazio dei nomi del controller e cercare le cartelle che corrispondessero alle parti.

MODIFICARE:

Sono tornato indietro e ho trovato il codice. Ecco l'idea generale.

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
In realtà è molto più facile. Sottoclasse WebFormsViewEngine e quindi aggiungilo all'array di percorsi che cerca già nel tuo costruttore.
Craig Stuntz

Buono a sapersi. L'ultima volta che ho dovuto modificare quella raccolta, non è stato possibile in quel modo.
Joel

Vale la pena ricordare che è necessario impostare la variabile "baseControllerNamespace" sullo spazio dei nomi del controller di base (ad esempio "Project.Controllers"), ma per il resto ha fatto esattamente ciò di cui avevo bisogno, 7 anni dopo essere stato pubblicato.
prototipo14

3

Prova qualcosa di simile:

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

Nota: per ASP.NET MVC 2 hanno percorsi di posizione aggiuntivi che dovrai impostare per le viste in "Aree".

 AreaViewLocationFormats
 AreaPartialViewLocationFormats
 AreaMasterLocationFormats

La creazione di un motore di visualizzazione per un'area è descritta nel blog di Phil .

Nota: questo è per la versione di anteprima 1, quindi è soggetto a modifiche.


1

La maggior parte delle risposte qui, cancella le posizioni esistenti chiamando ViewEngines.Engines.Clear()e quindi aggiungile di nuovo ... non è necessario farlo.

Possiamo semplicemente aggiungere le nuove posizioni a quelle esistenti, come mostrato di seguito:

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

Ora puoi configurare il tuo progetto per utilizzare quanto sopra RazorViewEnginein Global.asax:

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

Vedi questo tutorial per maggiori informazioni.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.