TerryR bạn của tôi, bạn và tôi nên có một thức uống. Chúng tôi có một số vấn đề tương tự.
1. Cấu trúc dự án: Tôi đồng ý với Eduardo rằng cấu trúc thư mục trong ứng dụng MVC để lại thứ gì đó mong muốn. Bạn có các thư mục Bộ điều khiển, Mô hình và Chế độ xem chuẩn. Nhưng sau đó, thư mục Lượt xem được chia thành một thư mục khác nhau cho mỗi Trình điều khiển, cộng với thư mục Dùng chung. Và mỗi Lượt xem / Tên điều khiển hoặc Lượt xem / Chia sẻ có thể được chia thành EditorTemsheet và DisplayTemsheet. Nhưng nó cho phép bạn quyết định cách tổ chức thư mục Mô hình của bạn (bạn có thể thực hiện có hoặc không có thư mục con & khai báo không gian tên bổ sung).
Chúa cấm bạn đang sử dụng các Khu vực trùng lặp cấu trúc thư mục Bộ điều khiển, Mô hình và Chế độ xem cho từng khu vực.
/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
Điều này có nghĩa là nếu bạn đang làm việc với một cái gì đó như WidgetContoder, bạn phải tìm trong các thư mục khác để tìm WidgetViewModels, WidgetViews, WidgetEditorTemsheet, WidgetDisplayTemsheet, v.v. những quy ước MVC này. Theo như đặt một mô hình, bộ điều khiển và chế độ xem trong cùng một thư mục nhưng với các không gian tên khác nhau, tôi tránh điều này vì tôi sử dụng ReSharper. Nó sẽ nguệch ngoạc gạch chân một không gian tên không khớp với thư mục nơi lớp được đặt. Tôi biết tôi có thể tắt tính năng R # này, nhưng nó giúp trong các phần khác của dự án.
Đối với các tệp không phải là lớp, MVC cung cấp cho bạn Nội dung và Tập lệnh. Chúng tôi cố gắng giữ tất cả các tệp tĩnh / không được biên dịch của chúng tôi ở những nơi này, một lần nữa, để tuân theo quy ước. Bất cứ khi nào chúng tôi kết hợp một thư viện js sử dụng các chủ đề (hình ảnh và hoặc css), các tệp chủ đề đều nằm ở đâu đó bên dưới / nội dung. Đối với tập lệnh, chúng tôi chỉ cần đặt tất cả chúng trực tiếp vào / script. Ban đầu, điều này là để nhận được intellisense từ VS, nhưng bây giờ chúng tôi nhận được intellisense từ R # bất kể vị trí trong / script, tôi cho rằng chúng ta có thể đi chệch khỏi đó và chia các tập lệnh theo thư mục để tổ chức tốt hơn. Bạn đang sử dụng ReSharper? Đó là IMO vàng nguyên chất.
Một miếng vàng nhỏ khác giúp ích rất nhiều cho việc tái cấu trúc là T4MVC. Sử dụng điều này, chúng ta không cần phải nhập các đường dẫn chuỗi cho tên khu vực, tên bộ điều khiển, tên hành động, thậm chí các tệp trong nội dung & tập lệnh. T4MVC mạnh mẽ gõ tất cả các chuỗi ma thuật cho bạn. Đây là một ví dụ nhỏ về cách cấu trúc dự án của bạn không quan trọng bằng nếu bạn đang sử dụng 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. Truy cập dữ liệu: Tôi không có kinh nghiệm với PetaPoco, nhưng tôi chắc chắn rằng nó đáng để kiểm tra. Đối với các báo cáo phức tạp của bạn, bạn đã xem xét các dịch vụ Báo cáo Máy chủ SQL chưa? Hoặc, bạn đang chạy trên một db khác nhau? Xin lỗi tôi không rõ chính xác những gì bạn đang yêu cầu. Chúng tôi sử dụng EF + LINQ, nhưng chúng tôi cũng đưa một số kiến thức nhất định về cách tạo báo cáo trong các lớp miền. Vì vậy, chúng ta có kho lưu trữ cuộc gọi dịch vụ miền điều khiển, thay vì có kho lưu trữ cuộc gọi điều khiển trực tiếp. Đối với các báo cáo đặc biệt, chúng tôi sử dụng Dịch vụ báo cáo SQL, một lần nữa không hoàn hảo, nhưng người dùng của chúng tôi muốn có thể đưa dữ liệu vào Excel một cách dễ dàng và SSRS giúp chúng tôi dễ dàng.
3. Tổ chức mã phía khách hàng và kết xuất giao diện người dùng: Đây là nơi tôi nghĩ rằng tôi có thể cung cấp một số trợ giúp. Lấy một trang từ cuốn sách xác thực MVC không phô trương và AJAX không phô trương. Xem xét điều này:
<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" />
Bỏ qua chức năng thành công ajax bây giờ (nhiều hơn về điều này sau). Bạn có thể thoát khỏi một tập lệnh cho một số hành động của mình:
$('.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();
}
}
}
});
}
});
Đoạn mã trên sẽ đảm nhận việc xác nhận, hiển thị spinner, hiển thị thông báo chờ và ẩn tin nhắn spinner / Wait sau khi cuộc gọi ajax hoàn tất. Bạn định cấu hình các hành vi bằng cách sử dụng các thuộc tính data- *, như các thư viện không phô trương.
Câu hỏi chung
- Máy khách MVC so với máy chủ MVC? Tôi đã không cố gắng hiệu chỉnh các hành động bạn đã thực hiện trong hàm thành công vì có vẻ như bộ điều khiển của bạn đang trả về JSON. Nếu bộ điều khiển của bạn đang trả về JSON, bạn có thể muốn xem KnockoutJS. Knockout JS phiên bản 2.0 đã được phát hành ngày hôm nay . Nó có thể cắm ngay vào JSON của bạn, để một nhấp chuột có thể quan sát có thể tự động liên kết dữ liệu với các mẫu javascript của bạn. Mặt khác, nếu bạn không phiền khi các phương thức hành động ajax của bạn trả về HTML thay vì JSON, chúng có thể trả về UL đã được xây dựng với các con LI của nó và bạn có thể thêm nó vào một phần tử bằng cách sử dụng data-myapp-answer = "các kết quả". Chức năng thành công của bạn sau đó sẽ trông như thế này:
success: function(html) {
var responseId = $(clicked).data('myapp-response');
if (responseId) {
$('#' + responseId).empty().html(html);
}
}
Để tổng hợp câu trả lời tốt nhất của tôi cho điều này, nếu bạn phải trả về JSON từ các phương thức hành động của mình, bạn đang bỏ qua Chế độ xem phía máy chủ, vì vậy đây thực sự không phải là máy chủ MVC - đó chỉ là MC. Nếu bạn trả lại PartialViewResult bằng html cho các cuộc gọi ajax, thì đây là máy chủ MVC. Vì vậy, nếu ứng dụng của bạn phải trả về dữ liệu JSON cho các cuộc gọi ajax, hãy sử dụng MVVM của máy khách như KnockoutJS.
Dù bằng cách nào, tôi không thích JS bạn đã đăng vì nó trộn bố cục của bạn (thẻ html) với hành vi (tải dữ liệu không đồng bộ). Chọn một trong hai máy chủ MVC với các khung nhìn html một phần hoặc MVVM của máy khách với dữ liệu chế độ xem JSON thuần sẽ giải quyết vấn đề này cho bạn, nhưng việc xây dựng DOM / HTML theo cách thủ công trong javascript vi phạm sự phân tách các mối quan tâm.
- Tạo tệp Javascript Các tính năng thu nhỏ rõ ràng đang có trong .NET 4.5 . Nếu bạn đi theo con đường không phô trương, sẽ không có bất cứ điều gì ngăn bạn tải tất cả các tệp JS trong 1 tập lệnh. Tôi sẽ cẩn thận về việc tạo các tệp JS khác nhau cho từng loại thực thể, bạn sẽ kết thúc với vụ nổ tệp JS. Hãy nhớ rằng, khi tệp tập lệnh của bạn được tải, trình duyệt sẽ lưu nó vào bộ đệm cho các yêu cầu trong tương lai.
- Các truy vấn phức tạp Tôi không coi việc có tính năng như phân trang, sắp xếp, v.v., là phức tạp. Sở thích của tôi là xử lý vấn đề này với logic của URL và phía máy chủ, để làm cho các truy vấn db bị giới hạn khi cần. Tuy nhiên, chúng tôi được triển khai lên Azure, vì vậy tối ưu hóa truy vấn rất quan trọng đối với chúng tôi. Ví dụ : /widgets/show-{pageSize}-per-page/page-{pageNumber}/sort-by-{sortColumn}-{sortDirection}/{keyword}
. EF và LINQ to Entities có thể xử lý phân trang và sắp xếp theo các phương thức như .Take (), .Skip (), .OrderBy () và .OrderByDesceinating (), để bạn có được những gì bạn cần trong chuyến đi db. Tôi chưa tìm thấy nhu cầu về clientlib, vì vậy tôi thực sự không biết nhiều về họ. Nhìn vào câu trả lời khác để được tư vấn thêm về điều đó.
- Dự án lụa Chưa bao giờ nghe nói về điều này, sẽ phải kiểm tra nó. Tôi là một fan hâm mộ lớn của Steve Sanderson, những cuốn sách của anh ấy, BeginCollectionItem HtmlHelper và blog của anh ấy. Điều đó nói rằng, tôi không có bất kỳ kinh nghiệm nào với KnockoutJS trong sản xuất . Tôi đã xem hướng dẫn của nó, nhưng tôi cố gắng không cam kết điều gì cho đến khi ít nhất là phiên bản 2.0. Giống như tôi đã đề cập, KnockoutJS 2.0 vừa được phát hành.
- N-tier Nếu theo tầng, bạn có nghĩa là máy vật lý khác nhau, thì không, tôi không nghĩ bất cứ điều gì đi ra ngoài bất kỳ cửa sổ. Nói chung 3 tầng có nghĩa là bạn có 3 máy. Vì vậy, bạn có thể có một khách hàng béo làm tầng trình bày của mình, chạy trên máy của người dùng. Máy khách béo có thể truy cập một tầng dịch vụ, chạy trên máy chủ ứng dụng và trả về XML hoặc bất cứ thứ gì cho máy khách béo. Và tầng dịch vụ có thể lấy dữ liệu từ máy chủ SQL trên máy thứ 3.
MVC là một lớp, trên 1 tầng. Các bộ điều khiển, mô hình và khung nhìn của bạn đều là một phần của Lớp trình bày, là 1 lớp trong kiến trúc vật lý. MVC thực hiện mẫu Model-View-Controller, đây là nơi bạn có thể thấy các lớp bổ sung. Tuy nhiên, cố gắng không nghĩ 3 khía cạnh này là tầng hoặc lớp. Hãy thử nghĩ cả 3 trong số chúng là Mối quan tâm của Lớp trình bày.
Cập nhật sau bình luận trước / xe buýt / dữ liệu
Được rồi, vì vậy bạn đang sử dụng lớp và lớp thay thế cho nhau. Tôi thường sử dụng thuật ngữ "lớp" cho các bộ phận logic / dự án / lắp ráp và tầng để phân tách mạng vật lý. Xin lỗi vì sự nhầm lẫn.
Bạn sẽ tìm thấy khá nhiều người trong trại MVC nói rằng bạn không nên sử dụng "Mô hình" trong MVC cho mô hình dữ liệu thực thể của mình, bạn cũng không nên sử dụng Bộ điều khiển cho logic nghiệp vụ. Lý tưởng nhất là các mô hình của bạn phải là ViewModels dành riêng cho chế độ xem. Sử dụng một cái gì đó như Automapper, bạn đưa các thực thể của mình từ mô hình miền của bạn và DTO chúng vào ViewModels, được điêu khắc đặc biệt để sử dụng cho chế độ xem.
Bất kỳ quy tắc kinh doanh nào cũng phải là một phần của miền của bạn và bạn có thể triển khai chúng bằng cách sử dụng các dịch vụ miền / mẫu nhà máy / bất cứ điều gì phù hợp trong lớp miền của bạn, không phải trong lớp trình bày MVC. Các bộ điều khiển nên câm, mặc dù không hoàn toàn ngu ngốc như các mô hình và nên giao trách nhiệm cho miền cho bất kỳ điều gì đòi hỏi kiến thức kinh doanh. Bộ điều khiển quản lý luồng yêu cầu và phản hồi HTTP, nhưng bất kỳ thứ gì có giá trị kinh doanh thực sự đều phải cao hơn mức trả của bộ điều khiển.
Vì vậy, bạn vẫn có thể có một kiến trúc phân lớp, với MVC là lớp trình bày. Nó là một ứng dụng khách của lớp ứng dụng, lớp dịch vụ hoặc lớp miền của bạn, tùy thuộc vào cách bạn kiến trúc sư. Nhưng cuối cùng mô hình thực thể của bạn phải là một phần của miền, không phải mô hình trong MVC.