Terry, amico mio, tu ed io dovremmo bere qualcosa. Abbiamo alcuni problemi simili.
1. Struttura del progetto: concordo con Eduardo sul fatto che la struttura delle cartelle in un'app MVC lascia a desiderare. Hai le tue cartelle Controller, Modelli e Viste standard. Ma poi la cartella Views viene suddivisa in una cartella diversa per ciascun controller, oltre a una cartella condivisa. E ogni Visualizzazioni / ControllerName o Views / Shared possono essere suddivisi in EditorTemplates e DisplayTemplates. Ma ti consente di decidere come organizzare la cartella Modelli (puoi farlo con o senza sottocartelle e dichiarazioni di spazi dei nomi aggiuntive).
Dio ti proibisce di utilizzare le aree, che duplicano la struttura di cartelle Controller, Modelli e Viste per ciascuna area.
/Areas
/Area1Name
/Controllers
FirstController.cs
SecondController.cs
ThirdController.cs
/Models
(can organize all in here or in separate folders / namespaces)
/Views
/First
/DisplayTemplates
WidgetAbc.cshtml <-- to be used by views in Views/First
/EditorTemplates
WidgetAbc.cshtml <-- to be used by views in Views/First
PartialViewAbc.cshtml <-- to be used by FirstController
/Second
PartialViewDef.cshtml <-- to be used by SecondController
/Third
PartialViewMno.cshtml <-- to be used by ThirdController
/Shared
/DisplayTemplates
WidgetXyz.cshtml <-- to be used by any view in Area1
/EditorTemplates
WidgetXyz.cshtml <-- to be used by any view in Area1
PartialViewXyz.cshtml <-- to be used anywhere in Area1
_ViewStart.cshtml <-- area needs its own _ViewStart.cshtml
Web.config <-- put custom HTML Helper namespaces in here
Area1NameRegistration.cs <-- define routes for area1 here
/Area2Name
/Controllers
/Models
/Views
Area2NameRegistration.cs <-- define routes for area2 here
/Controllers
AccountController.cs
HomeController.cs
/Models
/Views
/Account
/DisplayTemplates
WidgetGhi.cshtml <-- to be used views in Views/Account
/EditorTemplates
WidgetGhi.cshtml <-- to be used views in Views/Account
PartialViewGhi.cshtml <-- to be used by AccountController
/Home
(same pattern as Account, views & templates are controller-specific)
/Shared
/DisplayTemplates
EmailAddress.cshtml <-- to be used by any view in any area
Time.cshtml <-- to be used by any view in any area
Url.cshtml <-- to be used by any view in any area
/EditorTemplates
EmailAddress.cshtml <-- to be used by any view in any area
Time.cshtml <-- to be used by any view in any area
Url.cshtml <-- to be used by any view in any area
_Layout.cshtml <-- master layout page with sections
Error.cshtml <-- custom page to show if unhandled exception occurs
_ViewStart.cshtml <-- won't be used automatically in an area
Web.config <-- put custom HTML Helper namespaces in here
Questo significa che se stai lavorando con qualcosa come un WidgetController, devi cercare in altre cartelle per trovare i relativi WidgetViewModels, WidgetViews, WidgetEditorTemplates, WidgetDisplayTemplates, ecc. Per quanto ingombranti possano essere, mi attengo ad esso e non deviare da queste convenzioni MVC. Per quanto riguarda l'inserimento di un modello, controller e vista nella stessa cartella ma con spazi dei nomi diversi, lo evito perché utilizzo ReSharper. Sottolinea sarà uno spazio dei nomi che non corrisponde alla cartella in cui si trova la classe. So che potrei disattivare questa funzione R #, ma aiuta in altre parti del progetto.
Per i file non di classe, MVC offre contenuti e script pronti all'uso. Cerchiamo di mantenere tutti i nostri file statici / non compilati in questi luoghi, di nuovo, per seguire la convenzione. Ogni volta che incorporiamo una libreria js che utilizza temi (immagini e o css), i file dei temi vanno tutti da qualche parte sotto / contenuto. Per lo script, li abbiamo semplicemente inseriti direttamente in / script. Inizialmente questo era per ottenere JS intellisense da VS, ma ora che otteniamo JS intellisense da R # indipendentemente dal posizionamento in / script, suppongo che potremmo deviare da quello e dividere gli script per cartella per organizzare meglio. Stai usando ReSharper? È oro puro IMO.
Un altro piccolo pezzo d'oro che aiuta molto con il refactoring è T4MVC. Usando questo, non abbiamo bisogno di digitare percorsi stringa per nomi di area, nomi di controller, nomi di azioni, persino file in contenuto e script. T4MVC digita fortemente tutte le corde magiche per te. Ecco un piccolo esempio di come la struttura del tuo progetto non conta tanto se stai usando T4MVC:
// no more magic strings in route definitions
context.MapRoutes(null,
new[] { string.Empty, "features", "features/{version}" },
new
{
area = MVC.PreviewArea.Name,
controller = MVC.PreviewArea.Features.Name,
action = MVC.PreviewArea.Features.ActionNames.ForPreview,
version = "december-2011-preview-1",
},
new { httpMethod = new HttpMethodConstraint("GET") }
);
@* T4MVC renders .min.js script versions when project is targeted for release *@
<link href="@Url.Content(Links.content.Site_css)?r=201112B" rel="stylesheet" />
<script src="@Url.Content(Links.scripts.jquery_1_7_1_js)" type="text/javascript">
</script>
@* render a route URL as if you were calling an action method directly *@
<a href="@Url.Action(MVC.MyAreaName.MyControllerName.MyActionName
(Model.SomeId))">@Html.DisplayFor(m => m.SomeText)</a>
// call action redirects as if you were executing an action method
return RedirectToAction(MVC.Area.MyController.DoSomething(obj1.Prop, null));
2. Accesso ai dati: non ho esperienza con PetaPoco, ma sono sicuro che vale la pena dare un'occhiata. Per i report complessi, hai preso in considerazione i servizi di report di SQL Server? Oppure, stai correndo su un diverso db? Mi dispiace non sono chiaro su cosa esattamente stai chiedendo. Utilizziamo EF + LINQ, ma forniamo anche alcune conoscenze su come generare report nelle classi di dominio. Pertanto, disponiamo di un repository di chiamate al servizio di dominio delle chiamate del controller anziché di un repository di chiamate del controller direttamente. Per i report ad hoc utilizziamo SQL Reporting Services, che di nuovo non è perfetto, ma ai nostri utenti piace essere in grado di trasferire facilmente i dati in Excel e SSRS ci semplifica.
3. Organizzazione del codice lato client e rendering dell'interfaccia utente: è qui che penso di poter offrire un aiuto. Prendi una pagina dal libro di convalida discreta MVC e AJAX discreto. Considera questo:
<img id="loading_spinner" src="/path/to/img" style="display:none;" />
<h2 id="loading_results" style="display:none;">
Please wait, this may take a while...
</h2>
<div id="results">
</div>
<input id="doSomethingDangerous" class="u-std-ajax"
type="button" value="I'm feeling lucky"
data-myapp-confirm="Are you sure you want to do this?"
data-myapp-show="loading_spinner,loading_results"
data-myapp-href="blah/DoDangerousThing" />
Ignora la funzione di successo ajax per ora (ne parleremo più avanti). Puoi cavartela con un singolo script per alcune delle tue azioni:
$('.u-std-ajax').click(function () {
// maybe confirm something first
var clicked = this;
var confirmMessage = $(clicked).data('myapp-confirm');
if (confirmMessage && !confirm(confirmMessage )) { return; }
// show a spinner? something global would be preferred so
// I dont have to repeat this on every page
// maybe the page should notify the user of what's going on
// in addition to the dialog?
var show = $(clicked).data('myapp-show');
if (show) {
var i, showIds = show.split(',');
for (i = 0; i < showIds.length; i++) {
$('#' + showIds[i]).show();
}
}
var url = $(clicked).data('myapp-href');
if (url) {
$.ajax({
url: url,
complete: function () {
// Need to hide the spinner, again would prefer to
// have this done elsewhere
if (show) {
for (i = 0; i < showIds.length; i++) {
$('#' + showIds[i]).hide();
}
}
}
});
}
});
Il codice sopra si occuperà della conferma, mostrando lo spinner, mostrando il messaggio di attesa e nascondendo il messaggio spinner / wait dopo che la chiamata ajax è stata completata. Configurare i comportamenti utilizzando gli attributi data- *, come le librerie discrete.
Domande generali
- Client MVC vs. server MVC? Non ho provato a chiarire le azioni intraprese nella funzione di successo perché sembra che il controller stia restituendo JSON. Se i tuoi controller restituiscono JSON, potresti dare un'occhiata a KnockoutJS. Knockout JS versione 2.0 è stato rilasciato oggi . Può collegarsi direttamente al tuo JSON, in modo che un clic osservabile possa associare automaticamente i dati ai tuoi modelli javascript. D'altra parte se non ti dispiace che i tuoi metodi di azione ajax restituiscano HTML anziché JSON, possono restituire l'UL già costruito con i suoi figli LI e puoi aggiungerlo a un elemento usando data-myapp-response = "risultati". La tua funzione di successo dovrebbe apparire così:
success: function(html) {
var responseId = $(clicked).data('myapp-response');
if (responseId) {
$('#' + responseId).empty().html(html);
}
}
Per riassumere la mia migliore risposta per questo, se devi restituire JSON dai tuoi metodi di azione, stai saltando la Vista lato server, quindi questo non è davvero MVC server - è solo MC. Se si restituisce PartialViewResult con html alle chiamate ajax, si tratta del server MVC. Quindi, se la tua app deve restituire dati JSON per chiamate ajax, usa il client MVVM come KnockoutJS.
Ad ogni modo, non mi piace il JS che hai pubblicato perché mescola il layout (tag html) con il comportamento (caricamento dati asincrono). La scelta del server MVC con viste HTML parziali o del client MVVM con dati viewmodel JSON puri risolverà questo problema, ma la costruzione manuale di DOM / HTML in javascript viola la separazione delle preoccupazioni.
- Creazione di file Javascript Apparentemente le funzionalità di minificazione sono disponibili in .NET 4.5 . Se segui il percorso discreto, non dovrebbe esserci nulla che ti impedisca di caricare tutto il tuo JS in 1 file di script. Starei attento a creare diversi file JS per ogni tipo di entità, finirai con un'esplosione di file JS. Ricorda, una volta caricato il file di script, il browser dovrebbe memorizzarlo nella cache per richieste future.
- Query complesse che non considero avere funzioni come impaginazione, ordinamento, ecc. Come complesse. La mia preferenza è di gestirlo con gli URL e la logica lato server, per rendere le query db limitate se necessario. Tuttavia, siamo distribuiti in Azure, quindi l'ottimizzazione delle query è importante per noi. Ad esempio: /widgets/show-{pageSize}-per-page/page-{pageNumber}/sort-by-{sortColumn}-{sortDirection}/{keyword}
. EF e LINQ to Entities possono gestire l'impaginazione e l'ordinamento con metodi come .Take (), .Skip (), .OrderBy () e .OrderByDescending (), in modo da ottenere ciò di cui hai bisogno durante il viaggio db. Non ho ancora trovato la necessità di un clientlib, quindi onestamente non ne so molto. Cerca altre risposte per ulteriori consigli al riguardo.
- Progetto seta Non ne ho mai sentito parlare, dovrò verificarlo. Sono un grande fan di Steve Sanderson, dei suoi libri, del suo BeginCollectionItem HtmlHelper e del suo blog. Detto questo, non ho alcuna esperienza con KnockoutJS in produzione . Ho controllato i suoi tutorial, ma cerco di non impegnarmi in qualcosa fino a quando non è almeno la versione 2.0. Come ho già detto, KnockoutJS 2.0 è stato appena rilasciato.
- Livello N Se per livello intendi una diversa macchina fisica, allora no, non penso che nulla vada fuori da nessuna finestra. Generalmente a 3 livelli significa che hai 3 macchine. Quindi potresti avere un client grasso come livello di presentazione, che viene eseguito sul computer di un utente. Il client fat potrebbe accedere a un livello di servizio, che viene eseguito su un server delle applicazioni e restituisce XML o altro al client fat. E il livello di servizio potrebbe ottenere i suoi dati da un server SQL su una terza macchina.
MVC è un livello, su 1 livello. I controller, i modelli e le viste fanno tutti parte del livello presentazione, che è di 1 livello nell'architettura fisica. MVC implementa il modello Model-View-Controller, che è dove potresti vedere livelli aggiuntivi. Tuttavia, cerca di non pensare a questi 3 aspetti come livelli o livelli. Prova a pensare a tutti e tre come a preoccupazioni sul livello di presentazione.
Aggiornamento dopo commento pres / bus / dati
Ok, quindi stai usando il livello e il livello in modo intercambiabile. Di solito uso il termine "livello" per le divisioni logiche / di progetto / di assieme e livello per la separazione fisica della rete. Dispiace per la confusione.
Troverai un bel po 'di persone nel campo MVC che affermano che non dovresti usare i "Modelli" in MVC per il tuo modello di dati di entità, né che dovresti usare i tuoi Controller per la logica aziendale. Idealmente, i tuoi modelli dovrebbero essere ViewModels specifici per la vista. Usando qualcosa come Automapper, prendi le tue entità dal tuo modello di dominio e le DTO in ViewModels, scolpite appositamente per l'uso dalla vista.
Qualsiasi regola aziendale dovrebbe anche far parte del tuo dominio e puoi implementarle usando i servizi di dominio / modello di fabbrica / qualunque cosa sia appropriata nel tuo livello di dominio, non nel livello di presentazione MVC. I controller dovrebbero essere stupidi, anche se non altrettanto stupidi come i modelli, e dovrebbero dare la responsabilità al dominio per tutto ciò che richiede conoscenza aziendale. I controller gestiscono il flusso di richieste e risposte HTTP, ma qualsiasi cosa con un reale valore aziendale dovrebbe essere superiore al livello di remunerazione del controller.
Quindi, puoi ancora avere un'architettura a strati, con MVC come livello di presentazione. È un client del livello applicazione, livello di servizio o livello di dominio, a seconda di come lo si progetta. Ma alla fine il tuo modello di entità dovrebbe far parte del dominio, non modelli in MVC.