分离关注点的通用设计原则
分离关注点是开发时的指导原则1。此原则主张应根据软件执行的工作类型将软件分离。例如,假设应用程序中包含两个逻辑,其中一个逻辑标识要显示给用户的注意事项,另一个以特定方式设置这些注意事项的格式,使其更加显眼。负责选择为哪些事项设置格式的行为应与负责设置格式的行为区分开,因为这两种行为只是碰巧彼此相关联的独立关注点。
从体系结构上来说,按此原则有逻辑地构建应用程序应将核心业务行为与基础结构及用户界面逻辑区分开。理想情况下,业务规则和逻辑应单独位于一个项目中,且该项目不依赖于应用程序中的其他项目。此区分操作可帮助确保该业务模型易于测试,且可在不与低级别实现详细信息紧密耦合的情况下逐步改进。在应用程序体系结构的使用层背后,关注点分离是核心设计思想。
ASP.NET MVC 概述
ASP.NET MVC 是使用 MVC 设计模式构建 Web 应用和 API 的框架2。其中:
- Model:模型
- View:视图
- Controller:控制器
MVC 模式
模型-视图-控制器 (MVC) 设计模式将应用程序分成 3 个主要组成部分:模型、视图和控制器。此模式有助于实现关注点分离。使用此模式,用户请求被路由至控制器,控制器负责使用模型来执行用户操作且/或检索查询结果,控制器选择要显示给用户的视图,并为其提供所需的模型数据。
下图显示 3 个主要组件及其相互引用关系:

