我的朋友TerryR,您和我应该喝一杯。我们有一些类似的问题。
1.项目结构:我同意Eduardo的观点,即MVC应用程序中的文件夹结构有待改进。您具有标准的Controllers,Models和Views文件夹。但是随后,Views文件夹被分解为每个Controller的不同文件夹,再加上Shared文件夹。每个View / ControllerName或Views / Shared都可以细分为EditorTemplates和DisplayTemplates。但是,它可以让您决定如何组织Models文件夹(可以使用或不使用子文件夹和其他名称空间声明)。
上帝禁止您使用Areas,它们会为每个区域复制Controllers,Models和Views文件夹结构。
/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
这意味着,如果您正在使用WidgetController之类的东西,则必须在其他文件夹中查找相关的WidgetViewModels,WidgetViews,WidgetEditorTemplates,WidgetDisplayTemplates等。尽管这很麻烦,但我坚持并没有偏离这些MVC约定。至于将模型,控制器和视图放在同一文件夹中但具有不同的命名空间,我避免了这一点,因为我使用了ReSharper。它会在与该类所在的文件夹不匹配的名称空间下模糊地加下划线。我知道我可以关闭此R#功能,但对项目的其他部分有帮助。
对于非类文件,MVC提供了开箱即用的内容和脚本。再次,我们尝试将所有静态/未编译文件保留在这些位置,以遵循惯例。每当我们合并使用主题(图像和css)的js库时,主题文件都放在/ content下的某个位置。对于脚本,我们只需将它们全部直接放入/ scripts。最初,这是为了从VS获取JS intellisense,但现在无论从/ scripts中放置什么,我们都可以从R#获取JS intellisense,我想我们可以偏离这一点,按文件夹划分脚本以更好地组织。您在使用ReSharper吗?它是纯金IMO。
T4MVC是另一个对重构有很大帮助的黄金。使用此方法,我们无需为区域名称,控制器名称,操作名称甚至内容和脚本中的文件键入字符串路径。T4MVC强烈为您键入所有魔术字符串。这是一个小样本,说明您使用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.数据访问:我没有使用过PetaPoco的经验,但是我确信值得一试。对于复杂的报表,您是否考虑过SQL Server报表服务?或者,您是否在其他数据库上运行?抱歉,我不清楚您到底要什么。我们使用EF + LINQ,但我们也对如何在域类中生成报告投入了一定的知识。因此,我们拥有控制器调用域服务调用存储库,而不是直接拥有控制器调用存储库。对于临时报告,我们使用SQL Reporting Services,这仍然不是完美的选择,但是我们的用户希望能够轻松地将数据引入Excel,而SSRS使得这一切变得容易。
3.客户端代码组织和UI渲染:我想可以在这里提供一些帮助。从《 MVC入门》和《 AJAX入门》一书中摘录。考虑一下:
<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" />
现在忽略ajax成功功能(稍后再介绍)。您可以通过一个脚本来执行一些操作:
$('.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();
}
}
}
});
}
});
上面的代码将负责确认,显示微调器,显示等待消息,并在ajax调用完成后隐藏微调器/等待消息。您可以使用data- *属性(例如非侵入式库)配置行为。
一般的问题
-客户端MVC与服务器MVC?我没有尝试解放成功函数中执行的操作,因为它看起来像您的控制器正在返回JSON。如果您的控制器返回JSON,则可能需要查看KnockoutJS。Knockout JS 2.0版今天发布。它可以直接插入您的JSON,以便可以观察到的点击可以将数据自动绑定到您的javascript模板。另一方面,如果您不介意ajax操作方法返回HTML而不是JSON,则它们可以返回已构造的UL及其LI子级,并且可以使用data-myapp-response =将其附加到元素上“结果”。您的成功函数将如下所示:
success: function(html) {
var responseId = $(clicked).data('myapp-response');
if (responseId) {
$('#' + responseId).empty().html(html);
}
}
总结一下,我的最佳答案是,如果必须从操作方法返回JSON,则跳过了服务器端View,因此这实际上不是服务器MVC,而只是MC。如果将带有html的PartialViewResult返回给ajax调用,则这是服务器MVC。因此,如果您的应用必须为ajax调用返回JSON数据,请使用客户端MVVM(如KnockoutJS)。
无论哪种方式,我都不喜欢您发布的JS,因为它会将布局(html标记)与行为(异步数据加载)混合在一起。选择具有部分html视图的服务器MVC或具有纯JSON viewmodel数据的客户端MVVM将为您解决此问题,但是在javascript中手动构造DOM / HTML会违反关注点分离。
-Javascript文件创建显然,.NET 4.5中提供了缩小功能。如果您走的很顺畅,那么应该没有什么阻止您将所有JS加载到1个脚本文件中。我会为每种实体类型创建不同的JS文件时要小心,最终会导致JS文件爆炸。请记住,一旦加载了脚本文件,浏览器应将其缓存以备将来使用。
-复杂的查询我认为分页,排序等功能并不复杂。我的首选是使用URL和服务器端逻辑来处理此问题,以根据需要限制数据库查询。但是,由于我们已部署到Azure,因此查询优化对我们很重要。例如:/widgets/show-{pageSize}-per-page/page-{pageNumber}/sort-by-{sortColumn}-{sortDirection}/{keyword}
。EF和LINQ to Entities可以使用.Take()、. Skip()、. OrderBy()和.OrderByDescending()等方法处理分页和排序,因此您可以在db旅行期间获得所需的内容。我还没有找到需要clientlib的需求,所以说实话我对它们了解不多。寻找其他答案以获取更多建议。
-Project Silk从来没有听说过这个,将不得不检查一下。我是Steve Sanderson,他的书,BeginCollectionItem HtmlHelper和他的博客的忠实拥护者。就是说,我没有在生产中使用KnockoutJS的经验。我已经检查了它的教程,但是在它的版本至少为2.0之前,我尽量不做任何事情。就像我提到的,KnockoutJS 2.0刚刚发布。
-N层如果按层表示不同的物理机器,那么不,我认为任何窗口都不会出错。通常,三层意味着您有3台计算机。因此,您可能有一个胖客户端作为您的表示层,可以在用户的计算机上运行。胖客户端可能会访问服务层,该服务层运行在应用程序服务器上,并向胖客户端返回XML或其他内容。服务层可能会从第三台计算机上的SQL Server获取其数据。
MVC是一层,位于1层上。控制器,模型和视图都属于表示层的一部分,表示层是物理体系结构中的1层。MVC实现了“模型-视图-控制器”模式,在这里您可能会看到其他层。但是,请尽量不要将这三个方面视为层或层。尝试将所有这三个问题视为表示层问题。
在pres / bus / data注释后更新
好的,因此您可以互换使用层和层。我通常将术语“层”用于逻辑/项目/程序集划分,将层用于物理网络分离。对困惑感到抱歉。
您会在MVC阵营中找到很多人,他们说您不应将MVC中的“模型”用于实体数据模型,也不应将控制器用于业务逻辑。理想情况下,您的模型应该是特定于视图的ViewModel。使用诸如Automapper之类的东西,您可以将您的实体从域模型中提取出来,并将它们DTO放入ViewModels中,该实体专门为视图而雕刻。
任何业务规则也应该属于您的域,您可以使用域服务/工厂模式/在域层(而不是MVC表示层)中适当的方式来实现它们。控制器应该是愚蠢的,尽管不像模型那样愚蠢,并且应该对域负责任何需要业务知识的事情。控制器管理HTTP请求和响应的流程,但是任何具有实际业务价值的事物都应高于控制器的薪资等级。
因此,您仍然可以使用MVC作为表示层的分层体系结构。它是应用程序层,服务层或域层的客户端,具体取决于您如何设计它。但是最终,您的实体模型应该是域的一部分,而不是MVC中的模型。