单页应用程序(SPA)用于企业应用程序(Angular2 & WebApi)- 第 1 部分 - 概述






4.73/5 (158投票s)
如何使用 Angular2 和 WebApi(RESTful)构建企业应用程序的 SPA。
系列中的其他文章
- 概述
- 添加新权限
- 项目结构
- 多语言 (i18n)
- DI & IoC - 为什么以及为什么不?
- RESTful & WebApi
- 管理应用生命周期
- 构建和部署应用
- TinyERP新版本(使用Angular 2 (typescript))
- CQRS:避免企业应用中的性能问题(第1部分)
- 多个数据存储:扩展你的存储库(第1部分)
- 多个数据存储:扩展你的存储库(第2部分)
- 基本身份验证(用户名/密码)与OWIN
- 基于令牌的授权与OWIN
- 重构架构/模式(第1部分)
引言
注意:在本系列文章中,我使用的是https://colorlib.com/polygon/gentelella的免费模板
TinyERP的参考源代码已移至“https://github.com/tranthanhtu0vn/TinyERP”。请查看指定分支以了解更多详情。如何运行和设置代码。
首先,我想与您分享的是“我们不学习技术,我们学习如何将技术用于我们的业务”。
注意:在此代码中,我们使用的是Angular 2的候选发布版本。新版本将于本月(2017年4月)发布,预计在未来两周内。
对于使用官方Angular版本的TinyERP新版本,请访问:https://codeproject.org.cn/Articles/1183727/TinyERP-New-version-with-Angular-is-available
如今,随着Web开发的飞速发展,越来越多的桌面应用程序(如HRM、CRM、Payroll等)被迁移到Web应用程序。
因此,Web应用程序可以充分利用Web环境带来的诸多优势。在网上很容易找到Web应用程序和桌面应用程序的优缺点,所以我们在这里不再讨论。
本系列文章的目的是向您介绍我是如何组织这些类型应用程序的项目。
一些亮点技术
- Angular2 (typescript)
- WebApi (RESTful)
- 实体框架
- Bootstrap
- IoC
- 多层架构
- 模块化你的应用
- 多语言
- 设计模式(工作单元、存储库等)
- SOLID原则
- Gulp
- NodeJs
目前仍有许多错误和需要改进的地方。
如何获取代码
在https://github.com/techcoaching/TinyERP.git上检出代码
如何运行代码
代码分为两个文件夹组织
- Client: 这是用typescript编写的客户端代码,使用angular2框架。
- Api: 这是处理客户端请求的后端代码。
运行客户端
- 要运行客户端代码,请检查并安装缺失的组件
- Nodejs: https://node.org.cn/en/
- Visual Code: https://vscode.js.cn
- 运行应用
- 打开命令提示符,转到客户端的根文件夹
- 输入“npm install”以安装缺失的nodejs包。
- 使用“npm start”运行应用
控制台输出
浏览器输出
到此为止,我们可以成功运行客户端了
运行API
- 首次运行时,我们需要在app.common\configurations\configuration.(debug|release).config中更新connectionstring
- 在visual studio (2015)中打开Api项目。
- 按f5在IIS Express上运行Api,我们将在浏览器中看到输出
配置默认路由,将删除此错误页面。
应用的首次预览
- 我们需要先登录才能继续
- 使用tu.tran@yahoo.com/123456登录,我们将看到如下默认页面(可以在应用程序配置中更改)
角色列表页面
在本节中,我们将介绍如何使用我的代码实现新页面。
分析
按照我的习惯,我在实现之前会分析页面,这有助于我们识别
- 列出完成页面所需的步骤。
- 找出缺失的信息,以便我们能立即寻求帮助。
- 思考从客户端到服务器端的逻辑流程,从UI到存储库。这样写出的代码会更漂亮。我的很多同事,他们会先写代码,然后再调试。当出现问题时,它会改变你的行为,我们会尝试修改代码使其能够工作。这可能会破坏逻辑流程,代码也不会遵循应用程序中使用的约定、架构。这会引发一些新的潜在问题,并且代码很难维护。
在分析完页面后,我们发现需要完成的列表如下:
客户端
+ 创建新模块(称为security),我将详细讨论如何对应用程序进行模块化。
+ 为Roles组件(angular中的页面称为component)和相应菜单项(左侧面板的子菜单)注册路由。
+ 将此设置模块导入并注册到应用程序配置中。
+ 创建组件文件(用于UI的html文件,用于逻辑处理的ts文件,以及用于该组件视图模型的ts文件)。
+ 实现Roles组件的UI
+ 实现Roles组件的逻辑(也包括调用服务器和获取数据)
+ 为此页面实现服务(服务将调用REST api获取权限列表)。
API端
+ 添加名为RolesController的新控制器,用于处理与Role相关的请求。
+ 添加RoleService和RoleRepository以获取Roles列表(在这种情况下,我使用了多层架构)
+ 将实体添加到相应的DbContext中。
好的,现在我们将一步一步地详细进行。
实现客户端
在本节中,<root>文件夹是客户端代码的文件夹(指向“client”文件夹的路径)。
- 创建一个新模块(称为security)。按照应用程序的约定,我们在“<root>/app/modules/secutiry/_share/config”中创建一个新的module.ts文件,如下图所示:
以及route.ts文件
在此文件中,我们将提供security模块的设置信息,例如:子菜单项列表、模块中的路由等。
- 为Roles组件(angular中的页面称为component)和相应菜单项(左侧面板的子菜单)注册路由。将此行添加到上面步骤中创建的module.ts文件中。
- 导入并注册此设置模块到应用程序配置中。这将把security模块注册到应用程序中,显示模块及其子菜单项将在左侧面板上显示。
系统将自动为我们注册security模块中的路由。
- 创建组件文件(用于UI的html文件,用于逻辑处理的ts文件,以及用于该组件视图模型的ts文件)。
- 实现Roles组件的UI,其中已经创建了一些指令,我们在本文中使用它们。我将在其他文章中更详细地解释如何在其他文章中实现这些指令。
在此html中,我们使用
- “grid”指令,用于显示角色列表。我们可以在mode.options属性中传递一些事件处理程序和grid的列。
- “page-action”指令,它将在角色列表上显示“Add Role”按钮,如下图所示:
- page、page-header、page-content指令,这是我的框架中页面的结构。
- 实现Roles组件的逻辑(也包括调用服务器和获取数据)
要声明新组件,我们需要
- 指定组件的模板文件(第9行)。
- 声明组件html文件中使用的指令数量(第11行)。如果指令未在此处声明,它将不会在roles.html文件中渲染,angular会将其视为HTML标签(<page-action/>、<grid />)。
- 声明新的Roles类,继承自BasePage。
- 声明组件的模型。每个组件都应该有自己的视图模型,这有助于我们降低roles.ts文件的复杂性。
- 通过调用roleService的适当方法从api获取数据。我们将此移至服务层,让roles.ts专注于页面的行为。
注意:我们将i18nHelper传递给RolesModel的构造函数。这将用于根据当前语言(支持多语言)解析grid列的标签。
- 为此页面实现服务(服务将调用REST api获取权限列表)。
这只是一个简单的REST api调用,用于获取角色列表。有关IConnection的更多信息,请参阅<root>/app/common/connectors/RESTConnector.ts
实现API
在本节中,<root>文件夹是api代码的文件夹(指向“api”文件夹的路径)。
- API项目结构
- App.Common:包含应用程序中任何地方都可以使用的通用代码
- App.Api:这是客户端可以调用的公共REST api。
- App.Service:包含所有服务接口及其相应的DTO。
- App.Service.Impl:仅包含App.Service中相应接口的实现。
- App.Repository:包含所有存储库接口。每个存储库只操作数据库中的一个实体。
- App.Repository.Impl:仅包含App.Resitory中相应接口的实现。
- App.Entity:包含应用程序的所有实体。
- App.Context:目前,它包含应用程序中的所有DbContext,因为我们使用了EF框架。我打算将来将其与App.Entity合并。
注意:层之间的通信(例如:App.Api -> App.Service => App.Repository),我们只使用接口。
- 添加名为RolesController的新控制器,用于处理与Role相关的请求。
按照应用程序的约定,我们在“App.Api/Features/Security”中创建新的RolesController
namespace App.Api.Features.Security { [RoutePrefix("api/roles")] public class RolesController : ApiController { [HttpGet] [Route("")] public IResponseData<IList<RoleListItemSummary>> GetRoles() { IResponseData<IList<RoleListItemSummary>> response = new ResponseData<IList<RoleListItemSummary>>(); try { IRoleService roleService = IoC.Container.Resolve<IRoleService>(); IList<RoleListItemSummary> roles=roleService.GetRoles(); response.SetData(roles); } catch (ValidationException ex) { response.SetErrors(ex.Errors); response.SetStatus(System.Net.HttpStatusCode.PreconditionFailed); } return response; } } }
我认为代码相当简单。从控制器调用适当的服务接口的“GetRoles”并获取角色列表(dto)。
IoC将返回该服务的具体实例。
在App.Service.Impl中,我们有一个bootstrap.cs文件,这是注册服务接口及其具体实现的地方。
namespace App.Service.Impl { public class Bootstrap : App.Common.Tasks.BaseTask<IBaseContainer>, IBootstrapper { public Bootstrap():base(App.Common.ApplicationType.All) { } public void Execute(IBaseContainer context) { context.RegisterSingleton<App.Service.Security.IRoleService, App.Service.Impl.Security.RoleService>(); } } }
- 添加IRoleService和RoleService以获取Roles列表(我在此案例中使用多层架构)
namespace App.Service.Security { public interface IRoleService { System.Collections.Generic.IList<RoleListItemSummary> GetRoles(); } } namespace App.Service.Impl.Security { internal class RoleService : IRoleService { public IList<RoleListItemSummary> GetRoles() { IRoleRepository repository = IoC.Container.Resolve<IRoleRepository>(); return repository.GetItems<RoleListItemSummary>(); } } }
注意
- 我们应该为每个操作定义适当的DTO,因为在大多数情况下,我们只获取实体的某些属性,这将有助于代码在未来更容易维护。
- RoleService类被声明为internal,而不是public类。因此,在此dll外部无法创建该类的实例。
- 添加IRoleRepository和RoleRepository以获取Roles列表
namespace App.Repository.Secutiry { public interface IRoleRepository: App.Common.Data.IBaseContentRepository<Role> { } } namespace App.Repository.Impl.Security { internal class RoleRepository: BaseContentRepository<Role>, IRoleRepository { public RoleRepository() : base(new App.Context.AppDbContext(App.Common.IOMode.Read)) { } public RoleRepository(IUnitOfWork uow) : base(uow.Context as IMSSQLDbContext) { } } }
注意:与RoleService相同,RoleRepository类也声明为internal。因此,只有接口可以在此项目外部使用。
- 将实体添加到相应的DbContext中
namespace App.Entity.Security { public class Role:BaseContent { public IList<Permission> Permissions { get; set; } public Role():base() { this.Permissions = new List<Permission>(); } public Role(string name, string desc, IList<Permission> permissions): this() { this.Name = name; this.Key = App.Common.Helpers.UtilHelper.ToKey(name); this.Description = desc; if (permissions == null) { return; } this.Permissions = permissions; } } } namespace App.Context { public class AppDbContext : App.Common.Data.MSSQL.MSSQLDbContext { public AppDbContext(IOMode mode = IOMode.Read) : base(new App.Common.Data.MSSQL.MSSQLConnectionString(), mode) { } public System.Data.Entity.DbSet<Role> Roles { get; set; } } }
有关Angular的更多信息,请参阅Angular2 - 概述
摘要
在本文中,我为您概述了如何获取角色列表并在UI上显示它们。
在下一篇文章中,我们将继续讨论如何创建和更新角色,并深入探讨框架的更多细节(例如:项目结构、指令等)。
有关更多信息
- Angular2,请访问
- 有关Angular的概述,请访问 https://codeproject.org.cn/Articles/1164843/Angular-Overview
- 有关Angular中的路由,请访问 https://codeproject.org.cn/Articles/1164813/Angular-Routing
- 有关Angular中的组件,请访问 https://codeproject.org.cn/Articles/1166112/Angular-Component
- 有关Angular中的绑定,请访问 https://codeproject.org.cn/Articles/1166418/Angular-Binding
- 有关Angular中的指令,请访问
- REST
- 有关概述概念,请访问 https://codeproject.org.cn/Articles/1164842/REST-Overview
- 有关在WebApi (C#) 中使用REST,请访问 https://codeproject.org.cn/Articles/1168872/REST-in-WebApi
- 有关高级REST,请访问 https://codeproject.org.cn/Articles/1176049/REST-in-advance