Condividi tramite



Settembre 2016

Volume 31 Numero 9

Il presente articolo è stato tradotto automaticamente.

ASP.NET Core - Funzionalità Feature Slices per ASP.NET Core MVC

Da Smith Steve

App Web di grandi dimensioni richiedono una migliore organizzazione rispetto a quelli di piccole dimensioni. Con le applicazioni di grandi dimensioni, della struttura organizzativa predefinito utilizzata da ASP.NET MVC (e MVC Core) inizia da utilizzare è. È possibile utilizzare due semplici tecniche per aggiornare l'approccio dell'organizzazione e di gestire un'applicazione in continua crescita.

Il modello Model-View-Controller (MVC) è maturo, anche nello spazio di Microsoft ASP.NET. La prima versione di ASP.NET MVC fornito nel 2009 e il primo riavvio completo della piattaforma, ASP.NET MVC di base, disponibile in anticipo l'estate. Durante questo periodo, come si è evoluto ASP.NET MVC, la struttura del progetto predefinita è rimasto invariata: cartelle per controller e visualizzazioni e spesso per i modelli (o forse ViewModel). Infatti, se si crea una nuova applicazione ASP.NET di base oggi, si noterà queste cartelle create dal modello predefinito, come illustrato nella Figura 1.

Struttura del modello applicazione Web ASP.NET di base predefinita
Figura 1 predefinito struttura modello di applicazione Web ASP.NET di base

Esistono numerosi vantaggi di questa struttura organizzativa. Familiare; Se è lavorato su un progetto ASP.NET MVC negli ultimi anni, si verrà immediatamente riconosciuto. La tabella è organizzata; Se sta cercando un controller o una vista, è necessario una buona idea in cui iniziare. Quando si sta iniziando un nuovo progetto, tale struttura organizzativa operazione funziona ragionevolmente bene, perché non ci sono ancora molti file. Crescita del progetto, tuttavia, non dell'attrito coinvolti nel trovare il file desiderato controller o una vista all'interno i numeri di file e cartelle in continua crescita di queste gerarchie.

Per vedere cosa intendo dire, imagine se sono così organizzati i file del computer nella stessa struttura. Anziché cartelle separate per progetti diversi o tipi di lavoro, è necessario solo le directory organizzate esclusivamente per i tipi di file. Potrebbero essere presenti cartelle per i documenti di testo, PDF, immagini e fogli di calcolo. Quando si crea un'attività particolare che coinvolge più tipi di documenti, è necessario mantenere saranno rimbalzati tra le cartelle diverse e lo scorrimento o una ricerca in molti file che non sono correlati alle attività in questione in ogni cartella. Questo è esattamente l'utilizzo delle funzionalità all'interno di un'applicazione MVC organizzati nel modo predefinito.

Il motivo di che ciò costituisce un problema è che i gruppi di file organizzati in base al tipo anziché scopo, tendono a non dispongono di coesione. Coesione indica il livello a cui appartengono insieme gli elementi di un modulo. In un progetto ASP.NET MVC tipico, un determinato controller farà riferimento a uno o più visualizzazioni (nella cartella corrispondente al nome del controller) correlate. Il controller sia la vista farà riferimento a uno o più elementi ViewModel correlati a responsabilità del controller. In genere, tuttavia, alcuni tipi di elemento ViewModel o viste vengono utilizzate da più di un tipo di controller e, in genere, il modello di dominio o il modello di persistenza viene spostato il proprio progetto separato.

Un progetto di esempio

Si consideri un semplice progetto incaricato della gestione dei quattro concetti di applicazione non strettamente correlati: Ninjas, impianti, Pirates e zombie. L'esempio effettivo consente solo di elenco, visualizzare e aggiungere questi concetti. Tuttavia, si supponga di complessità aggiuntiva consiste che prevede più visualizzazioni. La struttura organizzativa predefinita per questo progetto si presenterebbe come Figura 2.

Progetto di esempio con l'organizzazione predefinita
Figura 2 progetto di esempio con l'organizzazione predefinita