模型责任
模型 (Model) 表示应用程序和任何应由其执行的业务逻辑或操作的状态。业务逻辑应与保持应用程序状态的任何实现逻辑共同封装在模型中。强类型视图通常使用 ViewModel 类型,旨在包含要在该视图上显示的数据。控制器从模型创建并填充 ViewModel 实例。
视图责任
视图 (View) 负责通过用户界面展示内容。它们使用 Razor 视图引擎在 HTML 标记中嵌入 .NET 代码。视图页面中应尽可能少得包含逻辑处理,且其中的任何逻辑都必须与展示内容相关。如果发现需要在视图文件中执行大量逻辑以显示复杂模型中的数据,请考虑使用 View Component、ViewModel 或视图模板来简化视图。
控制器责任
控制器 (Controller) 是处理用户交互、使用模型并最终选择呈现视图的组件。在 MVC 应用程序中,视图仅显示信息;控制器处理并响应用户输入和交互。在 MVC 模式中,控制器是初始入口点,负责选择要使用的模型类型和要呈现的视图,控制应用如何响应给定请求,并因此得名。
控制器不应由于责任过多而变得过于复杂。要阻止控制器逻辑变得过于复杂,请将业务逻辑推出控制器并推入域模型。
创建 ASP.NET MVC Web 应用程序(.NET Framework)
有关如何创建并运行 ASP.NET MVC Web 应用程序的指南可参考:
ASP.NET MVC Web 应用程序的目录结构
Visual Studio 会在创建 ASP.NET MVC Web 应用程序时自动添加一些文件与目录结构,但这并不是固定的目录结构。事实上,那些处理大型应用程序的开发人员通常跨多个项目来分割应用程序,以便使应用程序更易于管理,默认的项目结构哦确实提供了一个很好的默认目录约定,是的应用程序的关注点更加清晰。
本站点介绍涉及 ASP.NET Code MVC Web 应用程序会采用在默认目录结构基础上再添加一些其它实用文件夹的以下目录结构:
{解决方案名称}
{项目名称}命名空间
- App_Code公共业务类
- App_Data本地数据文件
App_Start部分配置代码,如:路由、捆绑、Web API
- BundleConfig.cs打包器配置
- FilterConfig.cs过滤器配置
- RouteConfig.cs路由配置
- WebApiConfig.csWeb API 配置
Areas区域
{区域名称}定义区域
- Controllers区域控制器
- Data区域本地数据
- Scripts区域脚本
- Views区域视图
- {区域名称}AreaRegistration.cs区域注册配置
- Asserts自定义资源,如本地图片等非代码内容
- Content用于存储 CSS 样式等非脚本内容
Controllers控制器
- {名称}Controllers.cs控制器名称
- fonts自定义字体,主要用于 Bootstrap 模板系统引用
- MigrationsEntity Framework Code First 所生成与使用的迁移代码
- Models模型
- ScriptsJavascript 库与脚本
Views视图
{名称}视图子文件夹
- {视图名称}单视图
Shared共享视图文件夹
- _Layout.cshtml共享母版视图
- Error.cshtml共享错误页视图
- _ViewStart.cshtml呈现视图前运行的代码
- favicon.ico站点图标
- Global.asaxASP.NET 应用程序的入口点 [4]
- packages.configNuGet 包引用列表
- Startup.csASP.NET Core 应用程序的入口点,由 OWIN 提供 [4]
- Web.configASP.NET Web 应用程序配置信息
关键文件解析
~/Global.asax.cs
Global.asax.cs文件是一个特殊文件,其中包含 ASP.NET 应用程序生命周期事件的事件处理程序5。默认地,Global.asax.cs包含了Application_Start()方法,该方法在 MVC 应用程序启动时会被调用。Global.asax.cs默认代码如下:
using System.Web;using System.Web.Mvc;using System.Web.Optimization;using System.Web.Routing;
namespace {命名空间}{ public class MvcApplication : HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); // 注册所有区域 FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); // 注册所有全局过滤器 RouteConfig.RegisterRoutes(RouteTable.Routes); // 注册所有传统路由信息 BundleConfig.RegisterBundles(BundleTable.Bundles); // 注册所有捆绑器配置 } }}~/App_Start/RouteConfig.cs
路由概述
RouteConfig.cs文件配置了 ASP.NET 应用程序的传统路由信息5。
ASP.NET MVC 框架中的路由主要有两种用途:
- 匹配传入的请求(该请求不匹配服务器文件系统中的文件),并把这些请求映射到控制器操作
- 构造传出的 URL,用来响应控制器操作
代码功能分析
以下是此文件默认代码:
using System.Web.Mvc;using System.Web.Routing;
namespace {命名空间}{ public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { // {resource}.axd 表示扩展名为 .axd 所有资源,如 WebResource.axd // {*pathInfo} 表示匹配任何路径 // IgnoreRoute("{resource}.axd/{*pathInfo}") 方法通知路由系统忽略处理 ASP.NET 的 Web 资源文件 // 如:WebResource.axd 或 ScriptResource.axd routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute( name: "Default", // 路由名称 url: "{controller}/{action}/{id}", // 路由模板 defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } // 默认路由参数值 ); } }}这样的默认配置代码制定了一个名为 Default 的路由模板,它接受形如URL/{controller}/{action}/{id}的请求,其中:
{controller}为控制器名称,未提供时的默认值为Home{action}为动作名称,未提供时的默认值未Index{id}为一URL参数,UrlParameter.Optional表示包含可选参数的只读值
例如:
- 当 URL 请求为
URL本身时,路由会将请求传递至HomeController.Index()方法 - 当 URL 请求为
URL/Contact/Find/35时,路由会将请求传递至ContactController.Find(35)方法
更多
有关 ASP.NET MVC 路由的更多帮助可参考:
~/App_Start/BundleConfig.cs
捆绑与缩减概述
捆绑和缩减是可以在 ASP.NET 4.5 中使用的两种技术来提高请求加载时间。捆绑和缩减减少了对服务器的请求数并减小了请求资产(如 CSS 和 JavaScript)的大小,从而缩短了加载时间6。
捆绑是 ASP.NET 4.5 中引入的一项新功能,可轻松地将多个文件组合或捆绑到一个文件中。捆绑器可以创建 CSS、JavaScript 和其他捆绑包。较少的文件意味着 HTTP 请求更少,从而可以提高首页加载性能。
缩减对 JavaScript 或 CSS 执行各种不同的代码优化,如删除不必要的空白和注释并缩短变量名称为一个字符。例如考虑下面的 JavaScript 函数
AddAltToImg = function (imageTagAndImageID, imageContext) { ///<signature> ///<summary> Adds an alt tab to the image // </summary> //<param name="imgElement" type="String">The image selector.</param> //<param name="ContextForImage" type="String">The image context.</param> ///</signature> var imageElement = $(imageTagAndImageID, imageContext); imageElement.attr('alt', imageElement.attr('id').replace(/ID/, ''));}缩减之后,该函数将缩减为以下内容:
AddAltToImg=function(n,t){var i=$(n,t);i.attr("alt",i.attr("id").replace(/ID/, ""))}控制捆绑与缩减
web.config中的configuration/system.web/compilation的debug值会影响捆绑与缩减功能的启用或禁用,将debug值设置为false可以启用捆绑与缩减。
<system.web> <compilation debug="true" /> <!-- 为了清晰起见,其它与本节介绍的内容无关的行被省略,但不表示不应该包含其它行 --></system.web>另外,BundleTable类中的的EnableOptimizations可以覆盖web.config中的debug配置。
public static void RegisterBundles(BundleCollection bundles){ bundles.Add(new ScriptBundle("~/bundles/jquery").Include( "~/Scripts/jquery-{version}.js"));
// 为了清晰起见,其它与本节介绍的内容无关的代码被省略,但不表示不应该包含其它代码 BundleTable.EnableOptimizations = true;}代码功能分析
BundleConfig.cs文件的默认代码如下:
using System.Web.Optimization;
namespace IoTJissen{ public class BundleConfig { public static void RegisterBundles(BundleCollection bundles) { bundles.Add(new ScriptBundle("~/bundles/jquery").Include( "~/Scripts/jquery-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include( "~/Scripts/jquery.validate*"));
bundles.Add(new ScriptBundle("~/bundles/modernizr").Include( "~/Scripts/modernizr-*"));
bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include( "~/Scripts/bootstrap.js"));
bundles.Add(new StyleBundle("~/Content/css").Include( "~/Content/bootstrap.css", "~/Content/site.css")); } }}此代码对bundles使用Add()方法创建了 5 个捆绑包,其中:
- 参数为
ScriptBundle的为 JavaScript 捆绑包 - 参数为
StyleBundle的为 CSS 捆绑包
ScriptBundle与StyleBundle的参数皆为捆绑包的虚拟路径。
Bundle类的Include方法接收若干个带有路径的文件匹配字符串,捆绑器在捆绑时会遵循以下约定:
- 如果
FileX.min.js和FileX.js同时存在,则在发布时选择FileX.js进行捆绑,在调试时选择FileX.min.js进行捆绑 - 忽略仅由 IntelliSense 使用的 “-vsdoc” 文件(如 jquery-1.7.1-vsdoc.js)
使用{version}通配符可以通配版本号,这将允许开发人员在使用 NuGet 更新脚本版本时无需更改捆绑器或视图页面中的引用代码。
要使用已经创建好的捆绑包,可以在视图页使用如下代码:
<!DOCTYPE html><html lang="en"><head> @* 为了清晰起见,其它与本节介绍的内容无关的代码被省略,但不表示不应该包含其它代码 *@ @Styles.Render("~/Content/css") @Scripts.Render("~/bundles/modernizr")</head><body> @* 为了清晰起见,其它与本节介绍的内容无关的代码被省略,但不表示不应该包含其它代码 *@ @Scripts.Render("~/bundles/jquery") @Scripts.Render("~/bundles/bootstrap") @RenderSection("scripts", required: false)</body></html>例如,@Scripts.Render("~/bundles/jquery")引用了虚拟路径为~/bundles/jquery的 JavaScript 捆绑包,进而引用了~/Scripts/jquery-{version}.js,在调试时会进一步匹配~/Scripts/jquery-3.4.1.js,且在更新 jQuery 时无需对上述代码做出任何改动。
更多
有关捆绑与缩减的更多帮助可参考:
~/Views/Shared/_Layout.cshtml
使用母版页为不同的页面创建一致的布局
大多数站点都具有一致的外观和布局,大多数网站都由含页眉、导航、动态内容、页脚等几个部分,且每个站点内的各个页面也都遵循基本固定的布局,具有大量相似的元素或结构。建此布局的低效方法是在每个页面上分别定义标题、导航栏和页脚,但每次都要复制相同的标记。如果要更改某些内容(例如,更新页脚),则必须单独更改每个页面。
在 ASP.NET 网页中,可以定义为网站上的页面提供总体容器的母版页面。例如,母版页可以包含页眉、导航区和页脚,并包含一个占位符,其中包含动态内容。然后,可以定义单独的内容页,其中只包含该页的标记和代码。内容页面不必是完整的 HTML 页面;它们甚至不必有<body>元素。它们会在页面中或其它位置设置一行代码,告诉 ASP.NET 要在其中显示内容时使用的母版页。下面的图片显示了此关系的具体工作原理:

母版页名称需以前导下划线(_)开始,如果页的名称以下划线开头,则 ASP.NET 不会直接将该页发送到浏览器。此约定允许您定义站点所需的页面,但用户不能直接请求该页面。
有关如何在 ASP.NET 中使用母版页的更多帮助可参考:
参考
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 2200 天
部分内容可能已过时
评论