2016-04-20 14 views
11

Diciamo che ho un controller che utilizza il routing basato attributo per gestire un URL richiesto di/admin/prodotto in questo modo:Come specificare la posizione della vista in asp.net core mvc quando si utilizzano posizioni personalizzate?

[Route("admin/[controller]")]   
public class ProductController: Controller { 

    // GET: /admin/product 
    [Route("")] 
    public IActionResult Index() { 

     return View(); 
    } 
} 

Ora diciamo che mi piacerebbe mantenere le mie opinioni organizzati in una struttura di cartelle che riflette approssimativamente i percorsi di URL a cui sono collegati. Così mi piacerebbe la vista per questo controller per essere collocata qui:

/Views/Admin/Product.cshtml 

Per andare oltre, se avessi un controller come questo:

[Route("admin/marketing/[controller]")]   
public class PromoCodeListController: Controller { 

    // GET: /admin/marketing/promocodelist 
    [Route("")] 
    public IActionResult Index() { 

     return View(); 
    } 
} 

Vorrei che il quadro di cercare automaticamente è vista qui:

Views/Admin/Marketing/PromoCodeList.cshtml 

Idealmente l'approccio per informare il quadro della posizione vista avrebbe funzionato in modo generale sulla base delle informazioni percorso attributi base indipendentemente dal numero di url seg sono coinvolti (es. quanto profondamente annidato è).

Come posso istruire il framework MVC di base (attualmente sto utilizzando RC1) per cercare la vista del controller in tale posizione?

risposta

26

Un'altra soluzione a questo è quella di espandere le posizioni che il motore di visualizzazione guarda per una vista senza la necessità di creare un motore di visualizzazione personalizzata. Per questo approccio effettuare le seguenti operazioni:

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

Poi nel metodo ConfigureServices(IServiceCollection services) nelle startup.cs archiviare aggiungere il seguente codice per registrarlo con il contenitore IoC. Fare questo diritto dopo services.AddMvc();

services.Configure<RazorViewEngineOptions>(options => { 
     options.ViewLocationExpanders.Add(new ViewLocationExpander()); 
    }); 

Bam! Ora hai un modo per aggiungere qualsiasi struttura di directory personalizzata che desideri all'elenco delle posizioni in cui il motore di visualizzazione cerca viste e viste parziali. Basta aggiungerlo allo locationsstring[].Inoltre, è possibile inserire un file _ViewImports.cshtml nella stessa directory o in qualsiasi directory principale e verrà individuato e unito alle visualizzazioni presenti in questa nuova struttura di directory. Molto carino.

+0

Questa è una buona soluzione, tuttavia, questo non risolve il problema di trovare una vista che ha un attributo route sull'azione o sul controller. Il metodo di visualizzazione sembra ancora utilizzare il nome del controller e non il nome della rotta per individuare la vista. – Xipooo

+0

@Xipooo, buon punto. L'esempio che ho fornito è un buon inizio, ma per utilizzare il percorso è possibile impostare l'array 'locations' per includere '/ Views' +' context.ActionContext.HttpContext.Request.Path' + 'index.cshtml' o '.cshtml' . –

+0

Questo mi ha permesso di usare una vista che è stata aggiunta alla cartella bin tramite uno standard .net 1.6. Grazie ottima soluzione. – DeadlyChambers

2

Avrete bisogno di un RazorviewEngine personalizzato per questo.

In primo luogo, il motore:

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

Questo creerà un formato di superficie aggiuntiva, che corrisponde al caso d'uso di {areaName}/{controller}/{view}.

In secondo luogo, registrare il motore nel metodo ConfigureServices della classe Startup.cs:

public void ConfigureServices(IServiceCollection services) 
{ 
    // Add custom engine (must be BEFORE services.AddMvc() call) 
    services.AddSingleton<IRazorViewEngine, CustomEngine>(); 

    // Add framework services. 
    services.AddMvc(); 
} 

In terzo luogo, aggiungere un'area di routing ai tuoi percorsi MVC, nel metodo Configure:

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

Infine, il cambiamento la tua classe ProductController per utilizzare il AreaAttribute:

[Area("admin")] 
public class ProductController : Controller 
{ 
    public IActionResult Index() 
    { 
     return View(); 
    } 
} 

Ora, la struttura applicazione può apparire come questo:

sample project structure

+0

Will - grazie per questa soluzione. Posso confermare che funziona. Dopo ulteriori ricerche, ho trovato un altro modo che ritengo sia ancora più semplice iniettando la classe ExpandViewLocations. Valuterò la tua soluzione come risposta perché funziona e ti meriti il ​​merito della tua risposta approfondita. –

+0

Questa risposta non è più valida per i progetti ASP.Net Core correnti, poiché "AreaViewLocationFormats" non esiste più. Penso che questo sia stato spostato in 'RazorViewEngineOptions' ora. –

+1

@RonC Penso che sarebbe una buona idea cambiare la risposta accettata su questa domanda. –

8

Nel nucleo .net è possibile specificare l'intero percorso della vista.

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

+3

Totalmente vero, ma stavo cercando una soluzione per ottenere il framework per trovare automaticamente la vista in una posizione personalizzata senza doverlo specificare in questo modo. Questa è una buona soluzione per le persone che vogliono specificare manualmente la posizione della vista. –

+3

Sì, la tua risposta mi piace molto più di quella accettata. Stavo per usarlo fino a quando ho capito che era eccessivo per quello di cui avevo bisogno. Ho postato questo perché ero in grado di risolvere il mio problema senza dover aggiungere alcun codice personalizzato. Forse qualcuno lo troverà a portata di mano. Grazie per la tua risposta!Cordiali saluti –

16

Grandi notizie ... In ASP.NET Core 2, non è necessario un ViewEngine personalizzato o anche Anymore ExpandViewLocations.

In startup.cs, in ConfigureServices, è possibile aggiungere qualcosa di simile, che ho usato per implementare Caratteristica della struttura delle cartelle (preferisco di gran lunga sopra le convenzioni di default):

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

E il gioco è fatto! Non sono richieste classi speciali.

Suggerimento bonus: se si utilizza il programma di ricerca, si potrebbe notare che in alcuni punti Resharper non riesce a trovare le proprie visualizzazioni e fornisce fastidiosi avvisi. Per ovviare a questo, tirare nel pacchetto Resharper.Annotations e nelle vostre startup.cs (o in qualsiasi altro luogo in realtà) aggiungere uno di questi attributi per ciascuno dei vostri visualizzare posizioni:

[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")] 

Speriamo che questo risparmia alcune persone le ore di frustrazione che ho appena vissuto. :)

+1

Sei il mio eroe. Sono giunto a questa domanda perché volevo implementare la struttura delle cartelle basata sulle funzionalità e l'hai già fatto. Fantastico! –

+2

@JohnHargrove Grazie per le gentili parole, hai illuminato una giornata difficile :) –

+2

Mi piace la _Bonus tip_ sul ReSharper! Stavo evidenziando in rosso tutte le mie chiamate a 'View' e' PartialView'. – t3chb0t

Problemi correlati