TerryR mon ami, toi et moi devrions boire un verre. Nous avons des problèmes similaires.
1. Structure du projet: je suis d'accord avec Eduardo que la structure des dossiers dans une application MVC laisse à désirer. Vous disposez de vos dossiers Contrôleurs, Modèles et Vues standard. Mais le dossier Vues est ensuite divisé en un dossier différent pour chaque contrôleur, plus un dossier partagé. Et chaque Views / ControllerName ou Views / Shared peut être décomposé en EditorTemplates et DisplayTemplates. Mais il vous permet de décider comment organiser votre dossier Modèles (vous pouvez le faire avec ou sans sous-dossiers et déclarations d'espace de noms supplémentaires).
Dieu nous en préserve, vous utilisez des zones, qui dupliquent la structure de dossiers Contrôleurs, Modèles et Vues pour chaque zone.
/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
Cela signifie que si vous travaillez avec quelque chose comme un WidgetController, vous devez rechercher dans d'autres dossiers pour trouver les WidgetViewModels, WidgetViews, WidgetEditorTemplates, WidgetDisplayTemplates, etc. ces conventions MVC. Pour ce qui est de mettre un modèle, un contrôleur et une vue dans le même dossier mais avec des espaces de noms différents, j'évite cela car j'utilise ReSharper. Il soulignera un espace de noms qui ne correspond pas au dossier où se trouve la classe. Je sais que je pourrais désactiver cette fonction R #, mais cela aide dans d'autres parties du projet.
Pour les fichiers non-classe, MVC vous donne du contenu et des scripts prêts à l'emploi. Nous essayons de garder tous nos fichiers statiques / non compilés à ces endroits, encore une fois, pour suivre la convention. Chaque fois que nous incorporons une bibliothèque js qui utilise des thèmes (images et ou css), les fichiers de thème vont tous quelque part sous / content. Pour le script, nous les mettons tous directement dans / scripts. À l'origine, c'était pour obtenir JS intellisense de VS, mais maintenant que nous obtenons JS intellisense de R # quel que soit le placement dans / scripts, je suppose que nous pourrions nous en écarter et diviser les scripts par dossier pour mieux organiser. Utilisez-vous ReSharper? C'est de l'or pur IMO.
Un autre petit morceau d'or qui aide beaucoup à la refactorisation est le T4MVC. En utilisant cela, nous n'avons pas besoin de taper des chemins de chaîne pour les noms de zone, les noms de contrôleur, les noms d'action, même les fichiers dans le contenu et les scripts. T4MVC tape fortement toutes les cordes magiques pour vous. Voici un petit exemple de la façon dont la structure de votre projet n'a pas autant d'importance si vous utilisez 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. Accès aux données: je n'ai aucune expérience avec PetaPoco, mais je suis sûr que cela vaut la peine de vérifier. Pour vos rapports complexes, avez-vous envisagé les services de création de rapports SQL Server? Ou exécutez-vous sur une autre base de données? Désolé, je ne sais pas exactement ce que vous demandez. Nous utilisons EF + LINQ, mais nous mettons également certaines connaissances sur la façon de générer des rapports dans les classes de domaine. Ainsi, nous avons un référentiel d'appels de service de domaine d'appel de contrôleur, au lieu d'avoir un référentiel d'appels de contrôleur directement. Pour les rapports ad hoc, nous utilisons SQL Reporting Services, ce qui n'est pas parfait, mais nos utilisateurs aiment pouvoir importer facilement des données dans Excel, et SSRS nous facilite la tâche.
3. Organisation du code côté client et rendu de l'interface utilisateur: c'est là que je pense que je peux être en mesure d'offrir de l'aide. Prenez une page du livre de validation MVC discrète et AJAX discrète. Considère ceci:
<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" />
Ignorez la fonction de succès ajax pour l'instant (plus d'informations à ce sujet plus tard). Vous pouvez vous en tirer avec un seul script pour certaines de vos actions:
$('.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();
}
}
}
});
}
});
Le code ci-dessus prendra soin de la confirmation, en montrant le spinner, en affichant le message d'attente et en masquant le message spinner / wait une fois l'appel ajax terminé. Vous configurez les comportements à l'aide d'attributs data- *, comme les bibliothèques discrètes.
Questions générales
- MVC client vs MVC serveur? Je n'ai pas essayé de librarifier les actions que vous avez effectuées dans la fonction de réussite, car il semble que votre contrôleur retourne JSON. Si vos contrôleurs retournent JSON, vous voudrez peut-être regarder KnockoutJS. Knockout JS version 2.0 est sortie aujourd'hui . Il peut se connecter directement à votre JSON, de sorte qu'un clic observable peut automatiquement lier des données à vos modèles javascript. D'un autre côté, si cela ne vous dérange pas que vos méthodes d'action ajax retournent du HTML au lieu de JSON, elles peuvent renvoyer l'UL déjà construite avec ses enfants LI, et vous pouvez l'ajouter à un élément en utilisant data-myapp-response = "résultats". Votre fonction de réussite ressemblerait alors à ceci:
success: function(html) {
var responseId = $(clicked).data('myapp-response');
if (responseId) {
$('#' + responseId).empty().html(html);
}
}
Pour résumer ma meilleure réponse à cela, si vous devez renvoyer JSON à partir de vos méthodes d'action, vous sautez la vue côté serveur, donc ce n'est vraiment pas le MVC du serveur - c'est juste MC. Si vous renvoyez PartialViewResult avec html aux appels ajax, il s'agit du serveur MVC. Donc, si votre application doit renvoyer des données JSON pour les appels ajax, utilisez MVVM client comme KnockoutJS.
Quoi qu'il en soit, je n'aime pas le JS que vous avez publié car il mélange votre mise en page (balises html) avec un comportement (chargement de données asynchrones). Choisir MVC serveur avec des vues html partielles ou MVVM client avec des données de modèle de vue JSON pur résoudra ce problème pour vous, mais la construction manuelle de DOM / HTML en javascript viole la séparation des préoccupations.
- Création de fichiers Javascript Apparemment, des fonctionnalités de minification arrivent dans .NET 4.5 . Si vous optez pour la voie discrète, rien ne devrait vous empêcher de charger tous vos fichiers de script JS en 1. Je serais prudent sur la création de différents fichiers JS pour chaque type d'entité, vous vous retrouverez avec une explosion de fichiers JS. N'oubliez pas qu'une fois votre fichier de script chargé, le navigateur doit le mettre en cache pour les demandes futures.
- Requêtes complexes Je ne considère pas que des fonctionnalités telles que la pagination, le tri, etc. soient complexes. Ma préférence est de gérer cela avec les URL et la logique côté serveur, pour rendre les requêtes db aussi limitées que nécessaire. Cependant, nous sommes déployés sur Azure, donc l'optimisation des requêtes est importante pour nous. Par exemple: /widgets/show-{pageSize}-per-page/page-{pageNumber}/sort-by-{sortColumn}-{sortDirection}/{keyword}
. EF et LINQ to Entities peuvent gérer la pagination et le tri avec des méthodes telles que .Take (), .Skip (), .OrderBy () et .OrderByDescending (), afin que vous obteniez ce dont vous avez besoin pendant le trajet db. Je n'ai pas encore trouvé le besoin d'une bibliothèque cliente, donc honnêtement, je ne sais pas grand-chose à leur sujet. Consultez d'autres réponses pour plus de conseils à ce sujet.
- Projet soie Jamais entendu parler de celui-ci, devra le vérifier. Je suis un grand fan de Steve Sanderson, de ses livres, de son BeginCollectionItem HtmlHelper et de son blog. Cela dit, je n'ai aucune expérience avec KnockoutJS en production . J'ai vérifié ses tutoriels, mais j'essaie de ne pas m'engager sur quelque chose jusqu'à ce que ce soit au moins la version 2.0. Comme je l'ai mentionné, KnockoutJS 2.0 vient de sortir.
- N-tier Si par niveau vous voulez dire une machine physique différente, alors non, je ne pense pas que quelque chose sort par les fenêtres. En général, 3 niveaux signifie que vous avez 3 machines. Vous pouvez donc avoir un gros client comme niveau de présentation, qui s'exécute sur la machine d'un utilisateur. Le gros client peut accéder à un niveau de service, qui s'exécute sur un serveur d'applications et renvoie XML ou autre au gros client. Et le niveau de service peut obtenir ses données d'un serveur SQL sur une 3e machine.
MVC est une couche, sur 1 niveau. Vos contrôleurs, modèles et vues font tous partie de votre couche de présentation, qui est à 1 niveau dans l'architecture physique. MVC implémente le modèle Model-View-Controller, qui est l'endroit où vous pouvez voir des couches supplémentaires. Cependant, essayez de ne pas considérer ces 3 aspects comme des niveaux ou des couches. Essayez de les considérer tous comme des problèmes de présentation.
Mise à jour après le commentaire de pres / bus / data
D'accord, vous utilisez donc les niveaux et les couches de manière interchangeable. J'utilise généralement le terme «couche» pour les divisions logiques / projets / assemblages et le niveau pour la séparation physique du réseau. Désolé pour la confusion.
Vous trouverez un certain nombre de personnes dans le camp MVC qui disent que vous ne devez pas utiliser les «modèles» dans MVC pour votre modèle de données d'entité, ni utiliser vos contrôleurs pour la logique métier. Idéalement, vos modèles doivent être des ViewModels spécifiques à la vue. En utilisant quelque chose comme Automapper, vous prenez vos entités de votre modèle de domaine et les DTO dans ViewModels, sculptées spécifiquement pour être utilisées par la vue.
Toutes les règles métier doivent également faire partie de votre domaine, et vous pouvez les implémenter à l'aide des services de domaine / modèle d'usine / tout ce qui est approprié dans votre couche de domaine, pas dans la couche de présentation MVC. Les contrôleurs doivent être stupides, mais pas tout à fait aussi stupides que les modèles, et doivent donner la responsabilité au domaine pour tout ce qui nécessite des connaissances commerciales. Les contrôleurs gèrent le flux des demandes et des réponses HTTP, mais tout ce qui a une réelle valeur commerciale doit être supérieur au niveau de rémunération du contrôleur.
Ainsi, vous pouvez toujours avoir une architecture en couches, avec MVC comme couche de présentation. Il s'agit d'un client de votre couche d'application, couche de service ou couche de domaine, selon la façon dont vous l'architecturez. Mais en fin de compte, votre modèle d'entité doit faire partie du domaine, pas les modèles dans MVC.