翻译:使用 ASP.NET MVC 4, EF, Knockoutjs and Bootstrap 设计和开发站点 - 6 - 业务逻辑
Part 3: 设计逻辑层:核心开发
如前所述,我们的解决方案如下所示:
下面我们讨论整个应用的结构,根据应用中不同组件的逻辑相关性,分离到不同的层中,层与层之间的通讯通过或者不通过限制。分层属于架构风格,在应用的长时间生命周期中,解决维护和扩展问题。
所以,让我们在解决方案中添加一个类库项目,命名为 Application.Common.
Application.Common :
这是一个类库项目, 提供公共功能,可以被不同的业务逻辑层使用。例如:安全,日志,跟踪,验证等等. 定义在这个层中的组件,不仅可以被不同的层使用,还可以在不同的应用中使用。为了未来容易使用,我们使用依赖注入和抽象,在应用中实现最小化的修改。
例如,在我们马上用到的,验证组件用来验证数据,定制的日志器来记录错误或者警告。
在添加了 Common 类库之后,解决方案的文件夹如下所示:
Application.Core:
这个层实现系统的核心处理逻辑,封装相关的所有业务逻辑。从基本上说,这个层通常实现领域处理的逻辑。这个层还经常通过核心层的工作单元,以便完成 PI 特性,主要的目标是明确区分和分离核心领域的逻辑与基础架构的具体细节,例如,数据访问和数据仓储的具体技术,像 ORM ,或者简单的数据访问库,或者面向方面的架构等等。通过分离系统的核心功能,我们就可以进一步增强系统的可维护性,甚至可以替换底层的组件,而很少影响到整个应用。
下一步,我们将在解决方案中添加名为 Application.DAL 的类库。
Application.DAL:
DAL 的职责是提供数据访问和数据的持久化处理;维护多个会话,连接到不同的数据库等等。这里的主要目标是通过接口和约定包装 EF 数据访问上下文对象,使得核心层不会直接依赖 EF。数据持久化组件提供驻留在系统内的数据访问,也提供系统外的数据访问。比如对外部系统提供服务的 Web 服务。因此,这里既包含类似 仓储模式的机制来支持对系统内的数据访问,还提供服务代理来使用其它外部系统通过 Web 服务提供的数据,另外,层中还提供可以对所有仓储使用的基类和组件。
下一步,我们将会在解决方案中添加一个名为 Application.Repository 的类库。
Application.Repository:
这个类库仅仅可以通过 Application.Manager 访问,对于领域中的每一个根实体对象,我们需要创建一个仓储,基本上,所谓的仓储就是封装了访问应用数据的处理逻辑的类和组件。而且,它们处理数据处理的核心功能,使得整个应用有更好的可维护性,并且可以在 Manager 和 Core 之间进行解耦。
下一步,我们需要创建名为 Application.DTO 类库。
Application.DTO:
还是一个类库,包含了与实体不同的数据类,其中仅有表示数据的属性,但是没有处理数据的方法,用来在表示层 Applicaiton.Web 和服务层 Application.Manager 之间进行通讯。数据传输对象是用来封装数据的对象,用来从系统的一个子系统将数据传递到另外一个子系统。这里我们使用 DTO 对象在 Manager 层和 UI 层之间传递数据。使用 DTO 的主要优点是可以在分布式的系统中减少数据的流量,在 MVC 模式中,也是重要的一个部分。我们也可以为方法的调用来封装数据,在方法包含4,5个以上的参数的时候,经常使用 DTO 来传递参数。
下一步,我们创建 Application.Manager 类库。
Application.Manager :
这个类库仅仅被 Applicaiton.Web 访问,对于我们在 Manager 中定义的每个模块,Manager 的主要职责是接受来自 UI 的请求,将数据传递到仓储中的领域对象中进行处理,将处理结果返回到界面层,这个层是 UI 层和仓储层之间的中间层。
Application.Web:
在前面的文章中,我们已经使用 javascript 中的模拟数据实现过该层。这里并不仅仅依赖于 ASP.NET MVC,界面可以包含用户的界面组件,像 HTML,.aspx, cshtml,MVC 等等,它也可以是是任何的 Windows 应用。这里通过方法与 Manager 层通讯,封装返回的结果,选择将错误信息显示在页面1 或者页面2 中。这个层使用 javascript 来家在表示层的模型,但实际的数据处理通过 ajax 请求发送到服务器处理,所以,服务器负责处理业务立即。而表示层处理表示逻辑问题。
要理解各层之间通讯的最好方式,就是让我们重温一下初始的需求。
Screen 1: 联系人列表 - 显示所有联系人
1.1 界面需要显示数据库中所有的联系人信息.
1.2 用户可以删除联系人.
1.3 用户可以编辑联系人信息.
1.4 用户可以创建新的联系人.
为了填充表格中的数据,在页面加载的时候,我们调用 ContactController 的 GetAllProfiles() 方法,这个方法返回数据库中所有的联系人信息,然后以 JSON 的形式返回到页面,我们将数据以 self.Profiles 的形式绑定到一个 javascript 对象,下面就是 contact.js 代码中 GetAllProfiles() 方法的定义。
var ProfilesViewModel = function () { var self = this; var url = "/contact/GetAllProfiles"; var refresh = function () { $.getJSON(url, {}, function (data) { self.Profiles(data); }); };
在点击删除按钮的时候,我们调用 ContactController 的 GetAllProfiles() 方法,这个方法从数据库中删除联系人信息。下面是 contact.js 中 DeleteProfile() 方法的定义。
self.removeProfile = function (profile) { if (confirm("Are you sure you want to delete this profile?")) { var id = profile.ProfileId; waitingDialog({}); $.ajax({ type: 'DELETE', url: 'Contact/DeleteProfile/' + id, success: function () { self.Profiles.remove(profile); }, error: function (err) { var error = JSON.parse(err.responseText); $("<div></div>").html(error.Message).dialog({ modal: true, title: "Error", buttons: { "Ok": function () { $(this).dialog("close"); } } }).show(); }, complete: function () { closeWaitingDialog(); } }); } };
对于创建和编辑按钮来说,我们仅仅重定向到 CreateEdit 页面,如果 id 参数是 0 表示创建新联系人,对于编辑来说,id 就是编辑的联系人编号了。下面的代码就是 contact.js 中 的 createProfile 和 editProfile 方法
self.createProfile = function () { window.location.href = '/Contact/CreateEdit/0'; }; self.editProfile = function (profile) { window.location.href = '/Contact/CreateEdit/' + profile.ProfileId; };
下面的图展示了主要的三个层之间的关系。
Screen 2: 创建新联系人
界面提供一个空白的联系人界面,并且提供一下功能.
2.1 用户可以输入用户的姓,名,邮件地址
2.2 通过点击添加号码按钮,允许添加任何个电话号码
2.3 用户可以删除任意电话号码.
2.4 通过点击添加地址按钮,允许添加任意多个地址.
2.5 用户可以删除任意的地址.
2.6 点击保存按钮,可以保存用户输入的所有信息到数据库中,然后回到联系人列表页面.
2.7 点击返回按钮,可以回到联系人列表.
Screen 3: 更新联系人信息
这个界面显示联系人的详细信息.
3.1 用户可以编辑联系人的姓,名,邮件地址.
3.2 用户可以通过点击添加,删除号码按钮来添加,删除用户的电话号码.
3.3 用户可以通过点击添加,删除地址按钮来添加,删除用户的地址。
3.4 点击保存可以将用户的详细信息更新到数据库中,然后返回联系人列表
3.5 点击返回按钮可以回到联系人列表
如前面的实现所见,创建和编辑的需求使用的是同一个页面 CreateEdit.cshtml,通过 profileId 来进行区分,如果 profileId 是 0,表示新建,否则,就是编辑存在的信息,下面是实现的细节。
在任何情况下,在页面加载和初始化 PhoneType 和 AddressType 的时候,在 ContactController 控制器的 InitializePageData() 方法中,在 CreateEdit.js 中的下列代码初始化数组。
var AddressTypeData; var PhoneTypeData; $.ajax({ url: urlContact + '/InitializePageData', async: false, dataType: 'json', success: function (json) { AddressTypeData = json.lstAddressTypeDTO; PhoneTypeData = json.lstPhoneTypeDTO; } });
然后,对于编辑联系人信息来说,我们需要获取详细信息,通过 ContactController 中的GetProfileById() 方法实现,我们修改 CreateEdit.js 。
$.ajax({ url: urlContact + '/GetProfileById/' + profileId, async: false, dataType: 'json', success: function (json) { self.profile = ko.observable(new Profile(json)); self.phoneNumbers = ko.observableArray(ko.utils.arrayMap(json.PhoneDTO, function (phone) { return phone; })); self.addresses = ko.observableArray(ko.utils.arrayMap(json.AddressDTO, function (address) { return address; })); } });
最后,我们使用两个方法保存数据,如果我们是创建新的联系人,调用 ContactController 的 SaveProfileInformtion() 方法,否则调用 UpdateProfileInformation() 方法,我们将 CreateEdit.js 修改如下。
$.ajax({ type: (self.profile().ProfileId > 0 ? 'PUT' : 'POST'), cache: false, dataType: 'json', url: urlContact + (self.profile().ProfileId > 0 ? '/UpdateProfileInformation?id=' + self.profile().ProfileId : '/SaveProfileInformation'), data: JSON.stringify(ko.toJS(self.profile())), contentType: 'application/json; charset=utf-8', async: false, success: function (data) { window.location.href = '/contact'; }, error: function (err) { var err = JSON.parse(err.responseText); var errors = ""; for (var key in err) { if (err.hasOwnProperty(key)) { errors += key.replace("profile.", "") + " : " + err[key]; } } $("<div></div>").html(errors).dialog({ modal: true, title: JSON.parse(err.responseText).Message, buttons: { "Ok": function () { $(this).dialog("close"); } } }).show(); }, complete: function () { } });
总结
就是这样,希望你喜欢这篇文章,我不是一个专家,我希望你享受这个系列并且能够学到些什么。
如果有任何问题欢迎进行讨论,谢谢。
How to use code
从这里可以下载数据库的脚本:
Application_DB.sql
在 VS 中运行程序,需要启用 Allow NuGet to download missing packages during build ,
或者看下面的链接说明。
http://docs.nuget.org/docs/workflows/using-nuget-without-committing-packages
最后,修改你的 Application.Web 项目中的数据库连接串。
参考资料
- http://knockoutjs.com/
- https://github.com/ericmbarnard/Knockout-Validation/wiki/Configuration
- http://twitter.github.com/bootstrap/
- http://docs.castleproject.org/Windsor.MainPage.ashx
- http://microsoftnlayerapp.codeplex.com/
- http://msdn.microsoft.com/en-us/library/ff921348.aspx
Last edited Jan 18 at 6:41 AM by anandranjanpandey, version 5