TerryR mój przyjacielu, ty i ja powinniśmy się napić. Mamy podobne problemy.
1. Struktura projektu: Zgadzam się z Eduardo, że struktura folderów w aplikacji MVC pozostawia coś do życzenia. Masz swoje standardowe foldery Kontrolery, Modele i Widoki. Ale potem folder Widoki zostaje podzielony na inny folder dla każdego kontrolera oraz folder współdzielony. I każde Widoki / Nazwa Kontrolera lub Widoki / Udostępnione można podzielić na EditorTemplates i DisplayTemplates. Pozwala jednak zdecydować, jak zorganizować folder Modele (możesz to zrobić z podfolderami lub bez nich i dodatkowymi deklaracjami przestrzeni nazw).
Boże, zabraniaj, że używasz Obszarów, które duplikują strukturę folderów Kontrolery, Modele i Widoki dla każdego obszaru.
/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
Oznacza to, że jeśli pracujesz z czymś takim jak WidgetController, musisz zajrzeć do innych folderów, aby znaleźć powiązane WidgetViewModels, WidgetViews, WidgetEditorTemplates, WidgetDisplayTemplates itp. Choć jest to uciążliwe, trzymam się tego i nie odchodzę od te konwencje MVC. Jeśli chodzi o umieszczanie modelu, kontrolera i widoku w tym samym folderze, ale z różnymi przestrzeniami nazw, unikam tego, ponieważ używam ReSharper. Spowoduje to podkreślenie przestrzeni nazw, która nie pasuje do folderu, w którym znajduje się klasa. Wiem, że mógłbym wyłączyć tę funkcję R #, ale pomaga to w innych częściach projektu.
W przypadku plików nieklasowych MVC zapewnia Treść i Skrypty od razu po wyjęciu z pudełka. Staramy się zachować wszystkie nasze pliki statyczne / nieskompilowane w tych miejscach, ponownie, zgodnie z konwencją. Za każdym razem, gdy dołączamy bibliotekę js, która używa motywów (obrazów i lub css), wszystkie pliki motywów przechodzą gdzieś w / content. W przypadku skryptu po prostu umieszczamy je wszystkie bezpośrednio w / scripts. Pierwotnie miało to na celu uzyskanie inteligencji JS z VS, ale teraz, gdy otrzymujemy inteligencję JS z R # niezależnie od umieszczenia w / scripts, przypuszczam, że moglibyśmy od tego odstąpić i podzielić skrypty według folderów, aby lepiej zorganizować. Czy używasz ReSharper? To jest czyste złoto IMO.
Kolejnym małym kawałkiem złota, który bardzo pomaga przy refaktoryzacji, jest T4MVC. Korzystając z tego, nie musimy wpisywać ścieżek ciągów dla nazw obszarów, nazw kontrolerów, nazw akcji, a nawet plików w treści i skryptach. T4MVC zdecydowanie wpisuje dla ciebie wszystkie magiczne ciągi. Oto mała próbka tego, jak struktura projektu nie ma większego znaczenia, jeśli używasz 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. Dostęp do danych: Nie mam doświadczenia z PetaPoco, ale jestem pewien, że warto to sprawdzić. Czy w przypadku złożonych raportów rozważałeś usługi raportowania SQL Server? A może używasz innej bazy danych? Przepraszam, nie jestem pewien, o co dokładnie prosisz. Korzystamy z EF + LINQ, ale mamy też pewną wiedzę na temat generowania raportów w klasach domen. Mamy więc repozytorium wywołań usług domenowych kontrolerów zamiast bezpośredniego repozytorium wywołań kontrolerów. W przypadku raportów ad hoc korzystamy z usług SQL Reporting Services, co znowu nie jest idealne, ale nasi użytkownicy lubią mieć możliwość łatwego wprowadzania danych do Excela, a SSRS ułatwia nam to.
3. Organizacja kodu po stronie klienta i renderowanie interfejsu użytkownika: Myślę, że w tym miejscu mogę zaoferować pomoc. Weź stronę z książki MVC dyskretnej weryfikacji i dyskretnej AJAX. Rozważ to:
<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" />
Na razie zignoruj funkcję sukcesu ajax (więcej na ten temat później). Możesz uciec od jednego skryptu dla niektórych swoich działań:
$('.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();
}
}
}
});
}
});
Powyższy kod zajmie się potwierdzeniem, wyświetleniem pokrętła, wyświetleniem komunikatu oczekiwania i ukryciem komunikatu pokrętła / oczekiwania po zakończeniu wywołania ajax. Zachowania konfiguruje się za pomocą atrybutów data- *, takich jak dyskretne biblioteki.
Ogólne pytania
- Klient MVC kontra serwer MVC? Nie próbowałem zsyntetyzować działań podjętych w funkcji sukcesu, ponieważ wygląda na to, że kontroler zwraca JSON. Jeśli kontrolery zwracają JSON, warto przyjrzeć się KnockoutJS. Knockout JS wersja 2.0 została wydana dzisiaj . Można podłączyć bezpośrednio do JSON, dzięki czemu możliwe do zaobserwowania kliknięcie może automatycznie powiązać dane z szablonami javascript. Z drugiej strony, jeśli nie masz nic przeciwko temu, że twoje metody akcji ajax zwracają HTML zamiast JSON, mogą zwrócić już skonstruowane UL z jego potomkami LI, i możesz dołączyć to do elementu za pomocą data-myapp-response = „wyniki”. Twoja funkcja sukcesu wyglądałaby wtedy następująco:
success: function(html) {
var responseId = $(clicked).data('myapp-response');
if (responseId) {
$('#' + responseId).empty().html(html);
}
}
Podsumowując moją najlepszą odpowiedź na to pytanie, jeśli musisz zwrócić JSON z metod działania, pomijasz widok po stronie serwera, więc to naprawdę nie jest serwer MVC - to tylko MC. Jeśli zwrócisz PartialViewResult z html do wywołań ajax, to jest to serwer MVC. Jeśli więc aplikacja musi zwrócić dane JSON dla wywołań ajax, użyj klienta MVVM, takiego jak KnockoutJS.
Tak czy inaczej, nie podoba mi się opublikowany przez Ciebie JS, ponieważ łączy on Twój układ (tagi HTML) z zachowaniem (asynchroniczne ładowanie danych). Wybór MVC serwera z częściowymi widokami HTML lub klienta MVVM z czystymi danymi JSON viewmodel rozwiąże ten problem, ale ręczne tworzenie DOM / HTML w javascript narusza separację problemów.
- Tworzenie plików JavaScript Najwyraźniej funkcje minimalizacji są dostępne w .NET 4.5 . Jeśli pójdziesz dyskretną drogą, nic nie powinno Cię powstrzymywać przed załadowaniem całego JS w 1 pliku skryptu. Byłbym ostrożny przy tworzeniu różnych plików JS dla każdego typu encji, skończy się to eksplozją pliku JS. Pamiętaj, że po załadowaniu pliku skryptu przeglądarka powinna zapisać go w pamięci podręcznej na przyszłe żądania.
- Złożone zapytania Nie uważam, że takie funkcje jak paginacja, sortowanie itp. Są złożone. Wolę sobie z tym poradzić za pomocą logiki adresów URL i logiki po stronie serwera, aby zapytania db były tak ograniczone, jak to konieczne. Jesteśmy jednak wdrożeni na platformie Azure, dlatego optymalizacja zapytań jest dla nas ważna. Na przykład: /widgets/show-{pageSize}-per-page/page-{pageNumber}/sort-by-{sortColumn}-{sortDirection}/{keyword}
. EF i LINQ do encji mogą obsługiwać paginację i sortowanie za pomocą metod takich jak .Take (), .Skip (), .OrderBy () i .OrderByDescending (), dzięki czemu otrzymujesz to, czego potrzebujesz podczas podróży db. Nie znalazłem jeszcze potrzeby posiadania klienta, więc szczerze mówiąc, niewiele o nich wiem. Więcej odpowiedzi na ten temat znajdziesz w innych odpowiedziach.
- Projekt jedwabiu Nigdy o nim nie słyszałem, trzeba będzie to sprawdzić. Jestem wielkim fanem Steve'a Sandersona, jego książek, jego BeginCollectionItem HtmlHelper i jego bloga. To powiedziawszy, nie mam żadnego doświadczenia z KnockoutJS w produkcji . Sprawdziłem jego tutoriale, ale staram się nie angażować w coś, dopóki nie będzie to co najmniej wersja 2.0. Jak wspomniałem, KnockoutJS 2.0 został właśnie wydany.
- N-poziom Jeśli przez poziom masz na myśli inną maszynę fizyczną, to nie, nie sądzę, żeby coś wychodziło z okna. Ogólnie 3-poziom oznacza, że masz 3 maszyny. Więc możesz mieć grubego klienta jako warstwę prezentacji, która działa na komputerze użytkownika. Gruby klient może uzyskać dostęp do warstwy usługi, która działa na serwerze aplikacji i zwraca XML lub cokolwiek do grubego klienta. Warstwa usług może pobierać dane z serwera SQL na 3. komputerze.
MVC to jedna warstwa, na 1 poziomie. Kontrolery, modele i widoki są częścią Warstwy prezentacji, która jest 1 poziomem w architekturze fizycznej. MVC implementuje wzorzec Model-View-Controller, gdzie możesz zobaczyć dodatkowe warstwy. Staraj się jednak nie myśleć o tych 3 aspektach jako o poziomach lub warstwach. Spróbuj pomyśleć o wszystkich 3 z nich jako o problemach z warstwą prezentacji.
Aktualizacja po komentarzu pres / bus / data
Okej, więc używasz warstw i warstw zamiennie. Zazwyczaj używam terminu „warstwa” dla logicznych / projektowych / asemblacyjnych podziałów, a poziomu dla fizycznego oddzielenia sieci. Przepraszam za zamieszanie.
W obozie MVC znajduje się sporo osób, które mówią, że nie należy używać „modeli” w MVC dla modelu danych jednostki ani używać kontrolerów do logiki biznesowej. Idealnie byłoby, gdyby twoje modele były modelami ViewModels. Używając czegoś takiego jak Automapper, bierzesz swoje byty z modelu domeny i dodajesz je do ViewModels, specjalnie zaprojektowanych do użycia przez widok.
Wszelkie reguły biznesowe powinny również stanowić część domeny i można je wdrożyć za pomocą usług domenowych / wzorca fabrycznego / cokolwiek odpowiedniego w warstwie domeny, a nie w warstwie prezentacji MVC. Administratorzy powinni być głupi, choć nie tak głupi jak modele, i powinni ponosić odpowiedzialność za domenę za wszystko, co wymaga wiedzy biznesowej. Kontrolery zarządzają przepływem żądań i odpowiedzi HTTP, ale wszystko, co ma prawdziwą wartość biznesową, powinno znajdować się powyżej poziomu wynagrodzenia kontrolera.
Tak więc nadal możesz mieć architekturę warstwową, z MVC jako warstwą prezentacji. Jest to klient warstwy aplikacji, warstwy usługi lub warstwy domeny, w zależności od tego, w jaki sposób ją tworzysz. Ale ostatecznie twój model bytu powinien być częścią domeny, a nie modeli w MVC.