Per lavorare su un nuovo frammento di funzionalità che interessano Pirates, è necessario spostarsi verso il basso in controller e trovare il PiratesController e quindi passare dalle viste Pirates nel file di visualizzazione appropriata. Anche con cinque solo controller, è possibile visualizzare tanto di provata navigazione delle cartelle. Ciò spesso è peggio quando la radice del progetto include molte altre cartelle, controller e visualizzazioni non sono una accanto a altra in ordine alfabetico (in modo che le cartelle aggiuntive tendono a essere tra i due nell'elenco delle cartelle).

Un approccio alternativo per organizzare i file in base al tipo è per organizzarle lungo le righe della funzione dell'applicazione. Invece di cartelle per i controller, modelli e le viste, il progetto includerebbe cartelle organizzate in base a funzionalità o aree di responsabilità. Quando si crea un bug o funzionalità correlate a una determinata funzionalità dell'applicazione, è necessario mantenere aperta meno cartelle perché i file correlati possono essere archiviati insieme. Questa operazione può essere eseguita in diversi modi, ad esempio utilizzando la funzionalità incorporata di aree e il proprio convenzione per cartelle delle funzionalità in sequenza.

Come di vede i file principali di ASP.NET MVC

È utile dedicare qualche minuto per informazioni sul funzionamento di ASP.NET MVC di base con i tipi standard di file utilizzati da un'applicazione compilata in essa contenuti. La maggior parte dei file interessati sul lato server dell'applicazione devono essere classi scritte in un linguaggio .NET. Questi file di codice possono risiedere in qualsiasi posizione su disco, come può essere compilati e a cui fa riferimento l'applicazione. In particolare, Controller classe file non devono essere archiviati in qualsiasi cartella particolare. Vari tipi di classi del modello (modello di dominio, modello di visualizzazione, modello di persistenza e così via) sono uguali e può essere facilmente attivo in progetti separati dal progetto ASP.NET MVC di base. È possibile disporre e ridisporre la maggior parte dei file di codice nell'applicazione in qualsiasi modo desiderato.

Le viste, tuttavia, sono diverse. Le viste sono file di contenuto. In cui sono archiviati relativo classi controller dell'applicazione è irrilevanti, ma è importante che MVC individua la posizione in cui cercare. Aree forniscono il supporto incorporato per l'individuazione di visualizzazioni in posizioni diverse da quella predefinita viste. È inoltre possibile personalizzare come MVC determina la posizione delle viste.

Organizzazione dei progetti MVC utilizzando aree

Aree consentono di organizzare i moduli indipendenti all'interno di un'applicazione ASP.NET MVC. Ogni Area è una struttura di cartelle che riproduce le convenzioni di radice del progetto. Pertanto, l'applicazione MVC sarebbe le stesse convenzioni di cartella radice e una cartella aggiuntiva denominata aree, in cui sarebbe una cartella per ogni sezione dell'applicazione, che contiene le cartelle per controller e visualizzazioni (e probabilmente modelli o ViewModel, se necessario).

Le aree sono una potente funzionalità che consentono di suddividere un'applicazione di grandi dimensioni nelle applicazioni secondario separate e distinte logicamente. I controller, ad esempio, possono avere lo stesso nome in aree e in effetti, è normale che una classe HomeController in ogni area all'interno di un'applicazione.

Per aggiungere il supporto per le aree a un progetto ASP.NET MVC di base, è sufficiente creare una nuova cartella di livello principale denominata aree. In questa cartella, creare una nuova cartella per ogni parte dell'applicazione che si desidera organizzare all'interno di un'Area. Quindi, all'interno di questa cartella, aggiungere nuove cartelle per controller e visualizzazioni.

I file del controller pertanto devono trovarsi in:

/Areas/[area name]/Controllers/[controller name].cs

I controller necessario disporre di un attributo di Area applicato in modo che vengano automaticamente dal framework di sapere che appartengono all'interno di una particolare area:

namespace WithAreas.Areas.Ninjas.Controllers
{
  [Area("Ninjas")]
  public class HomeController : Controller

Le visualizzazioni devono quindi essere posizionate in:

/Areas/[area name]/Views/[controller name]/[action name].cshtml

Tutti i collegamenti che era necessario viste che sono stati spostati in aree devono essere aggiornati. Se si utilizzano gli helper tag, è possibile specificare il nome dell'area come parte dell'helper tag. Ad esempio:

<a asp-area="Ninjas" asp-controller="Home" asp-action="Index">Ninjas</a>

I collegamenti tra le visualizzazioni all'interno della stessa area è possono omettere l'attributo area asp.

L'ultima operazione da effettuare per supportare aree nell'applicazione è aggiornare le regole di routing predefinito per l'applicazione in Startup.cs nel metodo configura:

app.UseMvc(routes =>
{
  // Areas support
  routes.MapRoute(
    name: "areaRoute",
    template: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
  routes.MapRoute(
    name: "default",
    template: "{controller=Home}/{action=Index}/{id?}");
});

Ad esempio, l'applicazione di esempio per la gestione dei vari Ninjas, Pirates e così via potrà utilizzare aree per ottenere la struttura del progetto dell'organizzazione, come illustrato nel Figura 3.

Organizzazione di un progetto ASP.NET di base con aree
Figura 3 organizzare un progetto ASP.NET di base con aree

La funzionalità di aree offre un miglioramento rispetto la convenzione predefinita fornendo cartelle separate per ogni sezione logica dell'applicazione. Le aree sono una funzionalità incorporata in ASP.NET MVC di base, che richiedono l'installazione minima. Se non già in uso li, tenerne conto come un modo semplice per raggruppare insieme le sezioni correlate dell'app e separati dal resto dell'applicazione.

Tuttavia, l'organizzazione di aree è ancora molto elevati in termini di cartella. È possibile notarlo nello spazio verticale richiesto per mostrare il numero relativamente ridotto di file nella cartella aree. Se non si dispone di numerosi controller per ogni area e non si dispone di numerose visualizzazioni per ogni controller, questo sovraccarico cartella potrebbe aggiungere attrito in gran parte esattamente come la convenzione predefinita.

Fortunatamente, è possibile creare facilmente il proprio convenzione.

Cartelle delle funzionalità principali di ASP.NET MVC

All'esterno la convenzione predefinita della cartella o l'utilizzo della funzionalità di aree predefinite, il modo più diffuso per organizzare i progetti MVC è con le cartelle per ogni funzionalità. Ciò vale soprattutto per i team che hanno adottato le funzionalità di distribuzione in sezioni verticale (vedere bit.ly/2abpJ7t), in quanto la maggior parte dei problemi dell'interfaccia utente della sezione verticale possono trovarsi in una di queste cartelle di funzionalità.

Quando si organizzano il progetto dalla funzionalità (anziché al tipo di file), è necessario in genere una cartella radice (ad esempio funzionalità) entro il quale si disporrà di una sottocartella per ogni funzionalità. È molto simile a come sono organizzate in aree. All'interno di ogni cartella di funzionalità, tuttavia, si verrà includono tutti i controller necessari, visualizzazioni e ViewModel tipi. Nella maggior parte delle applicazioni, il risultato in una cartella con probabilmente 5 e 15 elementi, ognuno dei quali sono strettamente correlati tra loro. L'intero contenuto della cartella funzionalità può essere mantenuto nella visualizzazione in Esplora soluzioni. È possibile vedere un esempio di questa organizzazione per il progetto di esempio in Figura 4.

Organizzazione della cartella funzionalità
Figura 4 organizzazione della cartella funzionalità

Si noti che anche i controller di primo livello e le cartelle di viste sono state eliminate. Home page per l'app è ora presente nella relativa cartella funzionalità denominata Home e file condivisi come layout. cshtml si trovano in una cartella condivisa all'interno della cartella di funzionalità, nonché. Questa struttura di progetto organizzazione piuttosto sia scalabile e consente agli sviluppatori di mantenere le cartelle meno mentre si lavora su una particolare sezione di un'applicazione.

In questo esempio, a differenza delle aree, altre route non sono necessari e non gli attributi necessari per i controller (si noti, tuttavia, che i nomi dei controller deve essere univoci tra le funzionalità di questa implementazione). Per supportare questa organizzazione, è necessario un IViewLocationExpander e IControllerModelConvention personalizzati. Questi vengono entrambi utilizzati, con alcune ViewLocationFormats personalizzato, configurare MVC nella classe di avvio.

Per un determinato controller, è utile sapere a quale funzionalità sono associati. Aree di ottenere questo risultato mediante attributi; Questo approccio utilizza una convenzione. La convenzione prevede che il controller sia in uno spazio dei nomi denominato "Funzionalità" e per l'elemento successivo della gerarchia dello spazio dei nomi dopo "Funzionalità" nel nome di funzionalità. Questo nome viene aggiunta alle proprietà che sono disponibili durante la posizione di visualizzazione, come illustrato Figura 5.

Figura 5 FeatureConvention: IControllerModelConvention

{
  public void Apply(ControllerModel controller)
  {
    controller.Properties.Add("feature", 
      GetFeatureName(controller.ControllerType));
  }
  private string GetFeatureName(TypeInfo controllerType)
  {
    string[] tokens = controllerType.FullName.Split('.');
    if (!tokens.Any(t => t == "Features")) return "";
    string featureName = tokens
      .SkipWhile(t => !t.Equals("features",
        StringComparison.CurrentCultureIgnoreCase))
      .Skip(1)
      .Take(1)
      .FirstOrDefault();
    return featureName;
  }
}

Aggiungere questa convenzione come parte di MvcOptions quando aggiunta MVC in avvio:

services.AddMvc(o => o.Conventions.Add(new FeatureConvention()));

Per sostituire la logica di posizione di visualizzazione normale utilizzata da MVC con la convenzione basata su funzionalità, è possibile cancellare l'elenco di ViewLocationFormats utilizzato da MVC e sostituirlo con un elenco personalizzato. Questa operazione viene eseguita come parte della chiamata AddMvc, come illustrato nella Figura 6.

Figura 6 sostituendo la normale visualizzazione logica di percorso utilizzata da MVC

services.AddMvc(o => o.Conventions.Add(new FeatureConvention()))
  .AddRazorOptions(options =>
  {
    // {0} - Action Name
    // {1} - Controller Name
    // {2} - Area Name
    // {3} - Feature Name
    // Replace normal view location entirely
    options.ViewLocationFormats.Clear();
    options.ViewLocationFormats.Add("/Features/{3}/{1}/{0}.cshtml");
    options.ViewLocationFormats.Add("/Features/{3}/{0}.cshtml");
    options.ViewLocationFormats.Add("/Features/Shared/{0}.cshtml");
    options.ViewLocationExpanders.Add(new FeatureViewLocationExpander());
  }

Per impostazione predefinita, queste stringhe di formato includono segnaposto per le azioni ("{0})", i controller (\ {1 "\") e aree ("\ {2"). Questo approccio consente di aggiungere un quarto token ("\ {3") per le funzionalità.

I formati di percorso di visualizzazione utilizzati devono supportare viste con lo stesso nome ma usate dai diversi controller all'interno di una funzionalità. Ad esempio, è molto comune per più di un controller in una funzione e più controller che dispone di un metodo di indice. Questo è supportato da ricerca di viste in una cartella corrispondente al nome del controller. Di conseguenza, NinjasController.Index e SwordsController.Index sarebbe individuazione delle viste in /Features/Ninjas/Ninjas/Index.cshtml e /Features/Ninjas/Swords/Index.cshtml, rispettivamente (vedere Figura 7).

Più controller Per funzionalità
Figura 7 più controller per ogni funzionalità

Si noti che questo è facoltativo, ovvero se le funzionalità non sono necessaria per evitare ambiguità tra visualizzazioni (ad esempio, perché la funzionalità dispone di un solo controller), è possibile inserire le viste direttamente nella cartella della funzionalità. Inoltre, se si desidera utilizzare i prefissi di file rispetto alle cartelle, è possibile modificare la stringa di formato per l'utilizzo di "{3}{1}" anziché "{3}/{1}," risultante nella visualizzazione dei nomi file come NinjasIndex.cshtml e SwordsIndex.cshtml.

Sono inoltre supportate Shared viste, sia nella radice della cartella di funzionalità che in una sottocartella condivisa.

L'interfaccia IViewLocationExpander espone un metodo, ExpandViewLocations, che viene utilizzato dal framework per identificare le cartelle contenenti le viste. Queste cartelle vengono ricercate quando un'operazione restituisce una visualizzazione. Questo approccio richiede solo ViewLocationExpander sostituire il token "\ {3" con nome di funzionalità del controller, specificato tramite il FeatureConvention descritto in precedenza:

public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context,
  IEnumerable<string> viewLocations)
{
  // Error checking removed for brevity
  var controllerActionDescriptor =
    context.ActionContext.ActionDescriptor as ControllerActionDescriptor;
  string featureName = controllerActionDescriptor.Properties["feature"] as string;
  foreach (var location in viewLocations)
  {
    yield return location.Replace("{3}", featureName);
  }
}

Per supportare la pubblicazione in modo corretto, è necessario anche aggiornare publishOptions di Project in modo da includere la cartella di funzionalità:

"publishOptions": {
  "include": [
    "wwwroot",
    "Views",
    "Areas/**/*.cshtml",
    "Features/**/*.cshtml",
    "appsettings.json",
    "web.config"
  ]
},

La nuova convenzione dell'utilizzo di una cartella denominata funzionalità è completamente sotto controllo, insieme a come le cartelle sono organizzate in esso contenuti. Modificando il set di ViewLocationFormats (ed eventualmente il comportamento del tipo FeatureViewLocationExpander), è possibile configurare il controllo completo su viste dell'applicazione in cui si trovano, che è l'unico elemento necessario per riorganizzare i file, poiché i tipi di controller vengono individuati indipendentemente dalla cartella in cui si trovano.

Cartelle delle funzionalità side-By-Side

Se si desidera provare le cartelle delle funzionalità side-by-side con le convenzioni di Area MVC e la visualizzazione predefinita, è possibile farlo con solo piccole modifiche. Invece di cancellare il ViewLocationFormats, inserire i formati di funzionalità all'inizio dell'elenco (si noti l'ordine viene invertito):

options.ViewLocationFormats.Insert(0, "/Features/Shared/{0}.cshtml");
options.ViewLocationFormats.Insert(0, "/Features/{3}/{0}.cshtml");
options.ViewLocationFormats.Insert(0, "/Features/{3}/{1}/{0}.cshtml");

Per supportare le funzionalità combinate con aree, modificare la raccolta di AreaViewLocationFormats, nonché:

options.AreaViewLocationFormats.Insert(0, "/Areas/{2}/Features/Shared/{0}.cshtml");
options.AreaViewLocationFormats.Insert(0, "/Areas/{2}/Features/{3}/{0}.cshtml");
options.AreaViewLocationFormats.Insert(0, "/Areas/{2}/Features/{3}/{1}/{0}.cshtml");

Per quanto riguarda i modelli?

I lettori attenti noterà che non spostare i tipi di modello in cartelle delle funzionalità (o aree). In questo esempio, non vi sono tipi di elemento ViewModel separati, perché i modelli di che utilizzo sono incredibilmente semplici. In un'applicazione reale, è probabile che il dominio o modello di persistenza avranno più complessi di quelli che richiedono le visualizzazioni e che sarà possibile definire nel proprio progetto separato. L'applicazione MVC definirà probabilmente tipi ViewModel che contengono solo i dati necessari per una determinata visualizzazione, ottimizzata per la visualizzazione (o consumo dalla richiesta di API del client). Questi tipi di elemento ViewModel devono assolutamente essere inseriti nella cartella funzionalità in cui vengono utilizzati (e dovrebbe essere raro per questi tipi per essere condivisi tra le funzionalità).

Conclusioni

L'esempio include tutti e tre le versioni dell'applicazione multimediale NinjaPiratePlantZombie, con il supporto per l'aggiunta e visualizzazione di ogni tipo di dati. Scaricarlo (o visualizzarlo su GitHub) e riflettere su funzionamento nel contesto di un'applicazione di lavorare su ogni approccio. Esperimento con l'aggiunta di un'Area o una cartella di funzionalità a un'applicazione più grande si sta lavorando e decidere se si preferisce operare con sezioni di funzionalità come l'organizzazione di livello superiore di cartella struttura di cartelle di livello superiore dell'app in base ai tipi di file.

Il codice sorgente per questo esempio è disponibile all'indirizzo bit.ly/29MxsI0.


Steve Smith è un formatore indipendente, mentore e consulente, nonché un MVP di ASP.NET.  Ha contribuito decine di articoli per la documentazione ufficiale di ASP.NET di base (docs.asp.net) e consente ai team di accedere rapidamente a lavorare con ASP.NET di base. Contattarlo all'indirizzo ardalis.com.


Grazie al seguente esperto tecnico per la revisione dell'articolo: Ryan Nowak
Ryan Nowak è uno sviluppatore che lavora nel team di ASP.NET in Microsoft.