65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 ASP.NET MVC 4、EF、Knockoutjs 和 Bootstrap 设计和开发网站:第 2 部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (73投票s)

2013 年 1 月 13 日

CPOL

10分钟阅读

viewsIcon

234989

downloadIcon

15815

使用 asp.net MVC、EF、Knockoutjs 和 Bootstrap 设计一个简单、易于任何网页设计师理解的网站架构

背景

使用 ASP.NET MVC 4、EF、Knockoutjs 和 Bootstrap 设计和开发网站:第 1 部分

在本文的第一部分中,我讨论了在任何架构中使用关注点分离的好处,并使用 ASP.NET MVC 4、Knockout 和 Bootstrap 创建了一个用户界面应用程序,而无需了解数据将如何流动。

第一部分中描述的任何内容都不应被任何人认为是困难的。如果是这样,请单独给我发电子邮件/评论,在此之前务必吞下一小撮盐。

在本文的第二部分中,我将把我们在第一部分中学到的所有内容应用到需要编写的文章中。我还将介绍此应用程序的数据库设计以及使用结构化层实现业务逻辑。

请务必阅读第一部分以了解介绍细节。

如何使用代码

下载两个 Zip 文件并解压到单独的文件夹中

Application.zip:包含一个解决方案文件下的所有项目。要运行此应用程序,您的 Visual Studio 设置应启用“允许 NuGet 在构建期间下载缺失的包”。或者参考以下链接

http://docs.nuget.org/docs/workflows/using-nuget-without-committing-packages

Application_DB.Zip:它包含用于创建数据库和 PhoneType 和 AddressType 表主表条目的 SQL 脚本。

最后,修改 Application.Web 项目下的连接字符串。

第 2 部分:创建数据库设计(SQL Server 2008 r2):针对 DBA

从数据库设计角度来看,以下是需要实现的主要功能

  • 联系人可以有名字、姓氏和电子邮件。
  • 联系人可以有多个地址。
  • 联系人可以有多个电话号码。

为了实现联系人管理器功能,使用了以下数据库设计。

表之间的关系如下数据库图所示

第 3 部分:设计逻辑层:针对核心开发人员

如前一篇文章所述,我们的最终结构是

接下来,我们将讨论应用程序的整体结构,即组件按逻辑分组到不同的层中,这些层之间有/没有任何限制地相互通信,并且每个逻辑都有自己的目标。层是一种架构风格,它从长远来看解决了维护和增强问题。

因此,让我们继续在我们的解决方案中添加一个类库,并将其命名为 Application.Common。

Application.Common

这是一个类库应用程序,具有一些通用功能,可供不同的逻辑层使用。例如,安全性、日志记录、跟踪、验证等。此层中定义的组件不仅可以被此解决方案中的其他层重用,还可以被其他应用程序利用。为了方便将来更改,我们可以使用依赖注入和抽象,对应用程序进行最小的更改。

例如,在此层中,我们将使用验证器组件来验证数据输入,并使用自定义记录器来记录错误或警告。

下面是添加通用类库后解决方案文件夹的屏幕截图

接下来,我们将在我们的解决方案中添加一个类库,并将其命名为 Application.Core。

Application.Core

此层的组件实现系统核心功能并封装所有相关的业务逻辑。基本上,此层通常包含在其方法中实现领域逻辑的类。此层还在核心层中定义了工作单元契约,以便能够遵守 PI 原则。主要目标是区分并清晰地分离核心领域/业务的行为和基础设施实现细节,例如数据访问和链接到特定技术(如 ORM)的特定存储库,或者只是数据访问库,甚至架构的横切方面。因此,通过隔离应用程序核心功能,我们将大大提高我们系统的可维护性,我们甚至可以替换下层(数据访问、ORM 和数据库),而对应用程序的其余部分影响很小。

接下来,我们将在我们的解决方案中添加一个类库,并将其命名为 Application.DAL。

Application.DAL

DAL 的职责是提供针对存储数据库的数据访问和持久化操作;维护与多个数据库的多个会话和连接等。这里的主要目标是将 EF Context 用接口/契约封装起来,这样我们就可以从管理器和核心层中使用它,而无需直接依赖于 EF。数据持久性组件提供对我们系统边界内(例如,我们特定有界上下文中的主数据库)托管数据以及暴露在我们系统边界之外的数据(例如,外部系统的 Web 服务)的访问。因此,它具有“存储库”等组件,提供访问我们系统边界内托管数据的功能,或“服务代理”来消费其他外部后端系统公开的 Web 服务。此外,此层通常会包含具有可重用代码的基类/组件,供所有存储库类使用。

接下来,我们将在我们的解决方案中添加一个类库,并将其命名为 Application.Repository。

Application.Repository

这是一个类库,只能由 Application.Manager 访问。对于我们领域中的每个主要根 ENTITY,我们都需要创建一个存储库。基本上,存储库是封装访问应用程序数据源所需逻辑的类/组件。因此,它们集中了通用数据访问功能,以便应用程序可以更好地维护,并更好地分离“管理器”和“核心”层拥有的技术和逻辑。

接下来,我们将在我们的解决方案中添加一个类库,并将其命名为 Application.DTO。

Application.DTO

