TerryR mein Freund, du und ich sollten etwas trinken. Wir haben ähnliche Probleme.
1. Projektstruktur: Ich stimme Eduardo zu, dass die Ordnerstruktur in einer MVC-App zu wünschen übrig lässt. Sie haben Ihre Standardordner für Controller, Modelle und Ansichten. Dann wird der Ordner "Ansichten" für jeden Controller in einen anderen Ordner und einen freigegebenen Ordner unterteilt. Jeder Views / ControllerName oder Views / Shared kann in EditorTemplates und DisplayTemplates unterteilt werden. Sie können jedoch entscheiden, wie Sie Ihren Models-Ordner organisieren möchten (Sie können mit oder ohne Unterordner und zusätzliche Namespace-Deklarationen vorgehen).
Gott bewahre, dass Sie Bereiche verwenden, die die Ordnerstruktur für Controller, Modelle und Ansichten für jeden Bereich duplizieren.
/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
Das bedeutet, wenn Sie mit so etwas wie einem WidgetController arbeiten, müssen Sie in anderen Ordnern nach den zugehörigen WidgetViewModels, WidgetViews, WidgetEditorTemplates, WidgetDisplayTemplates usw. suchen diese MVC-Konventionen. Soweit ich ein Modell, einen Controller und eine Ansicht in denselben Ordner mit unterschiedlichen Namespaces lege, vermeide ich dies, da ich ReSharper verwende. Ein Namespace, der nicht mit dem Ordner übereinstimmt, in dem sich die Klasse befindet, wird schnell unterstrichen. Ich weiß, dass ich diese R # -Funktion deaktivieren kann, aber sie hilft in anderen Teilen des Projekts.
Bei Dateien, die keine Klassendateien sind, erhalten Sie von MVC standardmäßig Inhalte und Skripts. Wir versuchen, alle unsere statischen / nicht kompilierten Dateien an diesen Stellen aufzubewahren, um der Konvention zu folgen. Jedes Mal, wenn wir eine js-Bibliothek einbinden, die Themen (Bilder und / oder CSS) verwendet, werden die Themendateien irgendwo unter / content abgelegt. Bei Skripten fügen wir sie alle direkt in / scripts ein. Ursprünglich sollte dies JS Intellisense von VS erhalten, aber jetzt, da wir JS Intellisense von R # unabhängig von der Platzierung in / scripts erhalten, können wir davon abweichen und die Skripte zur besseren Organisation nach Ordnern unterteilen. Verwenden Sie ReSharper? Es ist reines Gold IMO.
Ein weiteres kleines Goldstück, das beim Refactoring sehr hilfreich ist, ist T4MVC. Auf diese Weise müssen wir keine Zeichenfolgenpfade für Bereichsnamen, Controllernamen, Aktionsnamen und sogar Dateien in Inhalten und Skripten eingeben. T4MVC tippt alle magischen Zeichenfolgen nachdrücklich für Sie. Hier ein kleines Beispiel, wie Ihre Projektstruktur nicht so wichtig ist, wenn Sie T4MVC verwenden:
// 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. Datenzugriff: Ich habe keine Erfahrung mit PetaPoco, bin mir aber sicher, dass es sich lohnt, dies zu überprüfen. Haben Sie bei Ihren komplexen Berichten SQL Server Reporting Services in Betracht gezogen? Oder läufst du auf einer anderen Datenbank? Entschuldigung, mir ist nicht klar, wonach genau Sie fragen. Wir verwenden EF + LINQ, setzen aber auch bestimmte Kenntnisse zum Generieren von Berichten in Domänenklassen ein. Daher haben wir das Anruf-Repository des Domänen-Service für Controller-Aufrufe, anstatt direkt über das Anruf-Repository des Controllers zu verfügen. Für Ad-hoc-Berichte verwenden wir SQL Reporting Services, was wiederum nicht perfekt ist, aber unsere Benutzer möchten, dass Daten problemlos in Excel importiert werden können, und SSRS vereinfacht dies für uns.
3. Organisation des clientseitigen Codes und Rendern der Benutzeroberfläche: Hier kann ich möglicherweise Hilfe anbieten. Nehmen Sie eine Seite aus dem Buch von MVC unauffällige Validierung und unauffällige AJAX. Bedenken Sie:
<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" />
Ignorieren Sie vorerst die Ajax-Erfolgsfunktion (dazu später mehr). Sie können mit einem einzigen Skript für einige Ihrer Aktionen davonkommen:
$('.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();
}
}
}
});
}
});
Der obige Code kümmert sich um die Bestätigung, zeigt den Spinner, zeigt die Wartemeldung und versteckt den Spinner / die Wartemeldung, nachdem der Ajax-Aufruf abgeschlossen ist. Sie konfigurieren das Verhalten mithilfe von data- * Attributen wie den unauffälligen Bibliotheken.
Allgemeine Fragen
- Client MVC vs. Server MVC? Ich habe nicht versucht, die Aktionen, die Sie in der Erfolgsfunktion ausgeführt haben, zu librarisieren, da Ihr Controller anscheinend JSON zurückgibt. Wenn Ihre Controller JSON zurückgeben, sollten Sie sich KnockoutJS ansehen. Knockout JS Version 2.0 wurde heute veröffentlicht . Es kann direkt in Ihren JSON-Code eingefügt werden, sodass durch einen beobachtbaren Klick Daten automatisch an Ihre JavaScript-Vorlagen gebunden werden. Wenn es Ihnen andererseits nichts ausmacht, dass Ihre Ajax-Aktionsmethoden HTML anstelle von JSON zurückgeben, können sie den bereits erstellten UL mit seinen untergeordneten LI-Elementen zurückgeben, und Sie können dies mit data-myapp-response = an ein Element anhängen "Ergebnisse". Ihre Erfolgsfunktion würde dann so aussehen:
success: function(html) {
var responseId = $(clicked).data('myapp-response');
if (responseId) {
$('#' + responseId).empty().html(html);
}
}
Um meine beste Antwort darauf zusammenzufassen: Wenn Sie JSON von Ihren Aktionsmethoden zurückgeben müssen, überspringen Sie die serverseitige Ansicht. Dies ist also wirklich keine Server-MVC, sondern nur MC. Wenn Sie PartialViewResult mit HTML an Ajax-Aufrufe zurückgeben, ist dies Server-MVC. Wenn Ihre App JSON-Daten für Ajax-Aufrufe zurückgeben muss, verwenden Sie Client-MVVM wie KnockoutJS.
In beiden Fällen gefällt mir das von Ihnen gepostete JS nicht, da es Ihr Layout (HTML-Tags) mit dem Verhalten (asynchrones Laden von Daten) mischt. Wenn Sie Server-MVC mit teilweisen HTML-Ansichten oder Client-MVVM mit reinen JSON-Ansichtsmodelldaten auswählen, wird dieses Problem für Sie gelöst. Die manuelle Erstellung von DOM / HTML in JavaScript verstößt jedoch gegen die Trennung von Bedenken.
- Erstellung von Javascript-Dateien In .NET 4.5 sind anscheinend Minimierungsfunktionen enthalten . Wenn Sie den unauffälligen Weg gehen, sollte Sie nichts daran hindern, Ihre gesamte JS in 1-Skriptdatei zu laden. Ich würde vorsichtig sein, wenn Sie für jeden Entitätstyp unterschiedliche JS-Dateien erstellen würden. Am Ende wird die JS-Datei aufgelöst. Denken Sie daran, dass der Browser Ihre Skriptdatei nach dem Laden für zukünftige Anforderungen zwischenspeichern sollte.
- Komplexe Abfragen Ich halte Features wie Paginierung, Sortieren usw. nicht für komplex. Ich bevorzuge es, dies mit URLs und serverseitiger Logik zu behandeln, um die Datenbankabfragen so begrenzt wie nötig zu machen. Da wir jedoch in Azure bereitgestellt werden, ist die Abfrageoptimierung für uns wichtig. Zum Beispiel: /widgets/show-{pageSize}-per-page/page-{pageNumber}/sort-by-{sortColumn}-{sortDirection}/{keyword}
. EF und LINQ to Entities können Seitenumbrüche und Sortierungen mit Methoden wie .Take (), .Skip (), .OrderBy () und .OrderByDescending () verarbeiten, sodass Sie während des DB-Trips das bekommen, was Sie brauchen. Ich habe noch keine Notwendigkeit für eine Client-Bibliothek gefunden, daher weiß ich ehrlich gesagt nicht viel über sie. Weitere Ratschläge hierzu finden Sie in den anderen Antworten.
- Projekt Seide Noch nie von diesem gehört, müssen Sie es überprüfen. Ich bin ein großer Fan von Steve Sanderson, seinen Büchern, seinem BeginCollectionItem HtmlHelper und seinem Blog. Allerdings habe ich keine Erfahrung mit KnockoutJS in der Produktion . Ich habe die Tutorials ausprobiert, aber ich versuche nicht, mich auf etwas festzulegen, bis es mindestens Version 2.0 ist. Wie ich bereits erwähnte, wurde KnockoutJS 2.0 gerade veröffentlicht.
- N-Tier Wenn mit Tier eine andere physische Maschine gemeint ist, dann nein, ich glaube nicht, dass irgendetwas aus einem Fenster geht. Im Allgemeinen bedeutet 3-Tier, dass Sie 3 Maschinen haben. Möglicherweise haben Sie einen Fat Client als Präsentationsebene, der auf dem Computer eines Benutzers ausgeführt wird. Der Fat Client kann auf eine Serviceebene zugreifen, die auf einem Anwendungsserver ausgeführt wird und XML oder was auch immer an den Fat Client zurückgibt. Die Serviceschicht bezieht ihre Daten möglicherweise von einem SQL-Server auf einem dritten Computer.
MVC ist eine Schicht auf einer Ebene. Ihre Controller, Modelle und Ansichten sind alle Teil Ihrer Präsentationsebene, die in der physischen Architektur eine Ebene darstellt. MVC implementiert das Model-View-Controller-Muster, in dem möglicherweise zusätzliche Ebenen angezeigt werden. Versuchen Sie jedoch, diese drei Aspekte nicht als Ebenen oder Schichten zu betrachten. Stellen Sie sich alle drei als Bedenken in Bezug auf die Präsentationsebene vor.
Update nach Pres / Bus / Datenkommentar
Okay, Sie verwenden Tier und Layer austauschbar. Normalerweise verwende ich den Begriff "Layer" für logische / Projekt- / Baugruppentrennungen und Tier für die physische Netzwerktrennung. Entschuldigung für die Verwirrung.
Es gibt im MVC-Camp einige Leute, die sagen, Sie sollten weder die "Modelle" in MVC für Ihr Entitätsdatenmodell noch Ihre Controller für die Geschäftslogik verwenden. Idealerweise sollten Ihre Modelle ansichtsspezifische ViewModels sein. Mit etwas wie Automapper nehmen Sie Ihre Entitäten aus Ihrem Domain-Modell und führen sie in ViewModels ein, die speziell für die Verwendung durch die Ansicht erstellt wurden.
Alle Geschäftsregeln sollten auch Teil Ihrer Domain sein, und Sie können sie mithilfe von Domain-Services / Factory-Patterns / was auch immer in Ihrer Domain-Schicht und nicht in der MVC-Präsentationsschicht angemessen ist implementieren. Controller sollten dumm sein, wenn auch nicht ganz so dumm wie Modelle, und der Domäne die Verantwortung für alles übertragen, was Geschäftskenntnisse erfordert. Controller verwalten den Fluss von HTTP-Anforderungen und -Antworten, aber alles, was einen echten geschäftlichen Wert hat, sollte über dem Gehaltsniveau des Controllers liegen.
Sie können also weiterhin eine mehrschichtige Architektur mit MVC als Präsentationsebene verwenden. Es ist ein Client Ihrer Anwendungs-, Dienst- oder Domänenschicht, je nachdem, wie Sie ihn erstellen. Letztendlich sollte Ihr Entitätsmodell jedoch Teil der Domäne sein, nicht Modelle in MVC.