同样,这是一个类库,其中包含不同的容器类,这些类公开属性但没有方法,并在表示层 (Application.Web) 和服务层 (Application.Manager) 之间进行通信。数据传输对象是一个用于封装数据并将其从应用程序的一个子系统发送到另一个子系统的对象。在这里,我们将使用管理器层通过 DTO 在自身和 UI 层之间传输数据。这里的主要好处是它减少了分布式应用程序中需要通过线路发送的数据量。它们在 MVC 模式中也作为出色的模型。我们还可以使用 DTO 来封装方法调用的参数。如果一个方法接受超过 4 或 5 个参数,这可能会很有用。

接下来,我们将在我们的解决方案中添加一个类库,并将其命名为 Application.Manager。

Application.Manager

这是一个类库,只能由 Application.Web 访问。对于每个模块,我们需要声明一个管理器。管理器的主要职责是接受来自 UI/Web 层的请求,然后与所需的存储库通信,并根据条件操作数据,然后返回响应。此层是 UI 和存储库之间的中间层。

Application.Web

在上一篇文章中,我们已经使用 JavaScript 虚拟数据实现了此层。这是一个独立的 ASP.NET MVC Web 应用程序,仅包含用户界面组件,如 html、**。aspx、cshtml、MVC 等。它也可以是任何 Windows 应用程序。它与管理器层的一些方法通信,然后评估结果并选择是显示错误还是 page1 或 page2 等。此层使用 JavaScript 加载用于演示的模型,但数据通过 Ajax 请求在服务器中处理,因此服务器只管理业务逻辑,而 JavaScript 管理演示逻辑。

为了更好地理解各层之间如何通信,让我们回顾一下最初的需求

屏幕 1:联系人列表 - 查看所有联系人

1.1 此屏幕应显示数据库中所有可用的联系人。
1.2 用户应能删除任何联系人。
1.3 用户应能编辑任何联系人详情。
1.4 用户应能创建新联系人。

为了在页面加载时填充网格数据,我们调用 ContactControllerGetAllProfiles() 方法。此方法将数据库中存在的所有配置文件作为 JSON 对象返回,然后我们将其与 JavaScript 对象 self.Profiles 绑定。以下是从 contact.js 调用 GetAllProfiles() 的代码

var ProfilesViewModel = function () {
    var self = this;
    var url = "/contact/GetAllProfiles";
    var refresh = function () {
        $.getJSON(url, {}, function (data) {
            self.Profiles(data);
        });
    };

单击“移除”按钮时,我们调用 ContactControllerDeleteProfile () 方法。此方法从数据库中移除该配置文件。以下是从 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.jscreateProfileeditProfile 的代码

self.createProfile = function () {
    window.location.href = '/Contact/CreateEdit/0';
};

self.editProfile = function (profile) {
    window.location.href = '/Contact/CreateEdit/' + profile.ProfileId;
};

以下是三个主要层之间通信的图示

屏幕 2:创建新联系人

此屏幕应显示一个空白屏幕,以提供以下功能。

2.1 用户应能输入其名字、姓氏和电子邮件地址。
2.2 用户应能通过单击“添加号码”添加任意数量的电话号码。
2.3 用户应能删除任何电话号码。
2.4 用户应能通过单击“添加新地址”添加任意数量的地址。
2.5 用户应能删除任何地址。
2.6 单击“保存”按钮应将联系人详细信息保存到数据库中,用户将返回到联系人列表页面。
2.7 单击“返回个人资料”按钮应将用户返回到联系人列表页面。

屏幕 3:更新现有联系人

此屏幕应显示包含所选联系人信息详情的屏幕。

  • 3.1 用户应能够修改其名字、姓氏和电子邮件地址。
  • 3.2 用户应能够通过单击“添加号码”或“删除”链接来修改/删除/添加任意数量的电话号码。
  • 3.3 用户应能够通过单击“添加新地址”或“删除”链接来修改/删除/添加任意数量的地址。
  • 3.4 单击“保存”按钮应更新数据库中的联系人详细信息,用户将返回到联系人列表页面。
  • 3.5 单击“返回个人资料”按钮应将用户返回到联系人列表页面。

如前述实现中所讨论的,对于“创建新”和“编辑现有”要求,我们使用单个页面 CreateEdit.cshtml,通过识别 profileId 的 URL 值,即如果 URL 中的 profileId 为 0,则表示创建新配置文件的请求,如果它有一些值,则表示编辑现有配置文件的请求。下面是实现细节

无论如何(创建或编辑),在页面加载时,我们都需要初始化 PhoneTypeAddressType 的数据。为此,我们在 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;
        }));
    }
});

最后,对于保存数据,我们在数据库中有两个方法。如果是新建,我们将调用 ContactControllerSaveProfileInformtion() 方法,否则我们将调用 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 () {
    }
});

结论

就是这样!!!希望您喜欢这篇文章。我不是专家,在撰写本文时也没有完全遵循整个行业标准。总而言之,这就是我所知道的开始设计 ASP.NET MVC 4 应用程序的全部内容。我希望您喜欢本教程并有所收获。

所有评论/投票都非常欢迎……微笑 | <img src=,如果您有任何问题,请随时提问;我很乐意在评论中进一步讨论。感谢您的时间!

参考文献

© . All rights reserved.